codeplay-common 4.9.2 → 4.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Version 6.5: recreate failed WebViews, defer background recovery, and suppress foreground recovery toasts.
|
|
1
2
|
const fs = require('fs');
|
|
2
3
|
const path = require('path');
|
|
3
4
|
const plist = require('plist');
|
|
@@ -1191,6 +1192,8 @@ if (!fs.existsSync(bridgeWebViewClientFilePath)) {
|
|
|
1191
1192
|
}
|
|
1192
1193
|
|
|
1193
1194
|
let fileContent = fs.readFileSync(bridgeWebViewClientFilePath, 'utf8');
|
|
1195
|
+
const bridgeWebViewLineEnding = fileContent.includes('\r\n') ? '\r\n' : '\n';
|
|
1196
|
+
fileContent = fileContent.replace(/\r\n/g, '\n');
|
|
1194
1197
|
|
|
1195
1198
|
// Define old and new code
|
|
1196
1199
|
const oldCodeStart = `@Override
|
|
@@ -1208,7 +1211,8 @@ const oldCodeStart = `@Override
|
|
|
1208
1211
|
return result;
|
|
1209
1212
|
}`;
|
|
1210
1213
|
|
|
1211
|
-
|
|
1214
|
+
// Match the previous patch so existing projects migrate without reinstalling Capacitor.
|
|
1215
|
+
const legacyReloadCode = `@Override
|
|
1212
1216
|
public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
|
|
1213
1217
|
super.onRenderProcessGone(view, detail);
|
|
1214
1218
|
|
|
@@ -1244,15 +1248,104 @@ const newCode = `@Override
|
|
|
1244
1248
|
return result;
|
|
1245
1249
|
}`;
|
|
1246
1250
|
|
|
1247
|
-
|
|
1251
|
+
const newCode = `// Codeplay WebView renderer recovery (before-build 6.5).
|
|
1252
|
+
private boolean codeplayRendererRecoveryPending = false;
|
|
1253
|
+
private static long codeplayRendererRecoveryWindowStart = -1L;
|
|
1254
|
+
private static int codeplayRendererRecoveryCount = 0;
|
|
1255
|
+
|
|
1256
|
+
@Override
|
|
1257
|
+
public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
|
|
1258
|
+
if (codeplayRendererRecoveryPending) return true;
|
|
1259
|
+
|
|
1260
|
+
boolean handled = false;
|
|
1261
|
+
List<WebViewListener> webViewListeners = bridge.getWebViewListeners();
|
|
1262
|
+
if (webViewListeners != null) {
|
|
1263
|
+
for (WebViewListener listener : webViewListeners) {
|
|
1264
|
+
handled = listener.onRenderProcessGone(view, detail) || handled;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
if (handled) return true;
|
|
1268
|
+
|
|
1269
|
+
codeplayRendererRecoveryPending = true;
|
|
1270
|
+
final androidx.appcompat.app.AppCompatActivity activity = bridge.getActivity();
|
|
1271
|
+
final boolean wasInForeground = activity != null && activity.getLifecycle()
|
|
1272
|
+
.getCurrentState().isAtLeast(androidx.lifecycle.Lifecycle.State.STARTED);
|
|
1273
|
+
|
|
1274
|
+
// A terminated renderer's WebView must never be loaded or reloaded again.
|
|
1275
|
+
if (view.getParent() instanceof android.view.ViewGroup) {
|
|
1276
|
+
((android.view.ViewGroup) view.getParent()).removeView(view);
|
|
1277
|
+
}
|
|
1278
|
+
view.destroy();
|
|
1279
|
+
|
|
1280
|
+
if (activity == null || activity.isFinishing() || activity.isDestroyed()) return true;
|
|
1281
|
+
|
|
1282
|
+
final android.os.Handler mainHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
|
1283
|
+
final Runnable recover = () -> {
|
|
1284
|
+
if (activity.isFinishing() || activity.isDestroyed()) return;
|
|
1285
|
+
|
|
1286
|
+
// Static counters survive Activity recreation and prevent an automatic restart loop.
|
|
1287
|
+
long now = android.os.SystemClock.elapsedRealtime();
|
|
1288
|
+
if (codeplayRendererRecoveryWindowStart < 0L ||
|
|
1289
|
+
now - codeplayRendererRecoveryWindowStart >= 60000L) {
|
|
1290
|
+
codeplayRendererRecoveryWindowStart = now;
|
|
1291
|
+
codeplayRendererRecoveryCount = 0;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
if (codeplayRendererRecoveryCount >= 3) {
|
|
1295
|
+
new android.app.AlertDialog.Builder(activity)
|
|
1296
|
+
.setTitle("Unable to restore the app")
|
|
1297
|
+
.setMessage("The app could not recover automatically. Please try again.")
|
|
1298
|
+
.setCancelable(false)
|
|
1299
|
+
.setPositiveButton("Try again", (dialog, which) -> {
|
|
1300
|
+
codeplayRendererRecoveryWindowStart = -1L;
|
|
1301
|
+
codeplayRendererRecoveryCount = 0;
|
|
1302
|
+
if (!activity.isFinishing() && !activity.isDestroyed()) activity.recreate();
|
|
1303
|
+
})
|
|
1304
|
+
.setNegativeButton("Close", (dialog, which) -> activity.finish())
|
|
1305
|
+
.show();
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
codeplayRendererRecoveryCount++;
|
|
1310
|
+
if (!wasInForeground) {
|
|
1311
|
+
Toast.makeText(activity.getApplicationContext(), "Restoring your app…", Toast.LENGTH_SHORT).show();
|
|
1312
|
+
}
|
|
1313
|
+
activity.recreate();
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
if (wasInForeground) {
|
|
1317
|
+
mainHandler.post(recover);
|
|
1318
|
+
} else {
|
|
1319
|
+
// Do not allocate another renderer while the app is hidden and memory may still be low.
|
|
1320
|
+
activity.getLifecycle().addObserver(new androidx.lifecycle.LifecycleEventObserver() {
|
|
1321
|
+
@Override
|
|
1322
|
+
public void onStateChanged(
|
|
1323
|
+
androidx.lifecycle.LifecycleOwner source,
|
|
1324
|
+
androidx.lifecycle.Lifecycle.Event event
|
|
1325
|
+
) {
|
|
1326
|
+
if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) {
|
|
1327
|
+
source.getLifecycle().removeObserver(this);
|
|
1328
|
+
mainHandler.post(recover);
|
|
1329
|
+
} else if (event == androidx.lifecycle.Lifecycle.Event.ON_DESTROY) {
|
|
1330
|
+
source.getLifecycle().removeObserver(this);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
return true;
|
|
1336
|
+
}`;
|
|
1337
|
+
|
|
1338
|
+
// Step 1: Update the stock method or migrate the previous reload-based patch.
|
|
1248
1339
|
let updated = false;
|
|
1249
1340
|
|
|
1250
|
-
if (fileContent.includes(
|
|
1251
|
-
console.log('
|
|
1341
|
+
if (fileContent.includes(newCode)) {
|
|
1342
|
+
console.log('ℹ️ Method already updated. No changes needed in "BridgeWebViewClient.java".');
|
|
1343
|
+
} else if (fileContent.includes(legacyReloadCode)) {
|
|
1344
|
+
fileContent = fileContent.replace(legacyReloadCode, newCode);
|
|
1345
|
+
updated = true;
|
|
1346
|
+
} else if (fileContent.includes(oldCodeStart)) {
|
|
1252
1347
|
fileContent = fileContent.replace(oldCodeStart, newCode);
|
|
1253
1348
|
updated = true;
|
|
1254
|
-
} else if (fileContent.includes(newCode)) {
|
|
1255
|
-
console.log('ℹ️ Method already updated. No changes needed in "BridgeWebViewClient.java".');
|
|
1256
1349
|
} else {
|
|
1257
1350
|
console.error('❌ Error: Neither old nor new code found. Unexpected content.');
|
|
1258
1351
|
process.exit(1);
|
|
@@ -1280,7 +1373,7 @@ if (!fileContent.includes(importToast)) {
|
|
|
1280
1373
|
|
|
1281
1374
|
// Step 3: Save if updated
|
|
1282
1375
|
if (updated) {
|
|
1283
|
-
fs.writeFileSync(bridgeWebViewClientFilePath, fileContent, 'utf8');
|
|
1376
|
+
fs.writeFileSync(bridgeWebViewClientFilePath, fileContent.replace(/\n/g, bridgeWebViewLineEnding), 'utf8');
|
|
1284
1377
|
console.log('✅ File updated successfully.');
|
|
1285
1378
|
} else {
|
|
1286
1379
|
console.log('ℹ️ No changes needed.');
|
|
@@ -4258,6 +4351,103 @@ function patchFile() {
|
|
|
4258
4351
|
|
|
4259
4352
|
//################################## SystemBars.java update for "@capacitor/android": "^8.3.0" plugin END ###############################
|
|
4260
4353
|
|
|
4354
|
+
// Android build optimization START
|
|
4355
|
+
function findAndroidBuildBlock(content, name) {
|
|
4356
|
+
// Mask comments and strings without changing offsets or line endings.
|
|
4357
|
+
const masked = content.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g,
|
|
4358
|
+
value => value.replace(/[^\r\n]/g, ' '));
|
|
4359
|
+
const header = new RegExp(`\\b${name}\\s*\\{`, 'g');
|
|
4360
|
+
let depth = 0;
|
|
4361
|
+
for (let index = 0; index < masked.length; index += 1) {
|
|
4362
|
+
if (depth === 0) {
|
|
4363
|
+
header.lastIndex = index;
|
|
4364
|
+
const match = header.exec(masked);
|
|
4365
|
+
if (match && match.index === index) {
|
|
4366
|
+
const start = header.lastIndex;
|
|
4367
|
+
let blockDepth = 1;
|
|
4368
|
+
for (let end = start; end < masked.length; end += 1) {
|
|
4369
|
+
if (masked[end] === '{') blockDepth += 1;
|
|
4370
|
+
if (masked[end] === '}' && --blockDepth === 0) return { start, end };
|
|
4371
|
+
}
|
|
4372
|
+
throw new Error(`Unclosed Android Gradle ${name} block.`);
|
|
4373
|
+
}
|
|
4374
|
+
}
|
|
4375
|
+
if (masked[index] === '{') depth += 1;
|
|
4376
|
+
if (masked[index] === '}') depth -= 1;
|
|
4377
|
+
}
|
|
4378
|
+
return null;
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
function syncAndroidBuildOptimization() {
|
|
4382
|
+
if (buildPlatform !== 'android' || !fs.existsSync(androidAppBuildGradlePath)) return;
|
|
4383
|
+
|
|
4384
|
+
const original = fs.readFileSync(androidAppBuildGradlePath, 'utf8');
|
|
4385
|
+
const newline = original.includes('\r\n') ? '\r\n' : '\n';
|
|
4386
|
+
const androidBlock = findAndroidBuildBlock(original, 'android');
|
|
4387
|
+
if (!androidBlock) throw new Error('Android Gradle android block was not found.');
|
|
4388
|
+
let androidContent = original.slice(androidBlock.start, androidBlock.end);
|
|
4389
|
+
const buildTypesBlock = findAndroidBuildBlock(androidContent, 'buildTypes');
|
|
4390
|
+
if (!buildTypesBlock) throw new Error('Android Gradle buildTypes block was not found.');
|
|
4391
|
+
let buildTypesContent = androidContent.slice(buildTypesBlock.start, buildTypesBlock.end);
|
|
4392
|
+
|
|
4393
|
+
// Match the existing finalrelease compatibility exception for this native plugin.
|
|
4394
|
+
const ffmpegPluginXml = path.join(process.cwd(), 'node_modules', 'codeplay-fmpg', 'plugin.xml');
|
|
4395
|
+
const optimizeRelease = !(fs.existsSync(ffmpegPluginXml)
|
|
4396
|
+
&& /<plugin\b/.test(fs.readFileSync(ffmpegPluginXml, 'utf8')));
|
|
4397
|
+
|
|
4398
|
+
for (const name of ['debug', 'release']) {
|
|
4399
|
+
const enabled = name === 'debug' || optimizeRelease;
|
|
4400
|
+
const block = findAndroidBuildBlock(buildTypesContent, name);
|
|
4401
|
+
const blockComments = buildTypesContent.match(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g) || [];
|
|
4402
|
+
if (!block && blockComments.some(comment => (comment.startsWith('//') || comment.startsWith('/*'))
|
|
4403
|
+
&& new RegExp(`\\b${name}\\s*\\{`).test(comment))) continue;
|
|
4404
|
+
let body = block ? buildTypesContent.slice(block.start, block.end) : `${newline} `;
|
|
4405
|
+
const comments = (body.match(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g) || [])
|
|
4406
|
+
.filter(value => value.startsWith('//') || value.startsWith('/*'));
|
|
4407
|
+
for (const setting of ['minifyEnabled', 'shrinkResources']) {
|
|
4408
|
+
const masked = body.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g,
|
|
4409
|
+
value => value.replace(/[^\r\n]/g, ' '));
|
|
4410
|
+
const settingPattern = new RegExp(`\\b${setting}\\b`);
|
|
4411
|
+
// Existing values, expressions and commented opt-outs take precedence.
|
|
4412
|
+
if (settingPattern.test(masked) || comments.some(comment => settingPattern.test(comment))) continue;
|
|
4413
|
+
const value = setting === 'shrinkResources'
|
|
4414
|
+
? enabled && /\bminifyEnabled\s*(?:=\s*)?true\b/.test(masked)
|
|
4415
|
+
: enabled;
|
|
4416
|
+
body = `${newline} ${setting} ${value}${body}`;
|
|
4417
|
+
}
|
|
4418
|
+
const activeBody = body.replace(/\/\*[\s\S]*?\*\/|\/\/[^\r\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g,
|
|
4419
|
+
value => value.startsWith('//') || value.startsWith('/*') ? value.replace(/[^\r\n]/g, ' ') : value);
|
|
4420
|
+
const defaultRules = [...activeBody.matchAll(/getDefaultProguardFile\(\s*(['"])proguard-android\.txt\1\s*\)/g)];
|
|
4421
|
+
for (const match of defaultRules.reverse()) {
|
|
4422
|
+
body = body.slice(0, match.index) + "getDefaultProguardFile('proguard-android-optimize.txt')"
|
|
4423
|
+
+ body.slice(match.index + match[0].length);
|
|
4424
|
+
}
|
|
4425
|
+
if (!/\bproguardFiles\b/.test(activeBody)
|
|
4426
|
+
&& !comments.some(comment => /\bproguardFiles\b/.test(comment))) {
|
|
4427
|
+
body = `${newline} proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'${body}`;
|
|
4428
|
+
}
|
|
4429
|
+
buildTypesContent = block
|
|
4430
|
+
? buildTypesContent.slice(0, block.start) + body + buildTypesContent.slice(block.end)
|
|
4431
|
+
: `${buildTypesContent.trimEnd()}${newline} ${name} {${body}}${newline} `;
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4434
|
+
androidContent = androidContent.slice(0, buildTypesBlock.start) + buildTypesContent
|
|
4435
|
+
+ androidContent.slice(buildTypesBlock.end);
|
|
4436
|
+
const next = original.slice(0, androidBlock.start) + androidContent + original.slice(androidBlock.end);
|
|
4437
|
+
if (next !== original) fs.writeFileSync(androidAppBuildGradlePath, next, 'utf8');
|
|
4438
|
+
|
|
4439
|
+
const propertiesPath = path.join(androidPlatformPath, 'gradle.properties');
|
|
4440
|
+
const properties = fs.existsSync(propertiesPath) ? fs.readFileSync(propertiesPath, 'utf8') : '';
|
|
4441
|
+
const property = 'android.r8.optimizedResourceShrinking=true';
|
|
4442
|
+
const propertyPattern = /^[ \t]*[#!]?[ \t]*android\.r8\.optimizedResourceShrinking(?:[ \t]*[=:]|[ \t]+|[ \t]*$)/m;
|
|
4443
|
+
const nextProperties = propertyPattern.test(properties)
|
|
4444
|
+
? properties
|
|
4445
|
+
: `${properties.trimEnd()}${newline}${property}${newline}`;
|
|
4446
|
+
if (nextProperties !== properties) fs.writeFileSync(propertiesPath, nextProperties, 'utf8');
|
|
4447
|
+
console.log('✅ Android build optimization defaults checked; existing settings and commented opt-outs preserved.');
|
|
4448
|
+
}
|
|
4449
|
+
// Android build optimization END
|
|
4450
|
+
|
|
4261
4451
|
async function main() {
|
|
4262
4452
|
validateThemeFolderLocation();
|
|
4263
4453
|
validateAndRestoreSignDetails();
|
|
@@ -4276,6 +4466,7 @@ async function main() {
|
|
|
4276
4466
|
checkAndupdateDropInViteConfig();
|
|
4277
4467
|
checkAdmobConfigurationProperty();
|
|
4278
4468
|
syncAndroidAdIdPermission();
|
|
4469
|
+
syncAndroidBuildOptimization();
|
|
4279
4470
|
}
|
|
4280
4471
|
|
|
4281
4472
|
await main();
|
|
@@ -4291,6 +4482,8 @@ await main();
|
|
|
4291
4482
|
Release Notes
|
|
4292
4483
|
|
|
4293
4484
|
6.5
|
|
4485
|
+
Enable debug/release optimization and optimized resource shrinking before Android builds.
|
|
4486
|
+
Preserve existing values and commented opt-outs without adding duplicate settings.
|
|
4294
4487
|
Kids ad configuration disables iOS ATT prompts and excludes Android AD_ID during builds.
|
|
4295
4488
|
Non-kids apps retain tracking prompts and normal permission behavior.
|
|
4296
4489
|
Ad configuration migration preserves newer local configuration versions.
|