Skip to content
Open
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
25 changes: 24 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,31 @@
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/Cap-go/capacitor-plus/compare/8.3.3...8.3.4) (2026-05-07)
## [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

- **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

- **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)
Comment on lines +6 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Do not manually edit the generated root changelog.

These changes should be produced by the CI/CD release process rather than committed as hand-authored edits. Revert the direct CHANGELOG.md changes and regenerate them through the prescribed pipeline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 6 - 30, Revert the hand-authored changes to the
generated root CHANGELOG.md, including the added 8.3.4 and 8.3.3 entries, and
leave changelog updates to the prescribed CI/CD release process.

Source: Path instructions


### Bug Fixes

Expand Down
254 changes: 6 additions & 248 deletions android/CHANGELOG.md

Large diffs are not rendered by default.

86 changes: 68 additions & 18 deletions android/capacitor/src/main/assets/native-bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -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) {
Expand All @@ -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 {
Expand All @@ -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';
Expand All @@ -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_';
Expand Down Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions android/capacitor/src/main/java/com/getcapacitor/PluginConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment on lines +167 to +178

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve insetsHandling: "disable" behavior.

The unconditional injections bypass insetHandlingEnabled, which is set to false when insetsHandling is "disable" at Lines 97-100. Disabled mode will therefore still inject or update --safe-area-inset-* values. Restore the flag check consistently across all three paths.

Also applies to: 194-195, 227-228

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java`
around lines 167 - 178, The safe-area CSS injections in SystemBars must respect
insetHandlingEnabled. Update the injection paths around
calcSafeAreaInsets/injectSafeAreaCSS, including the additional locations at the
referenced paths, to guard all three calls with the flag so insetsHandling:
"disable" neither injects nor updates safe-area values.

}
}

Expand All @@ -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(
Expand Down Expand Up @@ -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;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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<String> keys = object.keys();
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
8 changes: 4 additions & 4 deletions android/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@capacitor-plus/android",
"name": "@capacitor/android",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Update package-scope consumers with this rename.

Line [2] changes the public package identity to @capacitor/android, but scripts/sync-peer-dependencies.mjs:8-22 still recognizes only @capacitor-plus/*. The Android package will be skipped, and a corresponding core rename can make corePkg undefined during release preparation.

Proposed synchronization fix
-const CORE_DEPENDENTS = ['`@capacitor-plus/android`', '`@capacitor-plus/ios`'];
+const CORE_DEPENDENTS = ['`@capacitor/android`', '`@capacitor/ios`'];

-const corePkg = pkgs.find((p) => p.name === '`@capacitor-plus/core`');
+const corePkg = pkgs.find((p) => p.name === '`@capacitor/core`');

-await setPackageJsonDependencies(p, { '`@capacitor-plus/core`': range }, 'peerDependencies');
+await setPackageJsonDependencies(p, { '`@capacitor/core`': range }, 'peerDependencies');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/package.json` at line 2, Update the package-scope matching and
core-package resolution in sync-peer-dependencies so the renamed
`@capacitor/android` package is recognized alongside the existing package scope.
Ensure release preparation derives a defined corePkg for the new scope and
preserves synchronization for any remaining `@capacitor-plus` packages.

"version": "8.3.4",
"description": "Capacitor+: Enhanced Capacitor with automated upstream sync - Cross-platform apps with JavaScript and the web",
"homepage": "https://capgo.app/docs/plugins/capacitor-plus/",
"author": "Capgo Team <support@capgo.app> (https://capgo.app)",
"description": "Capacitor: Cross-platform apps with JavaScript and the web",
"homepage": "https://capacitorjs.com",
"author": "Ionic Team <hi@ionic.io> (https://ionic.io)",
"license": "MIT",
"repository": {
"type": "git",
Expand Down
Loading
Loading