@saltcorn/mobile-builder 1.6.3-beta.1 → 1.7.0-alpha.1
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.
- package/dist/copy_static_assets.cjs +10 -0
- package/dist/copy_static_assets.cjs.map +1 -0
- package/dist/copy_static_assets.d.cts +2 -0
- package/dist/copy_static_assets.d.cts.map +1 -0
- package/dist/mobile-builder.d.ts +7 -7
- package/dist/mobile-builder.d.ts.map +1 -1
- package/dist/mobile-builder.js +46 -51
- package/dist/mobile-builder.js.map +1 -1
- package/dist/tests/versioncode.test.d.ts +2 -0
- package/dist/tests/versioncode.test.d.ts.map +1 -0
- package/dist/tests/versioncode.test.js +24 -0
- package/dist/tests/versioncode.test.js.map +1 -0
- package/dist/tests/webpack.test.d.ts +2 -0
- package/dist/tests/webpack.test.d.ts.map +1 -0
- package/dist/tests/webpack.test.js +73 -0
- package/dist/tests/webpack.test.js.map +1 -0
- package/dist/utils/capacitor-helper.d.ts.map +1 -1
- package/dist/utils/capacitor-helper.js +7 -2
- package/dist/utils/capacitor-helper.js.map +1 -1
- package/dist/utils/common-build-utils.d.ts +135 -9
- package/dist/utils/common-build-utils.d.ts.map +1 -1
- package/dist/utils/common-build-utils.js +274 -63
- package/dist/utils/common-build-utils.js.map +1 -1
- package/dist/utils/xcode/create_share_extension.rb +98 -0
- package/package.json +12 -26
- package/webpack.config.js +22 -20
|
@@ -20,9 +20,10 @@ exports.prepareSplashIcon = prepareSplashIcon;
|
|
|
20
20
|
exports.prepareExportOptionsPlist = prepareExportOptionsPlist;
|
|
21
21
|
exports.modifyInfoPlist = modifyInfoPlist;
|
|
22
22
|
exports.writeEntitlementsPlist = writeEntitlementsPlist;
|
|
23
|
-
exports.
|
|
23
|
+
exports.injectCodeSignEntitlements = injectCodeSignEntitlements;
|
|
24
24
|
exports.copyShareExtFiles = copyShareExtFiles;
|
|
25
25
|
exports.modifyShareViewController = modifyShareViewController;
|
|
26
|
+
exports.createShareExtensionTarget = createShareExtensionTarget;
|
|
26
27
|
exports.modifyAppDelegate = modifyAppDelegate;
|
|
27
28
|
exports.writePrivacyInfo = writePrivacyInfo;
|
|
28
29
|
exports.copyServerFiles = copyServerFiles;
|
|
@@ -76,6 +77,7 @@ function prepareBuildDir(buildDir, templateDir, pushEnabled, backgroundFetchEnab
|
|
|
76
77
|
"@capacitor/core@7.4.5",
|
|
77
78
|
"@capacitor/assets@3.0.5",
|
|
78
79
|
"@capacitor/filesystem@7.1.6",
|
|
80
|
+
"@capacitor/file-transfer@1.0.12",
|
|
79
81
|
"@capacitor/camera@7.0.3",
|
|
80
82
|
"@capacitor/network@7.0.3",
|
|
81
83
|
"@capacitor-community/sqlite@7.0.3",
|
|
@@ -107,6 +109,11 @@ function prepareBuildDir(buildDir, templateDir, pushEnabled, backgroundFetchEnab
|
|
|
107
109
|
maxBuffer: 1024 * 1024 * 10,
|
|
108
110
|
});
|
|
109
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Write `capacitor.config.ts` into the build directory.
|
|
114
|
+
* @param buildDir directory where the app will be built
|
|
115
|
+
* @param config capacitor build settings
|
|
116
|
+
*/
|
|
110
117
|
function writeCapacitorConfig(buildDir, config) {
|
|
111
118
|
const cfgFile = (0, path_1.join)(buildDir, "capacitor.config.ts");
|
|
112
119
|
const content = `
|
|
@@ -116,9 +123,8 @@ const config: CapacitorConfig = {
|
|
|
116
123
|
appId: '${config.appId ? config.appId : "com.saltcorn.mobile.app"}',
|
|
117
124
|
appName: '${config.appName ? config.appName : "SaltcornMobileApp"}',
|
|
118
125
|
webDir: "www",
|
|
119
|
-
ios
|
|
120
|
-
|
|
121
|
-
},
|
|
126
|
+
// ios.limitsNavigationsToAppBoundDomains was dropped - it caused noisy
|
|
127
|
+
// script-injection warnings and wasn't needed for the cookie fix to work.
|
|
122
128
|
android: {
|
|
123
129
|
buildOptions: {
|
|
124
130
|
${config.keystorePath && config.buildType === "release"
|
|
@@ -130,15 +136,21 @@ const config: CapacitorConfig = {
|
|
|
130
136
|
: ""}
|
|
131
137
|
releaseType: '${config.buildType === "release" ? "AAB" : "APK"}',
|
|
132
138
|
},
|
|
133
|
-
${config.unsecureNetwork ? "allowMixedContent: true," : ""}
|
|
134
139
|
},
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
},`
|
|
140
|
-
: ""}
|
|
140
|
+
server: {
|
|
141
|
+
// App's origin on iOS - must match MOBILE_APP_ORIGINS in server/app.js.
|
|
142
|
+
iosScheme: 'SaltcornMobileApp',
|
|
143
|
+
},
|
|
141
144
|
plugins: {
|
|
145
|
+
// Needed together with WKAppBoundDomains (see modifyInfoPlist) to
|
|
146
|
+
// exempt the remote server's session cookie from iOS's third-party
|
|
147
|
+
// cookie blocking.
|
|
148
|
+
CapacitorHttp: {
|
|
149
|
+
enabled: true,
|
|
150
|
+
},
|
|
151
|
+
CapacitorCookies: {
|
|
152
|
+
enabled: true,
|
|
153
|
+
},
|
|
142
154
|
CapacitorSQLite: {
|
|
143
155
|
iosDatabaseLocation: 'Library/CapacitorDatabase',
|
|
144
156
|
iosIsEncryption: true,
|
|
@@ -165,6 +177,11 @@ const config: CapacitorConfig = {
|
|
|
165
177
|
export default config;`;
|
|
166
178
|
(0, fs_extra_1.writeFileSync)(cfgFile, content);
|
|
167
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Copy the app icon into all required asset slots (icon, splash variants).
|
|
182
|
+
* @param buildDir directory where the app will be built
|
|
183
|
+
* @param appIcon path to the source PNG
|
|
184
|
+
*/
|
|
168
185
|
function prepAppIcon(buildDir, appIcon) {
|
|
169
186
|
for (const icon of [
|
|
170
187
|
"icon-only",
|
|
@@ -178,6 +195,11 @@ function prepAppIcon(buildDir, appIcon) {
|
|
|
178
195
|
});
|
|
179
196
|
}
|
|
180
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Returns the Android permissions required for the build.
|
|
200
|
+
* @param allowFCM include FCM/push notification permissions
|
|
201
|
+
* @returns array of Android permission strings
|
|
202
|
+
*/
|
|
181
203
|
function androidPermissions(allowFCM) {
|
|
182
204
|
const state = (0, state_1.getState)();
|
|
183
205
|
if (!state)
|
|
@@ -199,6 +221,10 @@ function androidPermissions(allowFCM) {
|
|
|
199
221
|
}
|
|
200
222
|
return Array.from(permissions);
|
|
201
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Returns the Android hardware features required for the build.
|
|
226
|
+
* @returns array of Android feature strings
|
|
227
|
+
*/
|
|
202
228
|
function androidFeatures() {
|
|
203
229
|
const state = (0, state_1.getState)();
|
|
204
230
|
if (!state)
|
|
@@ -211,7 +237,14 @@ function androidFeatures() {
|
|
|
211
237
|
}
|
|
212
238
|
return Array.from(features);
|
|
213
239
|
}
|
|
214
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Patch `AndroidManifest.xml` with permissions, features, and optional intents.
|
|
242
|
+
* @param buildDir directory where the app will be built
|
|
243
|
+
* @param allowShareTo add share-to intent filter
|
|
244
|
+
* @param allowFCM add FCM push notification support
|
|
245
|
+
* @param allowAuthIntent add authentication intent
|
|
246
|
+
*/
|
|
247
|
+
async function modifyAndroidManifest(buildDir, allowShareTo, allowFCM, allowAuthIntent) {
|
|
215
248
|
console.log("modifyAndroidManifest");
|
|
216
249
|
try {
|
|
217
250
|
const androidManifest = (0, path_1.join)(buildDir, "android", "app", "src", "main", "AndroidManifest.xml");
|
|
@@ -229,9 +262,6 @@ async function modifyAndroidManifest(buildDir, allowShareTo, allowFCM, allowAuth
|
|
|
229
262
|
"android:fullBackupContent": "false",
|
|
230
263
|
"android:dataExtractionRules": "@xml/data_extraction_rules",
|
|
231
264
|
"android:networkSecurityConfig": "@xml/network_security_config",
|
|
232
|
-
...(allowClearTextTraffic
|
|
233
|
-
? { "android:usesCleartextTraffic": "true" }
|
|
234
|
-
: {}),
|
|
235
265
|
};
|
|
236
266
|
if (allowFCM) {
|
|
237
267
|
parsed.manifest.application[0]["meta-data"] = [
|
|
@@ -302,6 +332,11 @@ async function modifyAndroidManifest(buildDir, allowShareTo, allowFCM, allowAuth
|
|
|
302
332
|
console.log(`Unable to modify the AndroidManifest.xml: ${error.message ? error.message : "Unknown error"}`);
|
|
303
333
|
}
|
|
304
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Returns true if any of the included plugins exposes an authentication method.
|
|
337
|
+
* @param plugins list of included plugin names
|
|
338
|
+
* @returns whether an auth method is available
|
|
339
|
+
*/
|
|
305
340
|
function hasAuthMethod(plugins) {
|
|
306
341
|
const state = (0, state_1.getState)();
|
|
307
342
|
for (const pluginName of plugins) {
|
|
@@ -311,6 +346,10 @@ function hasAuthMethod(plugins) {
|
|
|
311
346
|
}
|
|
312
347
|
return false;
|
|
313
348
|
}
|
|
349
|
+
/**
|
|
350
|
+
* Write the Android data extraction rules XML (excludes app data from backups).
|
|
351
|
+
* @param buildDir directory where the app will be built
|
|
352
|
+
*/
|
|
314
353
|
function writeDataExtractionRules(buildDir) {
|
|
315
354
|
console.log("writeDataExtractionRules");
|
|
316
355
|
const dataExtractionRules = (0, path_1.join)(buildDir, "android", "app", "src", "main", "res", "xml", "data_extraction_rules.xml");
|
|
@@ -331,6 +370,11 @@ function writeDataExtractionRules(buildDir) {
|
|
|
331
370
|
</data-extraction-rules>
|
|
332
371
|
`);
|
|
333
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Copy the pre-populated SQLite database into the platform asset directories.
|
|
375
|
+
* @param buildDir directory where the app will be built
|
|
376
|
+
* @param platforms target platforms (`"android"` / `"ios"`)
|
|
377
|
+
*/
|
|
334
378
|
function copyPrepopulatedDb(buildDir, platforms) {
|
|
335
379
|
console.log("copyPrepopulatedDb", buildDir, platforms);
|
|
336
380
|
if (platforms.includes("android")) {
|
|
@@ -340,6 +384,11 @@ function copyPrepopulatedDb(buildDir, platforms) {
|
|
|
340
384
|
(0, fs_extra_1.copySync)((0, path_1.join)(buildDir, "www", "scdb.sqlite"), (0, path_1.join)(buildDir, "ios", "App", "App", "public", "assets", "databases", "prepopulated.db"));
|
|
341
385
|
}
|
|
342
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Extract the bare domain name from a URL (strips protocol, port, trailing slash).
|
|
389
|
+
* @param url full server URL
|
|
390
|
+
* @returns domain string
|
|
391
|
+
*/
|
|
343
392
|
function extractDomain(url) {
|
|
344
393
|
let domain = url;
|
|
345
394
|
if (domain.startsWith("http://"))
|
|
@@ -352,17 +401,26 @@ function extractDomain(url) {
|
|
|
352
401
|
domain = domain.substring(0, domain.indexOf(":"));
|
|
353
402
|
return domain;
|
|
354
403
|
}
|
|
355
|
-
|
|
404
|
+
/**
|
|
405
|
+
* Write the Android network security config. HTTPS is required, so this is
|
|
406
|
+
* always the restrictive (no cleartext) version.
|
|
407
|
+
* @param buildDir directory where the app will be built
|
|
408
|
+
*/
|
|
409
|
+
function writeNetworkSecurityConfig(buildDir) {
|
|
356
410
|
console.log("writeNetworkSecurityConfig");
|
|
357
411
|
const networkSecurityConfig = (0, path_1.join)(buildDir, "android", "app", "src", "main", "res", "xml", "network_security_config.xml");
|
|
358
|
-
|
|
412
|
+
const content = `<?xml version="1.0" encoding="utf-8"?>
|
|
359
413
|
<network-security-config>
|
|
360
|
-
<
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
</network-security-config>
|
|
364
|
-
`);
|
|
414
|
+
<base-config cleartextTrafficPermitted="false" />
|
|
415
|
+
</network-security-config>`;
|
|
416
|
+
(0, fs_extra_1.writeFileSync)(networkSecurityConfig, content);
|
|
365
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
*
|
|
420
|
+
* Patch `config.xml` with app name, app ID, and version.
|
|
421
|
+
* @param buildDir directory where the app will be built
|
|
422
|
+
* @param config object with optional `appName`, `appId`, and `appVersion` fields
|
|
423
|
+
*/
|
|
366
424
|
async function modifyConfigXml(buildDir, config) {
|
|
367
425
|
try {
|
|
368
426
|
const configXml = (0, path_1.join)(buildDir, "config.xml");
|
|
@@ -425,6 +483,11 @@ async function prepareSplashIcon(buildDir, splashIcon, platforms) {
|
|
|
425
483
|
console.log(`Unable to set the splash icon '${splashIcon}': ${error.message ? error.message : "Unknown error"}`);
|
|
426
484
|
}
|
|
427
485
|
}
|
|
486
|
+
/**
|
|
487
|
+
* Generate the iOS `AppIcon.appiconset` with all required sizes.
|
|
488
|
+
* @param buildDir directory where the app will be built
|
|
489
|
+
* @param appIcon path to the source PNG
|
|
490
|
+
*/
|
|
428
491
|
async function prepareAppIconSet(buildDir, appIcon) {
|
|
429
492
|
console.log("prepareAppIconSet", buildDir, appIcon);
|
|
430
493
|
const dir = (0, path_1.join)(buildDir, "AppIcon.appiconset");
|
|
@@ -463,6 +526,10 @@ async function prepareAppIconSet(buildDir, appIcon) {
|
|
|
463
526
|
console.log(`Unable to generate appicon set for '${appIcon}': ${error.message ? error.message : "Unknown error"}`);
|
|
464
527
|
}
|
|
465
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Write `ExportOptions.plist` used by `xcodebuild -exportArchive`.
|
|
531
|
+
* @param param0 buildDir, appId, and iOS provisioning parameters
|
|
532
|
+
*/
|
|
466
533
|
function prepareExportOptionsPlist({ buildDir, appId, iosParams }) {
|
|
467
534
|
console.log("prepareExportOptionsPlist", buildDir, appId, iosParams);
|
|
468
535
|
const buildShareExtBloock = () => {
|
|
@@ -474,12 +541,13 @@ function prepareExportOptionsPlist({ buildDir, appId, iosParams }) {
|
|
|
474
541
|
<string>${iosParams.shareExtensionProvisioningProfile.guuid}</string>`;
|
|
475
542
|
};
|
|
476
543
|
try {
|
|
544
|
+
const method = iosParams.isAdHoc ? "ad-hoc" : "app-store-connect";
|
|
477
545
|
(0, fs_extra_1.writeFileSync)((0, path_1.join)(buildDir, "ExportOptions.plist"), `<?xml version="1.0" encoding="UTF-8"?>
|
|
478
546
|
<!DOCTYPE plist PUBLIC "~//Apple/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
479
547
|
<plist version="1.0">
|
|
480
548
|
<dict>
|
|
481
549
|
<key>method</key>
|
|
482
|
-
<string
|
|
550
|
+
<string>${method}</string>
|
|
483
551
|
<key>provisioningProfiles</key>
|
|
484
552
|
<dict>
|
|
485
553
|
<key>${appId}</key>
|
|
@@ -494,26 +562,52 @@ function prepareExportOptionsPlist({ buildDir, appId, iosParams }) {
|
|
|
494
562
|
console.log(`Unable to write the ExportOptionsPlist file: ${error.message ? error.message : "Unknown error"}`);
|
|
495
563
|
}
|
|
496
564
|
}
|
|
497
|
-
|
|
565
|
+
/**
|
|
566
|
+
* Patch `Info.plist` with background modes, usage descriptions, and optional URL schemes.
|
|
567
|
+
* @param buildDir directory where the app will be built
|
|
568
|
+
* @param allowShareTo add URL scheme for share-to integration
|
|
569
|
+
* @param backgroundSyncEnabled background sync task is scheduled
|
|
570
|
+
* @param pushSyncEnabled add remote-notification background mode
|
|
571
|
+
* @param backgroundFetchEnabled background.js is included (sync or heartbeat configured)
|
|
572
|
+
* @param serverURL server URL, added to WKAppBoundDomains for the iOS cookie
|
|
573
|
+
* fix - changing servers (e.g. a new ngrok URL) needs a rebuild to apply.
|
|
574
|
+
*/
|
|
575
|
+
function modifyInfoPlist(buildDir, allowShareTo, backgroundSyncEnabled, pushSyncEnabled, backgroundFetchEnabled = false, serverURL) {
|
|
498
576
|
const infoPlist = (0, path_1.join)(buildDir, "ios", "App", "App", "Info.plist");
|
|
499
577
|
const content = (0, fs_extra_1.readFileSync)(infoPlist, "utf8");
|
|
578
|
+
let appBoundHostname;
|
|
579
|
+
if (serverURL) {
|
|
580
|
+
try {
|
|
581
|
+
appBoundHostname = new URL(serverURL).hostname;
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
// invalid/missing URL - skip WKAppBoundDomains rather than write a broken plist
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const backgroundModes = [
|
|
588
|
+
...(backgroundFetchEnabled ? ["fetch", "processing"] : []),
|
|
589
|
+
...(pushSyncEnabled ? ["remote-notification"] : []),
|
|
590
|
+
];
|
|
591
|
+
const taskIds = [
|
|
592
|
+
"com.transistorsoft.fetch",
|
|
593
|
+
...(backgroundSyncEnabled ? ["com.transistorsoft.background_sync"] : []),
|
|
594
|
+
...(backgroundFetchEnabled
|
|
595
|
+
? ["com.transistorsoft.push_sync_heartbeat"]
|
|
596
|
+
: []),
|
|
597
|
+
];
|
|
500
598
|
const newCfgs = `
|
|
501
|
-
${
|
|
599
|
+
${backgroundFetchEnabled
|
|
502
600
|
? `<key>BGTaskSchedulerPermittedIdentifiers</key>
|
|
503
601
|
<array>
|
|
504
|
-
|
|
505
|
-
</array>
|
|
506
|
-
<key>UIBackgroundModes</key>
|
|
507
|
-
<array>
|
|
508
|
-
<string>fetch</string>
|
|
602
|
+
${taskIds.map((id) => `<string>${id}</string>`).join("\n ")}
|
|
509
603
|
</array>
|
|
510
604
|
`
|
|
511
605
|
: ""}
|
|
512
606
|
|
|
513
|
-
${
|
|
607
|
+
${backgroundModes.length > 0
|
|
514
608
|
? `<key>UIBackgroundModes</key>
|
|
515
609
|
<array>
|
|
516
|
-
|
|
610
|
+
${backgroundModes.map((m) => `<string>${m}</string>`).join("\n ")}
|
|
517
611
|
</array>
|
|
518
612
|
`
|
|
519
613
|
: ""}
|
|
@@ -531,6 +625,12 @@ function modifyInfoPlist(buildDir, allowShareTo, backgroundSyncEnabled, pushSync
|
|
|
531
625
|
<true/>
|
|
532
626
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
|
533
627
|
<true/>
|
|
628
|
+
${appBoundHostname
|
|
629
|
+
? `<key>WKAppBoundDomains</key>
|
|
630
|
+
<array>
|
|
631
|
+
<string>${appBoundHostname}</string>
|
|
632
|
+
</array>`
|
|
633
|
+
: ""}
|
|
534
634
|
${allowShareTo
|
|
535
635
|
? `<key>CFBundleURLTypes</key>
|
|
536
636
|
<array>
|
|
@@ -546,29 +646,38 @@ function modifyInfoPlist(buildDir, allowShareTo, backgroundSyncEnabled, pushSync
|
|
|
546
646
|
</dict>
|
|
547
647
|
</array>`
|
|
548
648
|
: ""}
|
|
549
|
-
${allowClearTextTraffic
|
|
550
|
-
? `<key>NSAppTransportSecurity</key>
|
|
551
|
-
<dict>
|
|
552
|
-
<key>NSAllowsArbitraryLoads</key>
|
|
553
|
-
<true/>
|
|
554
|
-
</dict>`
|
|
555
|
-
: ""}
|
|
556
649
|
`;
|
|
557
650
|
// add newCfgs after the first <dict> tag
|
|
558
651
|
const newContent = content.replace(/<dict>/, `<dict>${newCfgs}`);
|
|
559
652
|
(0, fs_extra_1.writeFileSync)(infoPlist, newContent, "utf8");
|
|
560
653
|
}
|
|
561
|
-
|
|
654
|
+
/**
|
|
655
|
+
* Write `App.entitlements` with the optional APNs environment and app group.
|
|
656
|
+
* @param buildDir directory where the app will be built
|
|
657
|
+
* @param buildType `"debug"` → development APNs, `"release"` → production APNs
|
|
658
|
+
* @param pushSync include the APNs environment entitlement
|
|
659
|
+
* @param appGroupId optional app group ID for share extension data sharing
|
|
660
|
+
*/
|
|
661
|
+
function writeEntitlementsPlist(buildDir, buildType, pushSync, appGroupId) {
|
|
562
662
|
const file = (0, path_1.join)(buildDir, "ios", "App", "App", "App.entitlements");
|
|
663
|
+
const apsEnv = buildType === "release" ? "production" : "development";
|
|
664
|
+
const apsEnvEntry = pushSync
|
|
665
|
+
? ` <key>aps-environment</key>
|
|
666
|
+
<string>${apsEnv}</string>`
|
|
667
|
+
: "";
|
|
668
|
+
const appGroupsEntry = appGroupId
|
|
669
|
+
? ` <key>com.apple.security.application-groups</key>
|
|
670
|
+
<array>
|
|
671
|
+
<string>${appGroupId}</string>
|
|
672
|
+
</array>`
|
|
673
|
+
: "";
|
|
563
674
|
try {
|
|
564
675
|
(0, fs_extra_1.writeFileSync)(file, `<?xml version="1.0" encoding="UTF-8"?>
|
|
565
676
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
566
677
|
<plist version="1.0">
|
|
567
678
|
<dict>
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
<key>com.apple.security.application-groups</key>
|
|
571
|
-
<array />
|
|
679
|
+
${apsEnvEntry}
|
|
680
|
+
${appGroupsEntry}
|
|
572
681
|
</dict>
|
|
573
682
|
</plist>`);
|
|
574
683
|
}
|
|
@@ -576,23 +685,45 @@ function writeEntitlementsPlist(buildDir) {
|
|
|
576
685
|
console.log(`Unable to write the Entitlements plist file: ${error.message ? error.message : "Unknown error"}`);
|
|
577
686
|
}
|
|
578
687
|
}
|
|
579
|
-
|
|
688
|
+
/**
|
|
689
|
+
* Inject `CODE_SIGN_ENTITLEMENTS` into `project.pbxproj` for the App target.
|
|
690
|
+
* Uses `INFOPLIST_FILE = App/Info.plist;` as an anchor (unique to the App target).
|
|
691
|
+
* Idempotent — skips if the key is already present.
|
|
692
|
+
* @param buildDir directory where the app will be built
|
|
693
|
+
*/
|
|
694
|
+
function injectCodeSignEntitlements(buildDir) {
|
|
695
|
+
const pbxprojPath = (0, path_1.join)(buildDir, "ios", "App", "App.xcodeproj", "project.pbxproj");
|
|
580
696
|
try {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
697
|
+
let content = (0, fs_extra_1.readFileSync)(pbxprojPath, "utf8");
|
|
698
|
+
// check for this exact insertion, not just any CODE_SIGN_ENTITLEMENTS -
|
|
699
|
+
// the share-ext target sets its own, which isn't the same thing
|
|
700
|
+
if (content.includes("CODE_SIGN_ENTITLEMENTS = App/App.entitlements;")) {
|
|
701
|
+
console.log("CODE_SIGN_ENTITLEMENTS already set in project.pbxproj");
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
content = content.replaceAll("INFOPLIST_FILE = App/Info.plist;", `CODE_SIGN_ENTITLEMENTS = App/App.entitlements;\n\t\t\t\tINFOPLIST_FILE = App/Info.plist;`);
|
|
705
|
+
(0, fs_extra_1.writeFileSync)(pbxprojPath, content, "utf8");
|
|
706
|
+
console.log("CODE_SIGN_ENTITLEMENTS set in project.pbxproj for App target");
|
|
585
707
|
}
|
|
586
708
|
catch (error) {
|
|
587
|
-
console.log(`Unable to
|
|
709
|
+
console.log(`Unable to set CODE_SIGN_ENTITLEMENTS in project.pbxproj: ${error.message ?? "Unknown error"}`);
|
|
588
710
|
}
|
|
589
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Copy share extension source files into the iOS project directory.
|
|
714
|
+
* @param buildDir directory where the app will be built
|
|
715
|
+
*/
|
|
590
716
|
function copyShareExtFiles(buildDir) {
|
|
591
717
|
const iosAppDir = (0, path_1.join)(buildDir, "ios", "App");
|
|
592
718
|
const sefDir = (0, path_1.join)(buildDir, "share_extension_files");
|
|
593
719
|
(0, fs_extra_1.copySync)((0, path_1.join)(sefDir, "ShareViewController.swift"), (0, path_1.join)(iosAppDir, "share-ext", "ShareViewController.swift"), { overwrite: true });
|
|
594
720
|
(0, fs_extra_1.copySync)((0, path_1.join)(sefDir, "Info.plist"), (0, path_1.join)(iosAppDir, "share-ext", "Info.plist"), { overwrite: true });
|
|
595
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* Replace `YOUR_APP_GROUP_ID` placeholder in `ShareViewController.swift`.
|
|
724
|
+
* @param buildDir directory where the app will be built
|
|
725
|
+
* @param groupId app group identifier
|
|
726
|
+
*/
|
|
596
727
|
function modifyShareViewController(buildDir, groupId) {
|
|
597
728
|
const shareVCFile = (0, path_1.join)(buildDir, "ios", "App", "share-ext", "ShareViewController.swift");
|
|
598
729
|
let content = (0, fs_extra_1.readFileSync)(shareVCFile, "utf8");
|
|
@@ -600,15 +731,68 @@ function modifyShareViewController(buildDir, groupId) {
|
|
|
600
731
|
content = content.replace(/YOUR_APP_GROUP_ID/g, groupId);
|
|
601
732
|
(0, fs_extra_1.writeFileSync)(shareVCFile, content, "utf8");
|
|
602
733
|
}
|
|
603
|
-
|
|
734
|
+
/**
|
|
735
|
+
* Write the entitlements plist for the share extension target (app group
|
|
736
|
+
* only - no APNs entry, extensions don't receive push notifications).
|
|
737
|
+
* @param buildDir directory where the app will be built
|
|
738
|
+
* @param appGroupId app group ID shared with the main app
|
|
739
|
+
* @returns path to the entitlements file, relative to `ios/App`
|
|
740
|
+
*/
|
|
741
|
+
function writeShareExtEntitlementsPlist(buildDir, appGroupId) {
|
|
742
|
+
const relPath = (0, path_1.join)("share-ext", "ShareExtension.entitlements");
|
|
743
|
+
const file = (0, path_1.join)(buildDir, "ios", "App", relPath);
|
|
744
|
+
(0, fs_extra_1.writeFileSync)(file, `<?xml version="1.0" encoding="UTF-8"?>
|
|
745
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
746
|
+
<plist version="1.0">
|
|
747
|
+
<dict>
|
|
748
|
+
<key>com.apple.security.application-groups</key>
|
|
749
|
+
<array>
|
|
750
|
+
<string>${appGroupId}</string>
|
|
751
|
+
</array>
|
|
752
|
+
</dict>
|
|
753
|
+
</plist>`);
|
|
754
|
+
return relPath;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Add a "share-ext" Share Extension target to `App.xcodeproj`, so the app
|
|
758
|
+
* can be built with iOS share-to support without ever opening Xcode.
|
|
759
|
+
* @param buildDir directory where the app will be built
|
|
760
|
+
* @param appId main app's bundle id - the extension's is `${appId}.share-ext`
|
|
761
|
+
* @param appGroupId optional app group ID, wired into a matching
|
|
762
|
+
* entitlements plist for the new target
|
|
763
|
+
*/
|
|
764
|
+
function createShareExtensionTarget(buildDir, appId, appGroupId) {
|
|
765
|
+
const iosAppDir = (0, path_1.join)(buildDir, "ios", "App");
|
|
766
|
+
const projectPath = (0, path_1.join)(iosAppDir, "App.xcodeproj");
|
|
767
|
+
const entitlementsRelPath = appGroupId
|
|
768
|
+
? writeShareExtEntitlementsPlist(buildDir, appGroupId)
|
|
769
|
+
: undefined;
|
|
770
|
+
const scriptPath = (0, path_1.join)(__dirname, "xcode", "create_share_extension.rb");
|
|
771
|
+
const args = [scriptPath, projectPath, `${appId}.share-ext`];
|
|
772
|
+
if (entitlementsRelPath)
|
|
773
|
+
args.push(entitlementsRelPath);
|
|
774
|
+
const result = (0, child_process_1.spawnSync)("ruby", args, { cwd: iosAppDir });
|
|
775
|
+
if (result.status !== 0) {
|
|
776
|
+
throw new Error(`Unable to create the share extension target: ${result.stderr?.toString() || result.error?.message || "unknown error"}`);
|
|
777
|
+
}
|
|
778
|
+
console.log(result.stdout?.toString());
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Patch `AppDelegate.swift` to register background tasks and push handlers.
|
|
782
|
+
* @param buildDir directory where the app will be built
|
|
783
|
+
* @param pushSyncEnabled register remote-notification handler
|
|
784
|
+
* @param allowShareTo register URL scheme handler for share-to
|
|
785
|
+
* @param backgroundFetchEnabled initialize TSBackgroundFetch (sync or heartbeat)
|
|
786
|
+
*/
|
|
787
|
+
function modifyAppDelegate(buildDir, pushSyncEnabled, allowShareTo, backgroundFetchEnabled = false) {
|
|
604
788
|
const appDelegateFile = (0, path_1.join)(buildDir, "ios", "App", "App", "AppDelegate.swift");
|
|
605
789
|
let content = (0, fs_extra_1.readFileSync)(appDelegateFile, "utf8");
|
|
606
790
|
// modify cusomization point after application launch
|
|
607
791
|
content = content.replace(/func application\(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: \[UIApplication.LaunchOptionsKey: Any\]\?\) -> Bool {/, `func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
608
792
|
// Override point for customization after application launch.
|
|
609
793
|
|
|
610
|
-
|
|
611
|
-
${
|
|
794
|
+
|
|
795
|
+
${backgroundFetchEnabled
|
|
612
796
|
? `// [capacitor-background-fetch]
|
|
613
797
|
let fetchManager = TSBackgroundFetch.sharedInstance();
|
|
614
798
|
fetchManager?.didFinishLaunching();`
|
|
@@ -616,7 +800,7 @@ function modifyAppDelegate(buildDir, backgroundSyncEnabled, pushSyncEnabled, all
|
|
|
616
800
|
|
|
617
801
|
self.window?.rootViewController?.navigationController?.interactivePopGestureRecognizer?.isEnabled = false;
|
|
618
802
|
`);
|
|
619
|
-
if (
|
|
803
|
+
if (backgroundFetchEnabled) {
|
|
620
804
|
// add "import TSBackgroundFetch" before "@UIApplicationMain"
|
|
621
805
|
content = content.replace(/@UIApplicationMain/, `import TSBackgroundFetch
|
|
622
806
|
|
|
@@ -729,7 +913,7 @@ function modifyAppDelegate(buildDir, backgroundSyncEnabled, pushSyncEnabled, all
|
|
|
729
913
|
}
|
|
730
914
|
(0, fs_extra_1.writeFileSync)(appDelegateFile, content, "utf8");
|
|
731
915
|
}
|
|
732
|
-
function writePrivacyInfo(buildDir,
|
|
916
|
+
function writePrivacyInfo(buildDir, backgroundFetchEnabled) {
|
|
733
917
|
const infoFile = (0, path_1.join)(buildDir, "ios", "App", "PrivacyInfo.xcprivacy");
|
|
734
918
|
const content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
735
919
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -747,7 +931,7 @@ function writePrivacyInfo(buildDir, backgroundSyncEnabled) {
|
|
|
747
931
|
</array>
|
|
748
932
|
</dict>
|
|
749
933
|
|
|
750
|
-
${
|
|
934
|
+
${backgroundFetchEnabled
|
|
751
935
|
? `
|
|
752
936
|
<!-- [1] background_fetch: UserDefaults -->
|
|
753
937
|
<dict>
|
|
@@ -776,7 +960,7 @@ function copyServerFiles(buildDir) {
|
|
|
776
960
|
if (!(0, fs_extra_1.existsSync)(assetsDst)) {
|
|
777
961
|
(0, fs_extra_1.mkdirSync)(assetsDst, { recursive: true });
|
|
778
962
|
}
|
|
779
|
-
const serverRoot = (0, path_1.join)(require.resolve("@saltcorn/server"), "..");
|
|
963
|
+
const serverRoot = (0, path_1.join)(require.resolve("@saltcorn/server/package.json"), "..");
|
|
780
964
|
const srcPrefix = (0, path_1.join)(serverRoot, "public");
|
|
781
965
|
const srcAssests = [
|
|
782
966
|
"jquery-3.6.0.min.js",
|
|
@@ -835,7 +1019,7 @@ async function copySiteLogo(buildDir) {
|
|
|
835
1019
|
* create a cfg file, the app use this configs
|
|
836
1020
|
* @param param0
|
|
837
1021
|
*/
|
|
838
|
-
function writeCfgFile({ buildDir, entryPoint, entryPointType, serverPath, synchedTables, tenantAppName, autoPublicLogin, showContinueAsPublicUser, allowOfflineMode, syncOnReconnect, syncOnAppResume, pushSync, syncInterval, allowShareTo, }) {
|
|
1022
|
+
function writeCfgFile({ buildDir, entryPoint, entryPointType, serverPath, synchedTables, tenantAppName, autoPublicLogin, showContinueAsPublicUser, allowOfflineMode, syncOnReconnect, syncOnAppResume, pushSync, syncInterval, pushSyncHeartbeatInterval, allowShareTo, apnsEnvironment, }) {
|
|
839
1023
|
const wwwDir = (0, path_1.join)(buildDir, "www");
|
|
840
1024
|
let cfg = {
|
|
841
1025
|
version_tag: index_1.default.connectObj.version_tag,
|
|
@@ -851,7 +1035,9 @@ function writeCfgFile({ buildDir, entryPoint, entryPointType, serverPath, synche
|
|
|
851
1035
|
syncOnAppResume,
|
|
852
1036
|
pushSync,
|
|
853
1037
|
syncInterval,
|
|
1038
|
+
pushSyncHeartbeatInterval,
|
|
854
1039
|
allowShareTo,
|
|
1040
|
+
apnsEnvironment,
|
|
855
1041
|
};
|
|
856
1042
|
if (entryPointType !== "byrole") {
|
|
857
1043
|
cfg.entry_point = `get/${entryPointType === "pagegroup" ? "page" : entryPointType}/${entryPoint}`;
|
|
@@ -975,7 +1161,7 @@ async function buildTablesFile(buildDir, includedPlugins) {
|
|
|
975
1161
|
* @param buildDir directory where the app will be build
|
|
976
1162
|
*/
|
|
977
1163
|
function copyTranslationFiles(buildDir) {
|
|
978
|
-
const localesDir = (0, path_1.join)(require.resolve("@saltcorn/server"), "..", "locales");
|
|
1164
|
+
const localesDir = (0, path_1.join)(require.resolve("@saltcorn/server/package.json"), "..", "locales");
|
|
979
1165
|
(0, fs_extra_1.copySync)(localesDir, (0, path_1.join)(buildDir, "www", "data", "locales"));
|
|
980
1166
|
}
|
|
981
1167
|
/**
|
|
@@ -1037,7 +1223,7 @@ async function prepareSplashPage(buildDir, pageName, serverUrl, tenantAppName, u
|
|
|
1037
1223
|
title: page.title,
|
|
1038
1224
|
body: !contents
|
|
1039
1225
|
? { above: [] }
|
|
1040
|
-
: contents
|
|
1226
|
+
: "above" in contents
|
|
1041
1227
|
? contents
|
|
1042
1228
|
: { above: [contents] },
|
|
1043
1229
|
alerts: [],
|
|
@@ -1067,6 +1253,13 @@ async function prepareSplashPage(buildDir, pageName, serverUrl, tenantAppName, u
|
|
|
1067
1253
|
console.log(error);
|
|
1068
1254
|
}
|
|
1069
1255
|
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Write the iOS `Podfile` and run `pod install`.
|
|
1258
|
+
* @param buildDir directory where the app will be built
|
|
1259
|
+
* @param hasPush include push notification pods
|
|
1260
|
+
* @param hasBackgroundFetch include background fetch pod
|
|
1261
|
+
* @param hasSilentPush include silent-notifications pod
|
|
1262
|
+
*/
|
|
1070
1263
|
function writePodfile(buildDir, hasPush, hasBackgroundFetch, hasSilentPush) {
|
|
1071
1264
|
const state = (0, state_1.getState)();
|
|
1072
1265
|
let hasGeolocation = false;
|
|
@@ -1095,7 +1288,10 @@ function writePodfile(buildDir, hasPush, hasBackgroundFetch, hasSilentPush) {
|
|
|
1095
1288
|
pod 'CapacitorCommunitySqlite', :path => '../../node_modules/@capacitor-community/sqlite'
|
|
1096
1289
|
pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera'
|
|
1097
1290
|
pod 'CapacitorFilesystem', :path => '../../node_modules/@capacitor/filesystem'
|
|
1098
|
-
|
|
1291
|
+
pod 'CapacitorFileTransfer', :path => '../../node_modules/@capacitor/file-transfer'
|
|
1292
|
+
${hasGeolocation
|
|
1293
|
+
? `pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation'`
|
|
1294
|
+
: ""}
|
|
1099
1295
|
pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network'
|
|
1100
1296
|
pod 'CapacitorScreenOrientation', :path => '../../node_modules/@capacitor/screen-orientation'
|
|
1101
1297
|
pod 'SendIntent', :path => '../../node_modules/send-intent'
|
|
@@ -1105,8 +1301,12 @@ function writePodfile(buildDir, hasPush, hasBackgroundFetch, hasSilentPush) {
|
|
|
1105
1301
|
? `pod 'CapacitorPushNotifications', :path => '../../node_modules/@capacitor/push-notifications'
|
|
1106
1302
|
pod 'CapacitorDevice', :path => '../../node_modules/@capacitor/device'`
|
|
1107
1303
|
: ""}
|
|
1108
|
-
${hasSilentPush
|
|
1109
|
-
|
|
1304
|
+
${hasSilentPush
|
|
1305
|
+
? `pod 'CapacitorPluginSilentNotifications', :path => '../../node_modules/capacitor-plugin-silent-notifications'`
|
|
1306
|
+
: ""}
|
|
1307
|
+
${hasBackgroundFetch
|
|
1308
|
+
? `pod 'TransistorsoftCapacitorBackgroundFetch', :path => '../../node_modules/@transistorsoft/capacitor-background-fetch'`
|
|
1309
|
+
: ""}
|
|
1110
1310
|
end
|
|
1111
1311
|
|
|
1112
1312
|
target 'App' do
|
|
@@ -1167,6 +1367,11 @@ function modifyXcodeProjectFile(buildDir, appVersion, iosCfg) {
|
|
|
1167
1367
|
fileContent = fileContent.replaceAll(/MARKETING_VERSION = 1.0;/g, `MARKETING_VERSION = ${appVersion};`);
|
|
1168
1368
|
(0, fs_extra_1.writeFileSync)(projectFile, fileContent, "utf8");
|
|
1169
1369
|
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Convert a semver string to an Android integer version code (`major*1e6 + minor*1e3 + patch`).
|
|
1372
|
+
* @param appVersion semver string e.g. `"1.2.3"`
|
|
1373
|
+
* @returns integer version code
|
|
1374
|
+
*/
|
|
1170
1375
|
function generateAndroidVersionCode(appVersion) {
|
|
1171
1376
|
if (!/^\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(appVersion) || appVersion === "0.0.0")
|
|
1172
1377
|
throw new Error(`Invalid app version '${appVersion}'`);
|
|
@@ -1175,6 +1380,12 @@ function generateAndroidVersionCode(appVersion) {
|
|
|
1175
1380
|
parseInt(parts[1]) * 1000 +
|
|
1176
1381
|
parseInt(parts[2]));
|
|
1177
1382
|
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Patch `build.gradle` with the app version and optional keystore signing config.
|
|
1385
|
+
* @param buildDir directory where the app will be built
|
|
1386
|
+
* @param appVersion semver string used for `versionName` and `versionCode`
|
|
1387
|
+
* @param keyStoreData signing credentials; omit for unsigned debug builds
|
|
1388
|
+
*/
|
|
1178
1389
|
function modifyGradleConfig(buildDir, appVersion, keyStoreData) {
|
|
1179
1390
|
console.log("modifyGradleConfig");
|
|
1180
1391
|
const gradleFile = (0, path_1.join)(buildDir, "android", "app", "build.gradle");
|