From 23ad7f8a4231a3b78abec32bb67bbd0c415b5011 Mon Sep 17 00:00:00 2001 From: "Github Workflow (on behalf of markemer)" Date: Thu, 7 May 2026 16:55:19 +0000 Subject: [PATCH 01/10] Release 8.3.2 --- CHANGELOG.md | 14 ++++++++++++++ android/CHANGELOG.md | 6 ++++++ android/package.json | 2 +- cli/CHANGELOG.md | 13 +++++++++++++ cli/package.json | 2 +- core/CHANGELOG.md | 4 ++++ core/package.json | 2 +- ios/CHANGELOG.md | 4 ++++ ios/package.json | 2 +- lerna.json | 2 +- 10 files changed, 46 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ee704f6..3851a6663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) + +### Bug Fixes + +- **cli:** add cSettings support for compiler flags in generated Package.swift ([#8448](https://github.com/ionic-team/capacitor/issues/8448)) ([0bd0676](https://github.com/ionic-team/capacitor/commit/0bd0676315c5fd77e50312dd7b5bf4990dcbd7d0)) +- **cli:** add system framework and weak framework support in SPM Package.swift ([#8447](https://github.com/ionic-team/capacitor/issues/8447)) ([3232f0f](https://github.com/ionic-team/capacitor/commit/3232f0fe1d9811b0b5c500e3dc05cb8a250177f8)) +- **cli:** correct Capacitor plugin SPM compat check ([#8440](https://github.com/ionic-team/capacitor/issues/8440)) ([e5ccc45](https://github.com/ionic-team/capacitor/commit/e5ccc451dda27d56bca824ed644bd20fe4d988cb)) +- **cli:** generate binaryTarget entries for custom xcframeworks in Package.swift ([#8445](https://github.com/ionic-team/capacitor/issues/8445)) ([1f7e33f](https://github.com/ionic-team/capacitor/commit/1f7e33fca43d183332ec19d22b0d75ef81d8cc6d)) +- **cli:** generate resource entries in Package.swift ([#8455](https://github.com/ionic-team/capacitor/issues/8455)) ([790bd27](https://github.com/ionic-team/capacitor/commit/790bd27123497111984227010c3162cec94a108e)) +- **cli:** handle Cordova plugins without iOS source files ([#8443](https://github.com/ionic-team/capacitor/issues/8443)) ([0da130e](https://github.com/ionic-team/capacitor/commit/0da130eb7a861bee4e2c35bc0aac53ba9c983fc3)) +- **cli:** link plugin dependencies in Package.swift ([#8457](https://github.com/ionic-team/capacitor/issues/8457)) ([b3c769e](https://github.com/ionic-team/capacitor/commit/b3c769e856c826b1174518877cf86ac7ce73bf09)) +- **ios:** support Cordova plugins with Package.swift ([#8438](https://github.com/ionic-team/capacitor/issues/8438)) ([139943b](https://github.com/ionic-team/capacitor/commit/139943b0c05fddb2d1ce2d6f468800fddf17b4cf)) +- **SystemBars:** avoid extra view padding on API <= 34 ([#8439](https://github.com/ionic-team/capacitor/issues/8439)) ([5b135a7](https://github.com/ionic-team/capacitor/commit/5b135a70217be560e7176c8d5b514cc92ed3e4e4)) + ## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16) ### Bug Fixes diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 5812a4832..ca3b6f0ef 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) + +### Bug Fixes + +- **SystemBars:** avoid extra view padding on API <= 34 ([#8439](https://github.com/ionic-team/capacitor/issues/8439)) ([5b135a7](https://github.com/ionic-team/capacitor/commit/5b135a70217be560e7176c8d5b514cc92ed3e4e4)) + ## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16) ### Bug Fixes diff --git a/android/package.json b/android/package.json index f55229c31..5ecf01e07 100644 --- a/android/package.json +++ b/android/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/android", - "version": "8.3.1", + "version": "8.3.2", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index ac66f222f..1cc5720a1 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) + +### Bug Fixes + +- **cli:** add cSettings support for compiler flags in generated Package.swift ([#8448](https://github.com/ionic-team/capacitor/issues/8448)) ([0bd0676](https://github.com/ionic-team/capacitor/commit/0bd0676315c5fd77e50312dd7b5bf4990dcbd7d0)) +- **cli:** add system framework and weak framework support in SPM Package.swift ([#8447](https://github.com/ionic-team/capacitor/issues/8447)) ([3232f0f](https://github.com/ionic-team/capacitor/commit/3232f0fe1d9811b0b5c500e3dc05cb8a250177f8)) +- **cli:** correct Capacitor plugin SPM compat check ([#8440](https://github.com/ionic-team/capacitor/issues/8440)) ([e5ccc45](https://github.com/ionic-team/capacitor/commit/e5ccc451dda27d56bca824ed644bd20fe4d988cb)) +- **cli:** generate binaryTarget entries for custom xcframeworks in Package.swift ([#8445](https://github.com/ionic-team/capacitor/issues/8445)) ([1f7e33f](https://github.com/ionic-team/capacitor/commit/1f7e33fca43d183332ec19d22b0d75ef81d8cc6d)) +- **cli:** generate resource entries in Package.swift ([#8455](https://github.com/ionic-team/capacitor/issues/8455)) ([790bd27](https://github.com/ionic-team/capacitor/commit/790bd27123497111984227010c3162cec94a108e)) +- **cli:** handle Cordova plugins without iOS source files ([#8443](https://github.com/ionic-team/capacitor/issues/8443)) ([0da130e](https://github.com/ionic-team/capacitor/commit/0da130eb7a861bee4e2c35bc0aac53ba9c983fc3)) +- **cli:** link plugin dependencies in Package.swift ([#8457](https://github.com/ionic-team/capacitor/issues/8457)) ([b3c769e](https://github.com/ionic-team/capacitor/commit/b3c769e856c826b1174518877cf86ac7ce73bf09)) +- **ios:** support Cordova plugins with Package.swift ([#8438](https://github.com/ionic-team/capacitor/issues/8438)) ([139943b](https://github.com/ionic-team/capacitor/commit/139943b0c05fddb2d1ce2d6f468800fddf17b4cf)) + ## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16) ### Bug Fixes diff --git a/cli/package.json b/cli/package.json index b9f5d1ccc..abc3774df 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/cli", - "version": "8.3.1", + "version": "8.3.2", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 60a9ca82b..08c8815c3 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) + +**Note:** Version bump only for package @capacitor/core + ## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16) **Note:** Version bump only for package @capacitor/core diff --git a/core/package.json b/core/package.json index 54e8c7110..bc3bebc0f 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/core", - "version": "8.3.1", + "version": "8.3.2", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md index a16975c79..111b642ac 100644 --- a/ios/CHANGELOG.md +++ b/ios/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) + +**Note:** Version bump only for package @capacitor/ios + ## [8.3.1](https://github.com/ionic-team/capacitor/compare/8.3.0...8.3.1) (2026-04-16) ### Bug Fixes diff --git a/ios/package.json b/ios/package.json index 4f7200cad..0972ed946 100644 --- a/ios/package.json +++ b/ios/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/ios", - "version": "8.3.1", + "version": "8.3.2", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/lerna.json b/lerna.json index 5f513f656..d0a1ebc02 100644 --- a/lerna.json +++ b/lerna.json @@ -11,6 +11,6 @@ "tagVersionPrefix": "" } }, - "version": "8.3.1", + "version": "8.3.2", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } From b2d771926a180e60deea31992d7d4abcd5ca3bc7 Mon Sep 17 00:00:00 2001 From: jcesarmobile Date: Fri, 8 May 2026 18:44:22 +0200 Subject: [PATCH 02/10] fix(cli): copy plugin files in CocoaPods projects (#8467) --- cli/src/ios/update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/ios/update.ts b/cli/src/ios/update.ts index 9fcd1c718..82ca85a88 100644 --- a/cli/src/ios/update.ts +++ b/cli/src/ios/update.ts @@ -564,7 +564,7 @@ async function copyPluginsNativeFiles(config: Config, cordovaPlugins: Plugin[]) const isSPM = (await config.ios.packageManager) === 'SPM'; for (const p of cordovaPlugins) { const platformTag = getPluginPlatform(p, platform); - if (platformTag.$?.package) { + if (isSPM && platformTag.$?.package) { continue; } const sourceFiles = getPlatformElement(p, platform, 'source-file'); From 76ff70e0e40bbbca63c5195c572f166e5ce82089 Mon Sep 17 00:00:00 2001 From: "Github Workflow (on behalf of markemer)" Date: Fri, 8 May 2026 17:08:40 +0000 Subject: [PATCH 03/10] Release 8.3.3 --- CHANGELOG.md | 6 ++++++ android/CHANGELOG.md | 4 ++++ android/package.json | 2 +- cli/CHANGELOG.md | 6 ++++++ cli/package.json | 2 +- core/CHANGELOG.md | 4 ++++ core/package.json | 2 +- ios/CHANGELOG.md | 4 ++++ ios/package.json | 2 +- lerna.json | 2 +- 10 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3851a6663..d4ad41ff2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) + +### Bug Fixes + +- **cli:** copy plugin files in CocoaPods projects ([#8467](https://github.com/ionic-team/capacitor/issues/8467)) ([b2d7719](https://github.com/ionic-team/capacitor/commit/b2d771926a180e60deea31992d7d4abcd5ca3bc7)) + ## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) ### Bug Fixes diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index ca3b6f0ef..d4b2fc534 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) + +**Note:** Version bump only for package @capacitor/android + ## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) ### Bug Fixes diff --git a/android/package.json b/android/package.json index 5ecf01e07..07db8a26e 100644 --- a/android/package.json +++ b/android/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/android", - "version": "8.3.2", + "version": "8.3.3", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 1cc5720a1..452ba4cc6 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) + +### Bug Fixes + +- **cli:** copy plugin files in CocoaPods projects ([#8467](https://github.com/ionic-team/capacitor/issues/8467)) ([b2d7719](https://github.com/ionic-team/capacitor/commit/b2d771926a180e60deea31992d7d4abcd5ca3bc7)) + ## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) ### Bug Fixes diff --git a/cli/package.json b/cli/package.json index abc3774df..cd17f5d2f 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/cli", - "version": "8.3.2", + "version": "8.3.3", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 08c8815c3..26f5723fe 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) + +**Note:** Version bump only for package @capacitor/core + ## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) **Note:** Version bump only for package @capacitor/core diff --git a/core/package.json b/core/package.json index bc3bebc0f..373249f98 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/core", - "version": "8.3.2", + "version": "8.3.3", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md index 111b642ac..b9b7863b0 100644 --- a/ios/CHANGELOG.md +++ b/ios/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) + +**Note:** Version bump only for package @capacitor/ios + ## [8.3.2](https://github.com/ionic-team/capacitor/compare/8.3.1...8.3.2) (2026-05-07) **Note:** Version bump only for package @capacitor/ios diff --git a/ios/package.json b/ios/package.json index 0972ed946..bd4c0a320 100644 --- a/ios/package.json +++ b/ios/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/ios", - "version": "8.3.2", + "version": "8.3.3", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/lerna.json b/lerna.json index d0a1ebc02..d64bfcd44 100644 --- a/lerna.json +++ b/lerna.json @@ -11,6 +11,6 @@ "tagVersionPrefix": "" } }, - "version": "8.3.2", + "version": "8.3.3", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } From b7e167bbab47c57a5f72ff9e76a726ab7d8a1c1f Mon Sep 17 00:00:00 2001 From: "Alex J." Date: Tue, 12 May 2026 15:19:58 -0400 Subject: [PATCH 04/10] chore(cli): update telemetry endpoint (#8464) --- cli/src/ipc.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/src/ipc.ts b/cli/src/ipc.ts index 41894e53a..37c38110b 100644 --- a/cli/src/ipc.ts +++ b/cli/src/ipc.ts @@ -49,9 +49,9 @@ export async function receive(msg: IPCMessage): Promise { // This request is only made if telemetry is on. const req = request( { - hostname: 'api.ionicjs.com', + hostname: 'metrics-capacitor.outsystems.com', port: 443, - path: '/events/metrics', + path: '/metrics', method: 'POST', headers: { 'Content-Type': 'application/json', @@ -60,7 +60,7 @@ export async function receive(msg: IPCMessage): Promise { (response) => { debug('Sent %O metric to events service (status: %O)', data.name, response.statusCode); - if (response.statusCode !== 204) { + if (response.statusCode !== 202) { response.on('data', (chunk) => { debug('Bad response from events service. Request body: %O', chunk.toString()); }); From de1e506e48204f3d9a51bc911fafc341fd81fc36 Mon Sep 17 00:00:00 2001 From: "Github Workflow (on behalf of alexgerardojacinto)" Date: Tue, 12 May 2026 20:38:58 +0000 Subject: [PATCH 05/10] Release 8.3.4 --- CHANGELOG.md | 4 ++++ android/CHANGELOG.md | 4 ++++ android/package.json | 2 +- cli/CHANGELOG.md | 4 ++++ cli/package.json | 2 +- core/CHANGELOG.md | 4 ++++ core/package.json | 2 +- ios/CHANGELOG.md | 4 ++++ ios/package.json | 2 +- lerna.json | 2 +- 10 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ad41ff2..a7f2b7713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12) + +**Note:** Version bump only for package capacitor + ## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) ### Bug Fixes diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index d4b2fc534..74d538d9f 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12) + +**Note:** Version bump only for package @capacitor/android + ## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) **Note:** Version bump only for package @capacitor/android diff --git a/android/package.json b/android/package.json index 07db8a26e..8ae93f3f2 100644 --- a/android/package.json +++ b/android/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/android", - "version": "8.3.3", + "version": "8.3.4", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 452ba4cc6..bba24688f 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12) + +**Note:** Version bump only for package @capacitor/cli + ## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) ### Bug Fixes diff --git a/cli/package.json b/cli/package.json index cd17f5d2f..9df346b14 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/cli", - "version": "8.3.3", + "version": "8.3.4", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 26f5723fe..017293794 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12) + +**Note:** Version bump only for package @capacitor/core + ## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) **Note:** Version bump only for package @capacitor/core diff --git a/core/package.json b/core/package.json index 373249f98..eef113a50 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/core", - "version": "8.3.3", + "version": "8.3.4", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md index b9b7863b0..87ee73bb4 100644 --- a/ios/CHANGELOG.md +++ b/ios/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.3.4](https://github.com/ionic-team/capacitor/compare/8.3.3...8.3.4) (2026-05-12) + +**Note:** Version bump only for package @capacitor/ios + ## [8.3.3](https://github.com/ionic-team/capacitor/compare/8.3.2...8.3.3) (2026-05-08) **Note:** Version bump only for package @capacitor/ios diff --git a/ios/package.json b/ios/package.json index bd4c0a320..a7b8d3133 100644 --- a/ios/package.json +++ b/ios/package.json @@ -1,6 +1,6 @@ { "name": "@capacitor/ios", - "version": "8.3.3", + "version": "8.3.4", "description": "Capacitor: Cross-platform apps with JavaScript and the web", "homepage": "https://capacitorjs.com", "author": "Ionic Team (https://ionic.io)", diff --git a/lerna.json b/lerna.json index d64bfcd44..1e53d6bdc 100644 --- a/lerna.json +++ b/lerna.json @@ -11,6 +11,6 @@ "tagVersionPrefix": "" } }, - "version": "8.3.3", + "version": "8.3.4", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } From 731a82e554d9c1270241897228e934c38759bbbb Mon Sep 17 00:00:00 2001 From: jcesarmobile Date: Thu, 21 May 2026 17:39:31 +0200 Subject: [PATCH 06/10] chore: format java code (#8475) --- .../com/getcapacitor/BridgeWebChromeClient.java | 12 ++++++------ .../src/main/java/com/getcapacitor/FileUtils.java | 6 +++--- .../src/main/java/com/getcapacitor/Plugin.java | 4 ++-- .../cordova/MockCordovaWebViewImpl.java | 14 ++++++-------- .../com/getcapacitor/plugin/CapacitorHttp.java | 4 ++-- .../java/com/getcapacitor/ConfigBuildingTest.java | 6 +++--- 6 files changed, 22 insertions(+), 24 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java index 6ca1c8a83..58371a9ff 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java @@ -246,14 +246,14 @@ public boolean onJsPrompt(WebView view, String url, String message, String defau public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { super.onGeolocationPermissionsShowPrompt(origin, callback); Logger.debug("onGeolocationPermissionsShowPrompt: DOING IT HERE FOR ORIGIN: " + origin); - final String[] geoPermissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION }; + final String[] geoPermissions = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}; if (!PermissionHelper.hasPermissions(bridge.getContext(), geoPermissions)) { permissionListener = (isGranted) -> { if (isGranted) { callback.invoke(origin, true, false); } else { - final String[] coarsePermission = { Manifest.permission.ACCESS_COARSE_LOCATION }; + final String[] coarsePermission = {Manifest.permission.ACCESS_COARSE_LOCATION}; if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && PermissionHelper.hasPermissions(bridge.getContext(), coarsePermission) @@ -294,7 +294,7 @@ public boolean onShowFileChooser( filePathCallback.onReceiveValue(null); } }; - final String[] camPermission = { Manifest.permission.CAMERA }; + final String[] camPermission = {Manifest.permission.CAMERA}; permissionLauncher.launch(camPermission); } } else { @@ -305,7 +305,7 @@ public boolean onShowFileChooser( } private boolean isMediaCaptureSupported() { - String[] permissions = { Manifest.permission.CAMERA }; + String[] permissions = {Manifest.permission.CAMERA}; return ( PermissionHelper.hasPermissions(bridge.getContext(), permissions) || !PermissionHelper.hasDefinedPermission(bridge.getContext(), Manifest.permission.CAMERA) @@ -343,7 +343,7 @@ private boolean showImageCapturePicker(final ValueCallback filePathCallba activityListener = (activityResult) -> { Uri[] result = null; if (activityResult.getResultCode() == Activity.RESULT_OK) { - result = new Uri[] { imageFileUri }; + result = new Uri[] {imageFileUri}; } filePathCallback.onReceiveValue(result); }; @@ -362,7 +362,7 @@ private boolean showVideoCapturePicker(final ValueCallback filePathCallba activityListener = (activityResult) -> { Uri[] result = null; if (activityResult.getResultCode() == Activity.RESULT_OK) { - result = new Uri[] { activityResult.getData().getData() }; + result = new Uri[] {activityResult.getData().getData()}; } filePathCallback.onReceiveValue(result); }; diff --git a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java index ce09e7c2f..0fdf64feb 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java +++ b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java @@ -118,7 +118,7 @@ else if (isMediaDocument(uri)) { } final String selection = "_id=?"; - final String[] selectionArgs = new String[] { split[1] }; + final String[] selectionArgs = new String[] {split[1]}; return getDataColumn(context, contentUri, selection, selectionArgs); } @@ -195,7 +195,7 @@ private static String getDataColumn(Context context, Uri uri, String selection, String path = null; Cursor cursor = null; final String column = "_data"; - final String[] projection = { column }; + final String[] projection = {column}; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); @@ -292,7 +292,7 @@ private static String getPathToNonPrimaryVolume(Context context, String tag) { } private static String sanitizeFilename(String displayName) { - String[] badCharacters = new String[] { "..", "/" }; + String[] badCharacters = new String[] {"..", "/"}; String[] segments = displayName.split("/"); String fileName = segments[segments.length - 1]; for (String suspString : badCharacters) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index 19c8b8b3a..e55d933cc 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -465,7 +465,7 @@ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String c * @param callbackName the name of the callback to run when the permission request is complete */ protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginCall call, @NonNull String callbackName) { - requestPermissionForAliases(new String[] { alias }, call, callbackName); + requestPermissionForAliases(new String[] {alias}, call, callbackName); } /** @@ -583,7 +583,7 @@ public void pluginRequestAllPermissions() { */ @Deprecated public void pluginRequestPermission(String permission, int requestCode) { - ActivityCompat.requestPermissions(getActivity(), new String[] { permission }, requestCode); + ActivityCompat.requestPermissions(getActivity(), new String[] {permission}, requestCode); } /** diff --git a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java index 1115429d7..020c1457a 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java +++ b/android/capacitor/src/main/java/com/getcapacitor/cordova/MockCordovaWebViewImpl.java @@ -70,14 +70,12 @@ public CapacitorEvalBridgeMode(WebView webView, CordovaInterface cordova) { @Override public void onNativeToJsMessageAvailable(final NativeToJsMessageQueue queue) { - cordova - .getActivity() - .runOnUiThread(() -> { - String js = queue.popAndEncodeAsJs(); - if (js != null) { - webView.evaluateJavascript(js, null); - } - }); + cordova.getActivity().runOnUiThread(() -> { + String js = queue.popAndEncodeAsJs(); + if (js != null) { + webView.evaluateJavascript(js, null); + } + }); } } diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java index 46bc17417..7d5ab337e 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java @@ -18,8 +18,8 @@ @CapacitorPlugin( permissions = { - @Permission(strings = { Manifest.permission.WRITE_EXTERNAL_STORAGE }, alias = "HttpWrite"), - @Permission(strings = { Manifest.permission.READ_EXTERNAL_STORAGE }, alias = "HttpRead") + @Permission(strings = {Manifest.permission.WRITE_EXTERNAL_STORAGE}, alias = "HttpWrite"), + @Permission(strings = {Manifest.permission.READ_EXTERNAL_STORAGE}, alias = "HttpRead") } ) public class CapacitorHttp extends Plugin { diff --git a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java index 1af51039d..ae12edb02 100644 --- a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java +++ b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java @@ -42,7 +42,7 @@ public void setup() { config = new CapConfig.Builder(context) .setAllowMixedContent(true) - .setAllowNavigation(new String[] { "http://www.google.com" }) + .setAllowNavigation(new String[] {"http://www.google.com"}) .setAndroidScheme("test") .setCaptureInput(true) .setLoggingEnabled(true) @@ -64,7 +64,7 @@ public void setup() { @Test public void getCoreConfigValues() { assertTrue(config.isMixedContentAllowed()); - assertArrayEquals(new String[] { "http://www.google.com" }, config.getAllowNavigation()); + assertArrayEquals(new String[] {"http://www.google.com"}, config.getAllowNavigation()); assertEquals("test", config.getAndroidScheme()); assertTrue(config.isInputCaptured()); assertTrue(config.isLoggingEnabled()); @@ -97,7 +97,7 @@ public void getPluginInt() { @Test public void getPluginArray() { - String[] comparison = new String[] { "5", "6", "7", "8" }; + String[] comparison = new String[] {"5", "6", "7", "8"}; String[] testArray = config.getPluginConfiguration(TEST_PLUGIN_NAME).getArray("var5"); assertArrayEquals(comparison, testArray); } From bc745364129853769ca2fcfc3cc2a6c7624ee513 Mon Sep 17 00:00:00 2001 From: jcesarmobile Date: Thu, 21 May 2026 18:04:37 +0200 Subject: [PATCH 07/10] chore: format java code (#8477) --- .../java/com/getcapacitor/BridgeWebChromeClient.java | 12 ++++++------ .../src/main/java/com/getcapacitor/FileUtils.java | 6 +++--- .../src/main/java/com/getcapacitor/Plugin.java | 4 ++-- .../java/com/getcapacitor/plugin/CapacitorHttp.java | 4 ++-- .../java/com/getcapacitor/ConfigBuildingTest.java | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java index 58371a9ff..6ca1c8a83 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java +++ b/android/capacitor/src/main/java/com/getcapacitor/BridgeWebChromeClient.java @@ -246,14 +246,14 @@ public boolean onJsPrompt(WebView view, String url, String message, String defau public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) { super.onGeolocationPermissionsShowPrompt(origin, callback); Logger.debug("onGeolocationPermissionsShowPrompt: DOING IT HERE FOR ORIGIN: " + origin); - final String[] geoPermissions = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}; + final String[] geoPermissions = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION }; if (!PermissionHelper.hasPermissions(bridge.getContext(), geoPermissions)) { permissionListener = (isGranted) -> { if (isGranted) { callback.invoke(origin, true, false); } else { - final String[] coarsePermission = {Manifest.permission.ACCESS_COARSE_LOCATION}; + final String[] coarsePermission = { Manifest.permission.ACCESS_COARSE_LOCATION }; if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && PermissionHelper.hasPermissions(bridge.getContext(), coarsePermission) @@ -294,7 +294,7 @@ public boolean onShowFileChooser( filePathCallback.onReceiveValue(null); } }; - final String[] camPermission = {Manifest.permission.CAMERA}; + final String[] camPermission = { Manifest.permission.CAMERA }; permissionLauncher.launch(camPermission); } } else { @@ -305,7 +305,7 @@ public boolean onShowFileChooser( } private boolean isMediaCaptureSupported() { - String[] permissions = {Manifest.permission.CAMERA}; + String[] permissions = { Manifest.permission.CAMERA }; return ( PermissionHelper.hasPermissions(bridge.getContext(), permissions) || !PermissionHelper.hasDefinedPermission(bridge.getContext(), Manifest.permission.CAMERA) @@ -343,7 +343,7 @@ private boolean showImageCapturePicker(final ValueCallback filePathCallba activityListener = (activityResult) -> { Uri[] result = null; if (activityResult.getResultCode() == Activity.RESULT_OK) { - result = new Uri[] {imageFileUri}; + result = new Uri[] { imageFileUri }; } filePathCallback.onReceiveValue(result); }; @@ -362,7 +362,7 @@ private boolean showVideoCapturePicker(final ValueCallback filePathCallba activityListener = (activityResult) -> { Uri[] result = null; if (activityResult.getResultCode() == Activity.RESULT_OK) { - result = new Uri[] {activityResult.getData().getData()}; + result = new Uri[] { activityResult.getData().getData() }; } filePathCallback.onReceiveValue(result); }; diff --git a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java index 0fdf64feb..ce09e7c2f 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java +++ b/android/capacitor/src/main/java/com/getcapacitor/FileUtils.java @@ -118,7 +118,7 @@ else if (isMediaDocument(uri)) { } final String selection = "_id=?"; - final String[] selectionArgs = new String[] {split[1]}; + final String[] selectionArgs = new String[] { split[1] }; return getDataColumn(context, contentUri, selection, selectionArgs); } @@ -195,7 +195,7 @@ private static String getDataColumn(Context context, Uri uri, String selection, String path = null; Cursor cursor = null; final String column = "_data"; - final String[] projection = {column}; + final String[] projection = { column }; try { cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); @@ -292,7 +292,7 @@ private static String getPathToNonPrimaryVolume(Context context, String tag) { } private static String sanitizeFilename(String displayName) { - String[] badCharacters = new String[] {"..", "/"}; + String[] badCharacters = new String[] { "..", "/" }; String[] segments = displayName.split("/"); String fileName = segments[segments.length - 1]; for (String suspString : badCharacters) { diff --git a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java index e55d933cc..19c8b8b3a 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/Plugin.java +++ b/android/capacitor/src/main/java/com/getcapacitor/Plugin.java @@ -465,7 +465,7 @@ protected void requestAllPermissions(@NonNull PluginCall call, @NonNull String c * @param callbackName the name of the callback to run when the permission request is complete */ protected void requestPermissionForAlias(@NonNull String alias, @NonNull PluginCall call, @NonNull String callbackName) { - requestPermissionForAliases(new String[] {alias}, call, callbackName); + requestPermissionForAliases(new String[] { alias }, call, callbackName); } /** @@ -583,7 +583,7 @@ public void pluginRequestAllPermissions() { */ @Deprecated public void pluginRequestPermission(String permission, int requestCode) { - ActivityCompat.requestPermissions(getActivity(), new String[] {permission}, requestCode); + ActivityCompat.requestPermissions(getActivity(), new String[] { permission }, requestCode); } /** diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java index 7d5ab337e..46bc17417 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/CapacitorHttp.java @@ -18,8 +18,8 @@ @CapacitorPlugin( permissions = { - @Permission(strings = {Manifest.permission.WRITE_EXTERNAL_STORAGE}, alias = "HttpWrite"), - @Permission(strings = {Manifest.permission.READ_EXTERNAL_STORAGE}, alias = "HttpRead") + @Permission(strings = { Manifest.permission.WRITE_EXTERNAL_STORAGE }, alias = "HttpWrite"), + @Permission(strings = { Manifest.permission.READ_EXTERNAL_STORAGE }, alias = "HttpRead") } ) public class CapacitorHttp extends Plugin { diff --git a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java index ae12edb02..1af51039d 100644 --- a/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java +++ b/android/capacitor/src/test/java/com/getcapacitor/ConfigBuildingTest.java @@ -42,7 +42,7 @@ public void setup() { config = new CapConfig.Builder(context) .setAllowMixedContent(true) - .setAllowNavigation(new String[] {"http://www.google.com"}) + .setAllowNavigation(new String[] { "http://www.google.com" }) .setAndroidScheme("test") .setCaptureInput(true) .setLoggingEnabled(true) @@ -64,7 +64,7 @@ public void setup() { @Test public void getCoreConfigValues() { assertTrue(config.isMixedContentAllowed()); - assertArrayEquals(new String[] {"http://www.google.com"}, config.getAllowNavigation()); + assertArrayEquals(new String[] { "http://www.google.com" }, config.getAllowNavigation()); assertEquals("test", config.getAndroidScheme()); assertTrue(config.isInputCaptured()); assertTrue(config.isLoggingEnabled()); @@ -97,7 +97,7 @@ public void getPluginInt() { @Test public void getPluginArray() { - String[] comparison = new String[] {"5", "6", "7", "8"}; + String[] comparison = new String[] { "5", "6", "7", "8" }; String[] testArray = config.getPluginConfiguration(TEST_PLUGIN_NAME).getArray("var5"); assertArrayEquals(comparison, testArray); } From 93c72de40a2ec4c78b33659250cb08340083088e Mon Sep 17 00:00:00 2001 From: Robin Genz Date: Thu, 21 May 2026 18:54:21 +0200 Subject: [PATCH 08/10] feat: add method getDouble to plugin config (#7638) Co-authored-by: jcesarmobile --- .../java/com/getcapacitor/PluginConfig.java | 11 ++++++++++ .../java/com/getcapacitor/util/JSONUtils.java | 20 +++++++++++++++++++ ios/Capacitor/Capacitor/PluginConfig.swift | 7 +++++++ 3 files changed, 38 insertions(+) diff --git a/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java b/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java index 0f00fc530..1d21ae90e 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java +++ b/android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java @@ -65,6 +65,17 @@ public int getInt(String configKey, int defaultValue) { return JSONUtils.getInt(config, configKey, defaultValue); } + /** + * Get a double value for a plugin in the Capacitor config. + * + * @param configKey The key of the value to retrieve + * @param defaultValue A default value to return if the key does not exist in the config + * @return The value from the config, if key exists. Default value returned if not + */ + public double getDouble(String configKey, double defaultValue) { + return JSONUtils.getDouble(config, configKey, defaultValue); + } + /** * Get a string array value for a plugin in the Capacitor config. * diff --git a/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java b/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java index 1d2fc2078..f24e5aa0b 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java +++ b/android/capacitor/src/main/java/com/getcapacitor/util/JSONUtils.java @@ -75,6 +75,26 @@ public static int getInt(JSONObject jsonObject, String key, int defaultValue) { return defaultValue; } + /** + * Get a double value from the given JSON object. + * + * @param jsonObject A JSON object to search + * @param key A key to fetch from the JSON object + * @param defaultValue A default value to return if the key cannot be found + * @return The value at the given key in the JSON object, or the default value + */ + public static double getDouble(JSONObject jsonObject, String key, double defaultValue) { + String k = getDeepestKey(key); + try { + JSONObject o = getDeepestObject(jsonObject, key); + return o.getDouble(k); + } catch (JSONException ignore) { + // value was not found + } + + return defaultValue; + } + /** * Get a JSON object value from the given JSON object. * diff --git a/ios/Capacitor/Capacitor/PluginConfig.swift b/ios/Capacitor/Capacitor/PluginConfig.swift index f2461e102..8d73bc1de 100644 --- a/ios/Capacitor/Capacitor/PluginConfig.swift +++ b/ios/Capacitor/Capacitor/PluginConfig.swift @@ -30,6 +30,13 @@ import Foundation return defaultValue } + @objc public func getDouble(_ configKey: String, _ defaultValue: Double) -> Double { + if let val = (self.config)[keyPath: KeyPath(configKey)] as? Double { + return val + } + return defaultValue + } + public func getArray(_ configKey: String, _ defaultValue: JSArray? = nil) -> JSArray? { if let val = (self.config)[keyPath: KeyPath(configKey)] as? JSArray { return val From e456de083e19644f484bec5a5359cb67960ac8bc Mon Sep 17 00:00:00 2001 From: Joey Pender Date: Fri, 22 May 2026 13:39:38 -0500 Subject: [PATCH 09/10] fix(SystemBars): make `safe-area-inset-x` available on API <= 34 (#8424) --- .../com/getcapacitor/plugin/SystemBars.java | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java index b335b4b1c..76e74207c 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java @@ -164,13 +164,18 @@ private Insets calcSafeAreaInsets(WindowInsetsCompat insets) { } private void initSafeAreaCSSVariables() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && insetHandlingEnabled) { + WindowInsetsCompat insets; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { View v = (View) this.getBridge().getWebView().getParent(); - WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(v); - if (insets != null) { - Insets safeAreaInsets = calcSafeAreaInsets(insets); - injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); - } + insets = ViewCompat.getRootWindowInsets(v); + } else { + insets = WindowInsetsCompat.CONSUMED; + } + + if (insets != null) { + Insets safeAreaInsets = calcSafeAreaInsets(insets); + injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); } } @@ -186,10 +191,8 @@ private void initWindowInsetsListener() { // We need to correct for a possible shown IME v.setPadding(0, 0, 0, keyboardVisible ? imeInsets.bottom : 0); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) { - Insets safeAreaInsets = calcSafeAreaInsets(insets); - injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); - } + Insets safeAreaInsets = calcSafeAreaInsets(insets); + injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); return new WindowInsetsCompat.Builder(insets) .setInsets( @@ -221,10 +224,8 @@ private void initWindowInsetsListener() { .setInsets(WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout(), Insets.of(0, 0, 0, 0)) .build(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && hasViewportCover && insetHandlingEnabled) { - Insets safeAreaInsets = calcSafeAreaInsets(newInsets); - injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); - } + Insets safeAreaInsets = calcSafeAreaInsets(newInsets); + injectSafeAreaCSS(safeAreaInsets.top, safeAreaInsets.right, safeAreaInsets.bottom, safeAreaInsets.left); return newInsets; }); From 5bfdb1ddb19f1978eee6494075da0a476cef58d2 Mon Sep 17 00:00:00 2001 From: Angel J <78835633+iamanaws@users.noreply.github.com> Date: Sat, 23 May 2026 11:17:58 -0700 Subject: [PATCH 10/10] fix(http): support binary request bodies --- .../src/main/assets/native-bridge.js | 86 +++++-- .../util/CapacitorHttpUrlConnection.java | 28 +-- core/http.md | 2 +- core/native-bridge.ts | 103 +++++++-- core/src/core-plugins.ts | 2 +- core/src/tests/bridge.spec.ts | 211 +++++++++++++++++- .../Plugins/CapacitorUrlRequest.swift | 2 +- .../Capacitor/assets/native-bridge.js | 86 +++++-- 8 files changed, 444 insertions(+), 76 deletions(-) diff --git a/android/capacitor/src/main/assets/native-bridge.js b/android/capacitor/src/main/assets/native-bridge.js index f5e7cc440..1458690a7 100644 --- a/android/capacitor/src/main/assets/native-bridge.js +++ b/android/capacitor/src/main/assets/native-bridge.js @@ -44,6 +44,33 @@ var nativeBridge = (function (exports) { reader.onerror = reject; reader.readAsBinaryString(file); }); + const readArrayBufferAsBase64 = (arrayBuffer) => { + const bytes = new Uint8Array(arrayBuffer); + const chunkSize = 0x8000; + let binary = ''; + for (let i = 0; i < bytes.length; i += chunkSize) { + binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); + } + return btoa(binary); + }; + const readBlobAsBase64 = (blob) => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result; + resolve(result.indexOf(',') >= 0 ? result.split(',')[1] : result); + }; + reader.onerror = reject; + reader.readAsDataURL(blob); + }); + const readDataViewAsBase64 = (dataView) => readArrayBufferAsBase64(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)); + const base64StringToArrayBuffer = (base64) => { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes.buffer; + }; const convertFormData = async (formData) => { const newFormData = []; for (const pair of formData.entries()) { @@ -65,9 +92,38 @@ var nativeBridge = (function (exports) { return newFormData; }; const convertBody = async (body, contentType) => { - if (body instanceof ReadableStream || body instanceof Uint8Array) { + if (body instanceof File) { + const fileData = await readFileAsBase64(body); + return { + data: fileData, + type: 'file', + headers: { 'Content-Type': body.type }, + }; + } + else if (body instanceof Blob) { + return { + data: await readBlobAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || body.type || 'application/octet-stream' }, + }; + } + else if (body instanceof ArrayBuffer) { + return { + data: readArrayBufferAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + else if (ArrayBuffer.isView(body)) { + return { + data: readDataViewAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + if ((typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) || body instanceof Uint8Array) { let encodedData; - if (body instanceof ReadableStream) { + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) { const reader = body.getReader(); const chunks = []; while (true) { @@ -87,7 +143,9 @@ var nativeBridge = (function (exports) { else { encodedData = body; } - let data = new TextDecoder().decode(encodedData); + let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) || contentType === 'application/octet-stream' + ? readDataViewAsBase64(encodedData) + : new TextDecoder().decode(encodedData); let type; if (contentType === 'application/json') { try { @@ -102,7 +160,7 @@ var nativeBridge = (function (exports) { type = 'formData'; } else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) { - type = 'image'; + type = 'binary'; } else if (contentType === 'application/octet-stream') { type = 'binary'; @@ -128,14 +186,6 @@ var nativeBridge = (function (exports) { type: 'formData', }; } - else if (body instanceof File) { - const fileData = await readFileAsBase64(body); - return { - data: fileData, - type: 'file', - headers: { 'Content-Type': body.type }, - }; - } return { data: body, type: 'json' }; }; const CAPACITOR_HTTP_INTERCEPTOR = '/_capacitor_http_interceptor_'; @@ -535,15 +585,15 @@ var nativeBridge = (function (exports) { data: requestData, dataType: type, headers: nativeHeaders, + responseType: 'arraybuffer', }); const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type']; - let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json')) - ? JSON.stringify(nativeResponse.data) - : nativeResponse.data; // use null data for 204 No Content HTTP response - if (nativeResponse.status === 204) { - data = null; - } + const data = nativeResponse.status === 204 + ? null + : (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json')) + ? JSON.stringify(nativeResponse.data) + : base64StringToArrayBuffer(nativeResponse.data); // intercept & parse response before returning const response = new Response(data, { headers: nativeResponse.headers, diff --git a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java index 6180d6420..c2f966ae2 100644 --- a/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java +++ b/android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java @@ -1,6 +1,5 @@ package com.getcapacitor.plugin.util; -import android.os.Build; import android.os.LocaleList; import android.text.TextUtils; import com.getcapacitor.Bridge; @@ -19,7 +18,6 @@ import java.net.URLEncoder; import java.net.UnknownServiceException; import java.nio.charset.StandardCharsets; -import java.util.Base64; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -209,13 +207,8 @@ public void setRequestBody(PluginCall call, JSValue body, String bodyType) throw dataString = call.getString("data"); } this.writeRequestBody(dataString != null ? dataString : ""); - } else if (bodyType != null && bodyType.equals("file")) { - try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - os.write(Base64.getDecoder().decode(body.toString())); - } - os.flush(); - } + } else if (bodyType != null && (bodyType.equals("file") || bodyType.equals("binary"))) { + this.writeRequestBody(decodeBase64RequestBody(body.toString())); } else if (contentType.contains("application/x-www-form-urlencoded")) { try { JSObject obj = body.toJSObject(); @@ -252,6 +245,17 @@ private void writeRequestBody(String body) throws IOException { } } + private void writeRequestBody(byte[] body) throws IOException { + try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { + os.write(body); + os.flush(); + } + } + + static byte[] decodeBase64RequestBody(String body) { + return android.util.Base64.decode(body, android.util.Base64.DEFAULT); + } + private void writeObjectRequestBody(JSObject object) throws IOException, JSONException { try (DataOutputStream os = new DataOutputStream(connection.getOutputStream())) { Iterator keys = object.keys(); @@ -296,11 +300,7 @@ private void writeFormDataRequestBody(String boundary, JSArray entries) throws I os.writeBytes("Content-Transfer-Encoding: binary" + lineEnd); os.writeBytes(lineEnd); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - os.write(Base64.getDecoder().decode(value)); - } else { - os.write(android.util.Base64.decode(value, android.util.Base64.DEFAULT)); - } + os.write(decodeBase64RequestBody(value)); os.writeBytes(lineEnd); } diff --git a/core/http.md b/core/http.md index f459597c1..0e0030594 100644 --- a/core/http.md +++ b/core/http.md @@ -233,7 +233,7 @@ Make a Http DELETE Request to a server using native libraries. | **`webFetchExtra`** | RequestInit | Extra arguments for fetch when running on the web | | **`responseType`** | HttpResponseType | This is used to parse the response appropriately before returning it to the requestee. If the response content-type is "json", this value is ignored. | | **`shouldEncodeUrlParams`** | boolean | Use this option if you need to keep the URL unencoded in certain cases (already encoded, azure/firebase testing, etc.). The default is _true_. | -| **`dataType`** | 'file' \| 'formData' | This is used if we've had to convert the data from a JS type that needs special handling in the native layer | +| **`dataType`** | 'file' \| 'formData' \| 'binary' | This is used if we've had to convert the data from a JS type that needs special handling in the native layer | #### HttpParams diff --git a/core/native-bridge.ts b/core/native-bridge.ts index 4fc028f37..f9916625d 100644 --- a/core/native-bridge.ts +++ b/core/native-bridge.ts @@ -30,6 +30,44 @@ const readFileAsBase64 = (file: File): Promise => reader.readAsBinaryString(file); }); +const readArrayBufferAsBase64 = (arrayBuffer: ArrayBufferLike): string => { + const bytes = new Uint8Array(arrayBuffer); + const chunkSize = 0x8000; + let binary = ''; + + for (let i = 0; i < bytes.length; i += chunkSize) { + binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); + } + + return btoa(binary); +}; + +const readBlobAsBase64 = (blob: Blob): Promise => + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result as string; + resolve(result.indexOf(',') >= 0 ? result.split(',')[1] : result); + }; + reader.onerror = reject; + + reader.readAsDataURL(blob); + }); + +const readDataViewAsBase64 = (dataView: ArrayBufferView): string => + readArrayBufferAsBase64(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)); + +const base64StringToArrayBuffer = (base64: string): ArrayBuffer => { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + + return bytes.buffer as ArrayBuffer; +}; + const convertFormData = async (formData: FormData): Promise => { const newFormData: CapFormDataEntry[] = []; for (const pair of formData.entries()) { @@ -55,11 +93,38 @@ const convertBody = async ( body: Document | XMLHttpRequestBodyInit | ReadableStream | undefined, contentType?: string, ): Promise => { - if (body instanceof ReadableStream || body instanceof Uint8Array) { - let encodedData; - if (body instanceof ReadableStream) { + if (body instanceof File) { + const fileData = await readFileAsBase64(body); + return { + data: fileData, + type: 'file', + headers: { 'Content-Type': body.type }, + }; + } else if (body instanceof Blob) { + return { + data: await readBlobAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || body.type || 'application/octet-stream' }, + }; + } else if (body instanceof ArrayBuffer) { + return { + data: readArrayBufferAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } else if (ArrayBuffer.isView(body)) { + return { + data: readDataViewAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + + if ((typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) || body instanceof Uint8Array) { + let encodedData: Uint8Array; + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) { const reader = body.getReader(); - const chunks: any[] = []; + const chunks: Uint8Array[] = []; while (true) { const { done, value } = await reader.read(); if (done) break; @@ -73,10 +138,13 @@ const convertBody = async ( } encodedData = concatenated; } else { - encodedData = body; + encodedData = body as Uint8Array; } - let data = new TextDecoder().decode(encodedData); + let data = + contentType?.startsWith('image') || contentType === 'application/octet-stream' + ? readDataViewAsBase64(encodedData) + : new TextDecoder().decode(encodedData); let type; if (contentType === 'application/json') { try { @@ -88,7 +156,7 @@ const convertBody = async ( } else if (contentType === 'multipart/form-data') { type = 'formData'; } else if (contentType?.startsWith('image')) { - type = 'image'; + type = 'binary'; } else if (contentType === 'application/octet-stream') { type = 'binary'; } else { @@ -110,13 +178,6 @@ const convertBody = async ( data: await convertFormData(body), type: 'formData', }; - } else if (body instanceof File) { - const fileData = await readFileAsBase64(body); - return { - data: fileData, - type: 'file', - headers: { 'Content-Type': body.type }, - }; } return { data: body, type: 'json' }; @@ -577,17 +638,17 @@ const initBridge = (w: any): void => { data: requestData, dataType: type, headers: nativeHeaders, + responseType: 'arraybuffer', }); const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type']; - let data = contentType?.startsWith('application/json') - ? JSON.stringify(nativeResponse.data) - : nativeResponse.data; - // use null data for 204 No Content HTTP response - if (nativeResponse.status === 204) { - data = null; - } + const data = + nativeResponse.status === 204 + ? null + : contentType?.startsWith('application/json') + ? JSON.stringify(nativeResponse.data) + : base64StringToArrayBuffer(nativeResponse.data); // intercept & parse response before returning const response = new Response(data, { diff --git a/core/src/core-plugins.ts b/core/src/core-plugins.ts index a1efe9c90..261474551 100644 --- a/core/src/core-plugins.ts +++ b/core/src/core-plugins.ts @@ -245,7 +245,7 @@ export interface HttpOptions { * This is used if we've had to convert the data from a JS type that needs * special handling in the native layer */ - dataType?: 'file' | 'formData'; + dataType?: 'file' | 'formData' | 'binary'; } export interface HttpParams { diff --git a/core/src/tests/bridge.spec.ts b/core/src/tests/bridge.spec.ts index daf7f0328..99a7fd59a 100644 --- a/core/src/tests/bridge.spec.ts +++ b/core/src/tests/bridge.spec.ts @@ -9,8 +9,16 @@ import { createCapacitor } from '../runtime'; describe('bridge', () => { let win: WindowCapacitor; let cap: CapacitorInstance; + let originalFetch: typeof window.fetch; + let originalHeaders: typeof Headers; + let originalRequest: typeof Request; + let originalResponse: typeof Response; beforeEach(() => { + originalFetch = window.fetch; + originalHeaders = globalThis.Headers; + originalRequest = globalThis.Request; + originalResponse = globalThis.Response; win = {}; initBridge(win); // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -19,6 +27,13 @@ describe('bridge', () => { window.prompt = () => {}; }); + afterEach(() => { + window.fetch = originalFetch; + globalThis.Headers = originalHeaders; + globalThis.Request = originalRequest; + globalThis.Response = originalResponse; + }); + it('android nativePromise error', (done) => { mockAndroidPluginResult({ success: false, @@ -115,6 +130,121 @@ describe('bridge', () => { }); }); + it('android patched fetch sends Blob bodies as base64 binary data', async () => { + mockFetchApi(); + let callData: any; + (win as any).androidBridge = { + postMessage: (m: string) => { + callData = JSON.parse(m); + Promise.resolve().then(() => { + cap.fromNative!({ + callbackId: callData.callbackId, + methodName: callData.methodName, + pluginId: callData.pluginId, + success: true, + data: { + data: 'ok', + headers: { 'Content-Type': 'text/plain' }, + status: 200, + url: 'https://example.com/upload', + }, + }); + }); + }, + }; + (win as any).CapacitorHttpAndroidInterface = { + isEnabled: () => true, + }; + initBridge(win); + cap = createCapacitor(win); + + await window.fetch('https://example.com/upload', { + body: new Blob([new Uint8Array([1, 2])], { type: 'application/octet-stream' }), + headers: { 'Content-Type': 'application/octet-stream' }, + method: 'POST', + }); + + expect(callData.options.data).toBe('AQI='); + expect(callData.options.dataType).toBe('binary'); + expect(callData.options.headers['content-type']).toBe('application/octet-stream'); + }); + + it('android patched fetch sends ArrayBuffer bodies as base64 binary data', async () => { + mockFetchApi(); + let callData: any; + (win as any).androidBridge = { + postMessage: (m: string) => { + callData = JSON.parse(m); + Promise.resolve().then(() => { + cap.fromNative!({ + callbackId: callData.callbackId, + methodName: callData.methodName, + pluginId: callData.pluginId, + success: true, + data: { + data: 'ok', + headers: { 'Content-Type': 'text/plain' }, + status: 200, + url: 'https://example.com/upload', + }, + }); + }); + }, + }; + (win as any).CapacitorHttpAndroidInterface = { + isEnabled: () => true, + }; + initBridge(win); + cap = createCapacitor(win); + + await window.fetch('https://example.com/upload', { + body: new Uint8Array([3, 4]).buffer, + headers: { 'Content-Type': 'application/octet-stream' }, + method: 'POST', + }); + + expect(callData.options.data).toBe('AwQ='); + expect(callData.options.dataType).toBe('binary'); + expect(callData.options.headers['content-type']).toBe('application/octet-stream'); + }); + + it('android patched fetch returns binary responses byte-for-byte', async () => { + mockFetchApi(); + let callData: any; + (win as any).androidBridge = { + postMessage: (m: string) => { + callData = JSON.parse(m); + Promise.resolve().then(() => { + cap.fromNative!({ + callbackId: callData.callbackId, + methodName: callData.methodName, + pluginId: callData.pluginId, + success: true, + data: { + data: 'AQI=', + headers: { 'Content-Type': 'application/octet-stream' }, + status: 200, + url: 'https://example.com/upload', + }, + }); + }); + }, + }; + (win as any).CapacitorHttpAndroidInterface = { + isEnabled: () => true, + }; + initBridge(win); + cap = createCapacitor(win); + + const response = await window.fetch('https://example.com/upload', { + body: 'body', + method: 'POST', + }); + + expect(callData.options.responseType).toBe('arraybuffer'); + expect(Array.from(new Uint8Array(await response.arrayBuffer()))).toEqual([1, 2]); + }); + const mockAndroidPluginResult = (pluginResult: PluginResult) => { win.androidBridge = { postMessage: (m) => { @@ -122,7 +252,7 @@ describe('bridge', () => { Promise.resolve().then(() => { pluginResult.callbackId = d.callbackId; pluginResult.methodName = d.methodName; - cap.fromNative(pluginResult); + cap.fromNative!(pluginResult); }); }, }; @@ -136,11 +266,88 @@ describe('bridge', () => { Promise.resolve().then(() => { pluginResult.callbackId = m.callbackId; pluginResult.methodName = m.methodName; - cap.fromNative(pluginResult); + cap.fromNative!(pluginResult); }); }, }, }, }; }; + + const mockFetchApi = () => { + class MockHeaders { + private readonly headers: Record = {}; + + constructor(init?: HeadersInit) { + if (init == null) { + return; + } + + Object.entries(init as Record).forEach(([key, value]) => { + this.headers[key.toLocaleLowerCase()] = value; + }); + } + + delete(key: string) { + delete this.headers[key.toLocaleLowerCase()]; + } + + entries() { + return Object.entries(this.headers)[Symbol.iterator](); + } + + get(key: string) { + return this.headers[key.toLocaleLowerCase()] ?? null; + } + + has(key: string) { + return this.headers[key.toLocaleLowerCase()] != null; + } + + set(key: string, value: string) { + this.headers[key.toLocaleLowerCase()] = value; + } + } + + class MockRequest { + readonly body?: BodyInit | null; + readonly headers: Headers; + readonly method: string; + readonly url: string; + + constructor(resource: RequestInfo | URL, init?: RequestInit) { + this.body = init?.body; + this.headers = new globalThis.Headers(init?.headers); + this.method = init?.method ?? 'GET'; + this.url = resource.toString(); + } + } + + class MockResponse { + private readonly bytes: Uint8Array; + + constructor( + body?: BodyInit | null, + readonly init?: ResponseInit, + ) { + if (body instanceof ArrayBuffer) { + this.bytes = new Uint8Array(body); + } else if (ArrayBuffer.isView(body)) { + this.bytes = new Uint8Array(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength)); + } else if (typeof body === 'string') { + this.bytes = new TextEncoder().encode(body); + } else { + this.bytes = new Uint8Array(); + } + } + + async arrayBuffer() { + return this.bytes.buffer.slice(this.bytes.byteOffset, this.bytes.byteOffset + this.bytes.byteLength); + } + } + + globalThis.Headers = MockHeaders as any; + globalThis.Request = MockRequest as any; + globalThis.Response = MockResponse as any; + }; }); diff --git a/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift b/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift index 344333688..8308c3d73 100644 --- a/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift +++ b/ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift @@ -172,7 +172,7 @@ open class CapacitorUrlRequest: NSObject, URLSessionTaskDelegate { } public func getRequestData(_ body: JSValue, _ contentType: String, _ dataType: String? = nil) throws -> Data? { - if dataType == "file" { + if dataType == "file" || dataType == "binary" { guard let stringData = body as? String else { throw CapacitorUrlRequestError.serializationError("[ data ] argument could not be parsed as string") } diff --git a/ios/Capacitor/Capacitor/assets/native-bridge.js b/ios/Capacitor/Capacitor/assets/native-bridge.js index f5e7cc440..1458690a7 100644 --- a/ios/Capacitor/Capacitor/assets/native-bridge.js +++ b/ios/Capacitor/Capacitor/assets/native-bridge.js @@ -44,6 +44,33 @@ var nativeBridge = (function (exports) { reader.onerror = reject; reader.readAsBinaryString(file); }); + const readArrayBufferAsBase64 = (arrayBuffer) => { + const bytes = new Uint8Array(arrayBuffer); + const chunkSize = 0x8000; + let binary = ''; + for (let i = 0; i < bytes.length; i += chunkSize) { + binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); + } + return btoa(binary); + }; + const readBlobAsBase64 = (blob) => new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result; + resolve(result.indexOf(',') >= 0 ? result.split(',')[1] : result); + }; + reader.onerror = reject; + reader.readAsDataURL(blob); + }); + const readDataViewAsBase64 = (dataView) => readArrayBufferAsBase64(dataView.buffer.slice(dataView.byteOffset, dataView.byteOffset + dataView.byteLength)); + const base64StringToArrayBuffer = (base64) => { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes.buffer; + }; const convertFormData = async (formData) => { const newFormData = []; for (const pair of formData.entries()) { @@ -65,9 +92,38 @@ var nativeBridge = (function (exports) { return newFormData; }; const convertBody = async (body, contentType) => { - if (body instanceof ReadableStream || body instanceof Uint8Array) { + if (body instanceof File) { + const fileData = await readFileAsBase64(body); + return { + data: fileData, + type: 'file', + headers: { 'Content-Type': body.type }, + }; + } + else if (body instanceof Blob) { + return { + data: await readBlobAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || body.type || 'application/octet-stream' }, + }; + } + else if (body instanceof ArrayBuffer) { + return { + data: readArrayBufferAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + else if (ArrayBuffer.isView(body)) { + return { + data: readDataViewAsBase64(body), + type: 'binary', + headers: { 'Content-Type': contentType || 'application/octet-stream' }, + }; + } + if ((typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) || body instanceof Uint8Array) { let encodedData; - if (body instanceof ReadableStream) { + if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) { const reader = body.getReader(); const chunks = []; while (true) { @@ -87,7 +143,9 @@ var nativeBridge = (function (exports) { else { encodedData = body; } - let data = new TextDecoder().decode(encodedData); + let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) || contentType === 'application/octet-stream' + ? readDataViewAsBase64(encodedData) + : new TextDecoder().decode(encodedData); let type; if (contentType === 'application/json') { try { @@ -102,7 +160,7 @@ var nativeBridge = (function (exports) { type = 'formData'; } else if (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('image')) { - type = 'image'; + type = 'binary'; } else if (contentType === 'application/octet-stream') { type = 'binary'; @@ -128,14 +186,6 @@ var nativeBridge = (function (exports) { type: 'formData', }; } - else if (body instanceof File) { - const fileData = await readFileAsBase64(body); - return { - data: fileData, - type: 'file', - headers: { 'Content-Type': body.type }, - }; - } return { data: body, type: 'json' }; }; const CAPACITOR_HTTP_INTERCEPTOR = '/_capacitor_http_interceptor_'; @@ -535,15 +585,15 @@ var nativeBridge = (function (exports) { data: requestData, dataType: type, headers: nativeHeaders, + responseType: 'arraybuffer', }); const contentType = nativeResponse.headers['Content-Type'] || nativeResponse.headers['content-type']; - let data = (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json')) - ? JSON.stringify(nativeResponse.data) - : nativeResponse.data; // use null data for 204 No Content HTTP response - if (nativeResponse.status === 204) { - data = null; - } + const data = nativeResponse.status === 204 + ? null + : (contentType === null || contentType === void 0 ? void 0 : contentType.startsWith('application/json')) + ? JSON.stringify(nativeResponse.data) + : base64StringToArrayBuffer(nativeResponse.data); // intercept & parse response before returning const response = new Response(data, { headers: nativeResponse.headers,