@absolutejs/absolute 0.20.0-beta.115 → 0.20.0-beta.117
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/CHANGELOG.md +18 -0
- package/changelog.json +38 -1
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/build.js +9 -4
- package/dist/build.js.map +5 -4
- package/dist/cli/{androidEmulatorController-nybhzc6c.js → androidEmulatorController-v6chbhem.js} +2 -2
- package/dist/cli/{androidNativeWatcher-2930tb8c.js → androidNativeWatcher-mgqws6ag.js} +2 -2
- package/dist/cli/{compile-ya4kd2sg.js → compile-5m830csy.js} +2 -2
- package/dist/cli/{dev-5cqmf7za.js → dev-ww30tdrv.js} +10 -7
- package/dist/cli/{expoProject-f5j20xms.js → expoProject-w2pzjwnx.js} +1 -1
- package/dist/cli/{index-28pbrd3q.js → index-18q9qc7f.js} +15 -2
- package/dist/cli/{index-5fmgn0nx.js → index-5268nwjx.js} +1 -1
- package/dist/cli/{index-5skbydh0.js → index-hneb973j.js} +1 -1
- package/dist/cli/{index-6mbm4n3g.js → index-k8r00xh9.js} +1 -1
- package/dist/cli/{index-dr21p6ae.js → index-nxcwg9ma.js} +590 -152
- package/dist/cli/{index-qr0pc777.js → index-wtyg9cy0.js} +1 -1
- package/dist/cli/index.js +5 -5
- package/dist/cli/{iosNativeWatcher-t6gv0p1w.js → iosNativeWatcher-8w1gawg4.js} +2 -2
- package/dist/cli/{iosSimulatorController-5n2a9fxv.js → iosSimulatorController-5vhqvhg6.js} +2 -2
- package/dist/cli/{mobile-fg2226vv.js → mobile-21vsmrjd.js} +47 -56
- package/dist/cli/{start-jt61f40d.js → start-3bhf56tj.js} +3 -3
- package/dist/index.js +12 -6
- package/dist/index.js.map +6 -5
- package/dist/mobile/index.js +692 -274
- package/dist/mobile/index.js.map +8 -6
- package/dist/mobile/remoteMacAgentEntry.js +564 -231
- package/dist/mobile/shellBootstrap.js +6 -1
- package/dist/react/hooks/index.js.map +2 -2
- package/dist/src/dev/serverEntryCopies.d.ts +2 -0
- package/dist/src/mobile/capacitorProject.d.ts +4 -0
- package/dist/src/mobile/expoEmbeddedAssets.d.ts +1 -0
- package/dist/src/mobile/nativePackages.d.ts +10 -0
- package/dist/src/mobile/updateRuntime.d.ts +1 -0
- package/package.json +6 -2
|
@@ -16,17 +16,80 @@ import {
|
|
|
16
16
|
mkdir as mkdir2,
|
|
17
17
|
mkdtemp,
|
|
18
18
|
readdir,
|
|
19
|
-
readFile as
|
|
19
|
+
readFile as readFile3,
|
|
20
20
|
rename as rename2,
|
|
21
21
|
rm as rm2,
|
|
22
22
|
writeFile as writeFile2
|
|
23
23
|
} from "fs/promises";
|
|
24
24
|
import { createHash as createHash3 } from "crypto";
|
|
25
|
-
import { basename, dirname as
|
|
25
|
+
import { basename, dirname as dirname4, join as join8, relative as relative3, resolve as resolve5, sep } from "path";
|
|
26
|
+
|
|
27
|
+
// src/mobile/nativePackages.ts
|
|
28
|
+
import { readFile } from "fs/promises";
|
|
29
|
+
import { dirname, join, resolve } from "path";
|
|
30
|
+
var ABSOLUTE_CAPACITOR_DEVICES_VERSION = "0.8.0";
|
|
31
|
+
var ABSOLUTE_CAPACITOR_SYNC_VERSION = "0.9.3";
|
|
32
|
+
var ABSOLUTE_DEVICES_VERSION = "0.7.0";
|
|
33
|
+
var ABSOLUTE_EXPO_DEVICES_VERSION = "0.0.11";
|
|
34
|
+
var ABSOLUTE_NATIVE_EXACT_PACKAGES = new Set([
|
|
35
|
+
"@absolutejs/devices",
|
|
36
|
+
"@absolutejs/devices-capacitor",
|
|
37
|
+
"@absolutejs/sync-capacitor"
|
|
38
|
+
]);
|
|
39
|
+
var CAPACITOR_PACKAGE_SPECS = [
|
|
40
|
+
"@capacitor/core@8.5.0",
|
|
41
|
+
"@capacitor/app@8.1.1",
|
|
42
|
+
"@capacitor/browser@8.0.4",
|
|
43
|
+
"@capacitor/network@8.0.1",
|
|
44
|
+
"@capacitor/preferences@8.0.1",
|
|
45
|
+
"@capacitor/cli@8.5.0",
|
|
46
|
+
"@capacitor/android@8.5.0",
|
|
47
|
+
"@capacitor/ios@8.5.0",
|
|
48
|
+
`@absolutejs/devices@${ABSOLUTE_DEVICES_VERSION}`,
|
|
49
|
+
`@absolutejs/devices-capacitor@${ABSOLUTE_CAPACITOR_DEVICES_VERSION}`
|
|
50
|
+
];
|
|
51
|
+
var CAPACITOR_SYNC_PACKAGE_SPECS = [
|
|
52
|
+
`@absolutejs/sync-capacitor@${ABSOLUTE_CAPACITOR_SYNC_VERSION}`,
|
|
53
|
+
"@capacitor-community/sqlite@8.1.1"
|
|
54
|
+
];
|
|
55
|
+
var resolvedPackageVersion = async (projectRoot, name) => {
|
|
56
|
+
const directory = resolve(projectRoot);
|
|
57
|
+
let source;
|
|
58
|
+
try {
|
|
59
|
+
source = await readFile(join(directory, "node_modules", name, "package.json"), "utf8");
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT")
|
|
62
|
+
return;
|
|
63
|
+
const parent = dirname(directory);
|
|
64
|
+
return parent === directory ? undefined : resolvedPackageVersion(parent, name);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const manifest = JSON.parse(source);
|
|
68
|
+
return typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "version") : undefined;
|
|
69
|
+
} catch {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var assertNativePackageVersions = async (projectRoot, specs, exactPackages) => {
|
|
74
|
+
const stale = await packagesNeedingExactInstall(projectRoot, specs, new Set(specs.map(packageNameFromSpec)), exactPackages);
|
|
75
|
+
if (stale.length > 0)
|
|
76
|
+
throw new TypeError(`Native dependency installation did not resolve the tested versions: ${stale.join(", ")}. A stale workspace-local installation may be shadowing hoisted packages. Restore a clean dependency installation for this app/workspace with your package manager, then retry absolute mobile init or sync. A forced install alone may leave obsolete workspace-local copies in place.`);
|
|
77
|
+
};
|
|
78
|
+
var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
|
|
79
|
+
var packagesNeedingExactInstall = async (projectRoot, specs, installed, exactPackages) => (await Promise.all(specs.map(async (spec) => {
|
|
80
|
+
const name = packageNameFromSpec(spec);
|
|
81
|
+
if (!installed.has(name))
|
|
82
|
+
return spec;
|
|
83
|
+
if (!exactPackages.has(name))
|
|
84
|
+
return;
|
|
85
|
+
if (await resolvedPackageVersion(projectRoot, name) === spec.slice(spec.lastIndexOf("@") + 1))
|
|
86
|
+
return;
|
|
87
|
+
return spec;
|
|
88
|
+
}))).filter((spec) => spec !== undefined);
|
|
26
89
|
|
|
27
90
|
// src/mobile/syncSchema.ts
|
|
28
91
|
import { readFileSync } from "fs";
|
|
29
|
-
import { dirname, join, resolve } from "path";
|
|
92
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
30
93
|
|
|
31
94
|
// node_modules/@absolutejs/sync/dist/client/index.js
|
|
32
95
|
var RUNTIME_TRANSPORT = Symbol.for("@absolutejs/sync/client-runtime-transport");
|
|
@@ -183,13 +246,13 @@ var localSchemaMetadata = (manifest) => {
|
|
|
183
246
|
return Reflect.get(sync, "localSchema");
|
|
184
247
|
};
|
|
185
248
|
var packageManifestPath = (projectRoot, packageName) => {
|
|
186
|
-
let directory =
|
|
249
|
+
let directory = resolve2(projectRoot);
|
|
187
250
|
while (true) {
|
|
188
|
-
const candidate =
|
|
251
|
+
const candidate = join2(directory, "node_modules", packageName, "package.json");
|
|
189
252
|
const manifest = manifestAt(candidate);
|
|
190
253
|
if (manifest && Reflect.get(manifest, "name") === packageName)
|
|
191
254
|
return candidate;
|
|
192
|
-
const parent =
|
|
255
|
+
const parent = dirname2(directory);
|
|
193
256
|
if (parent === directory)
|
|
194
257
|
return;
|
|
195
258
|
directory = parent;
|
|
@@ -428,7 +491,7 @@ var dependencyNames = (manifest) => [
|
|
|
428
491
|
Reflect.get(manifest, "peerDependencies")
|
|
429
492
|
].flatMap((dependencies) => object(dependencies) ? Object.keys(dependencies) : []).filter((name, index, names) => names.indexOf(name) === index).sort();
|
|
430
493
|
var discoverAbsoluteSyncSchema = (projectRoot) => {
|
|
431
|
-
const appManifestPath =
|
|
494
|
+
const appManifestPath = join2(resolve2(projectRoot), "package.json");
|
|
432
495
|
const appManifest = manifestAt(appManifestPath);
|
|
433
496
|
if (!appManifest)
|
|
434
497
|
return {
|
|
@@ -469,7 +532,7 @@ var discoverAbsoluteSyncSchema = (projectRoot) => {
|
|
|
469
532
|
|
|
470
533
|
// src/mobile/deviceCapabilities.ts
|
|
471
534
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
472
|
-
import { extname, join as
|
|
535
|
+
import { extname, join as join3, relative, resolve as resolve3 } from "path";
|
|
473
536
|
import { fileURLToPath } from "url";
|
|
474
537
|
import ts from "typescript";
|
|
475
538
|
var DEVICES_PACKAGE = "@absolutejs/devices";
|
|
@@ -644,7 +707,7 @@ var absoluteDeviceNativeRequirements = (plan) => {
|
|
|
644
707
|
};
|
|
645
708
|
var loadAbsoluteDeviceCapabilityProviders = (projectRoot, provider = "capacitor") => {
|
|
646
709
|
const adapter = ADAPTERS[provider];
|
|
647
|
-
let path =
|
|
710
|
+
let path = join3(resolve3(projectRoot), "node_modules", adapter, "package.json");
|
|
648
711
|
try {
|
|
649
712
|
readFileSync2(path, "utf8");
|
|
650
713
|
} catch {
|
|
@@ -702,7 +765,7 @@ var assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
|
|
|
702
765
|
if (missing.includes(spec))
|
|
703
766
|
return false;
|
|
704
767
|
try {
|
|
705
|
-
return readJson(
|
|
768
|
+
return readJson(join3(resolve3(projectRoot), "node_modules", packageName, "package.json")).version !== spec.slice(separator + 1);
|
|
706
769
|
} catch {
|
|
707
770
|
return true;
|
|
708
771
|
}
|
|
@@ -712,7 +775,7 @@ var assertAbsoluteDeviceCapabilityPackages = (projectRoot, plan) => {
|
|
|
712
775
|
throw new TypeError(`Device capabilities ${plan.capabilities.join(", ")} require ${unmet.join(", ")}. Run absolute mobile sync and approve the detected capability plugins.`);
|
|
713
776
|
};
|
|
714
777
|
var directAbsoluteProjectPackages = (projectRoot) => {
|
|
715
|
-
const manifest = readJson(
|
|
778
|
+
const manifest = readJson(join3(resolve3(projectRoot), "package.json"));
|
|
716
779
|
const packages = new Set;
|
|
717
780
|
for (const field of ["dependencies", "devDependencies"]) {
|
|
718
781
|
const dependencies = manifest[field];
|
|
@@ -723,16 +786,16 @@ var directAbsoluteProjectPackages = (projectRoot) => {
|
|
|
723
786
|
return packages;
|
|
724
787
|
};
|
|
725
788
|
var discoverAbsoluteDeviceCapabilities = (projectRoot, providers = loadAbsoluteDeviceCapabilityProviders(projectRoot)) => {
|
|
726
|
-
const root =
|
|
789
|
+
const root = resolve3(projectRoot);
|
|
727
790
|
if (!existsSync(root))
|
|
728
791
|
return [];
|
|
729
792
|
const known = new Set(Object.keys(providers));
|
|
730
793
|
const capabilities = new Set;
|
|
731
794
|
for (const path of SOURCE_GLOB.scanSync({ cwd: root })) {
|
|
732
|
-
const portable = relative(root,
|
|
795
|
+
const portable = relative(root, resolve3(root, path)).replaceAll("\\", "/");
|
|
733
796
|
if (isIgnored(portable))
|
|
734
797
|
continue;
|
|
735
|
-
const source = readFileSync2(
|
|
798
|
+
const source = readFileSync2(resolve3(root, portable), "utf8");
|
|
736
799
|
for (const name of importedCapabilities(source, portable))
|
|
737
800
|
if (known.has(name))
|
|
738
801
|
capabilities.add(name);
|
|
@@ -902,6 +965,7 @@ var createAbsoluteMobileUpdateRuntimeDescriptor = (options) => ({
|
|
|
902
965
|
},
|
|
903
966
|
deviceCapabilities: options.deviceCapabilities,
|
|
904
967
|
engine: options.config.engine,
|
|
968
|
+
...options.config.engine === "expo" ? { expoEmbeddedAssetsAbi: 1 } : {},
|
|
905
969
|
format: ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT,
|
|
906
970
|
shellAbi: ABSOLUTE_MOBILE_SHELL_ABI,
|
|
907
971
|
syncSchema: options.syncSchema ?? null,
|
|
@@ -930,7 +994,7 @@ var resolveAbsoluteMobileUpdateRuntime = (config, projectRoot) => {
|
|
|
930
994
|
};
|
|
931
995
|
|
|
932
996
|
// src/mobile/expoNativeObservability.ts
|
|
933
|
-
import { join as
|
|
997
|
+
import { join as join4 } from "path";
|
|
934
998
|
var HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
|
|
935
999
|
`;
|
|
936
1000
|
var XML_HEADER = `<!-- Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync. -->
|
|
@@ -1372,31 +1436,31 @@ export const enqueueAbsoluteExpoNativeObservabilityForTesting = async (testId: s
|
|
|
1372
1436
|
`;
|
|
1373
1437
|
var absoluteExpoNativeObservabilityFiles = (project) => new Map([
|
|
1374
1438
|
[
|
|
1375
|
-
|
|
1439
|
+
join4(project, "modules/absolute-mobile-observability/expo-module.config.json"),
|
|
1376
1440
|
moduleConfig
|
|
1377
1441
|
],
|
|
1378
1442
|
[
|
|
1379
|
-
|
|
1443
|
+
join4(project, "modules/absolute-mobile-observability/ios/AbsoluteMobileObservability.podspec"),
|
|
1380
1444
|
podspec
|
|
1381
1445
|
],
|
|
1382
1446
|
[
|
|
1383
|
-
|
|
1447
|
+
join4(project, "modules/absolute-mobile-observability/ios/AbsoluteMobileObservabilityModule.swift"),
|
|
1384
1448
|
iosModule
|
|
1385
1449
|
],
|
|
1386
1450
|
[
|
|
1387
|
-
|
|
1451
|
+
join4(project, "modules/absolute-mobile-observability/android/build.gradle"),
|
|
1388
1452
|
androidBuild
|
|
1389
1453
|
],
|
|
1390
1454
|
[
|
|
1391
|
-
|
|
1455
|
+
join4(project, "modules/absolute-mobile-observability/android/src/main/AndroidManifest.xml"),
|
|
1392
1456
|
androidManifest
|
|
1393
1457
|
],
|
|
1394
1458
|
[
|
|
1395
|
-
|
|
1459
|
+
join4(project, "modules/absolute-mobile-observability/android/src/main/java/expo/modules/absolutemobileobservability/AbsoluteMobileObservabilityModule.kt"),
|
|
1396
1460
|
androidModule
|
|
1397
1461
|
],
|
|
1398
1462
|
[
|
|
1399
|
-
|
|
1463
|
+
join4(project, "src/generated/AbsoluteNativeObservability.ts"),
|
|
1400
1464
|
expoNativeObservabilityRuntimeSource
|
|
1401
1465
|
]
|
|
1402
1466
|
]);
|
|
@@ -1405,7 +1469,7 @@ var absoluteExpoNativeObservabilityPaths = (project) => [
|
|
|
1405
1469
|
];
|
|
1406
1470
|
|
|
1407
1471
|
// src/mobile/expoActivityResultRecovery.ts
|
|
1408
|
-
import { join as
|
|
1472
|
+
import { join as join5 } from "path";
|
|
1409
1473
|
var HEADER2 = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
|
|
1410
1474
|
`;
|
|
1411
1475
|
var XML_HEADER2 = `<!-- Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync. -->
|
|
@@ -1642,47 +1706,306 @@ export const takeAbsoluteActivityResultCancellation = () =>
|
|
|
1642
1706
|
`;
|
|
1643
1707
|
var absoluteExpoActivityResultRecoveryFiles = (project) => new Map([
|
|
1644
1708
|
[
|
|
1645
|
-
|
|
1709
|
+
join5(project, "modules/absolute-activity-result-recovery/expo-module.config.json"),
|
|
1646
1710
|
moduleConfig2
|
|
1647
1711
|
],
|
|
1648
1712
|
[
|
|
1649
|
-
|
|
1713
|
+
join5(project, "modules/absolute-activity-result-recovery/ios/AbsoluteActivityResultRecovery.podspec"),
|
|
1650
1714
|
podspec2
|
|
1651
1715
|
],
|
|
1652
1716
|
[
|
|
1653
|
-
|
|
1717
|
+
join5(project, "modules/absolute-activity-result-recovery/ios/AbsoluteActivityResultRecoveryModule.swift"),
|
|
1654
1718
|
iosModule2
|
|
1655
1719
|
],
|
|
1656
1720
|
[
|
|
1657
|
-
|
|
1721
|
+
join5(project, "modules/absolute-activity-result-recovery/android/build.gradle"),
|
|
1658
1722
|
androidBuild2
|
|
1659
1723
|
],
|
|
1660
1724
|
[
|
|
1661
|
-
|
|
1725
|
+
join5(project, "modules/absolute-activity-result-recovery/android/src/main/AndroidManifest.xml"),
|
|
1662
1726
|
androidManifest2
|
|
1663
1727
|
],
|
|
1664
1728
|
[
|
|
1665
|
-
|
|
1729
|
+
join5(project, "modules/absolute-activity-result-recovery/android/src/main/java/expo/modules/absoluteactivityresultrecovery/AbsoluteActivityResultRecoveryModule.kt"),
|
|
1666
1730
|
androidModule2
|
|
1667
1731
|
],
|
|
1668
1732
|
[
|
|
1669
|
-
|
|
1733
|
+
join5(project, "src/generated/AbsoluteActivityResultRecovery.ts"),
|
|
1670
1734
|
runtime
|
|
1671
1735
|
]
|
|
1672
1736
|
]);
|
|
1673
1737
|
|
|
1738
|
+
// src/mobile/expoEmbeddedAssets.ts
|
|
1739
|
+
import { join as join6 } from "path";
|
|
1740
|
+
var header = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
|
|
1741
|
+
`;
|
|
1742
|
+
var android = `${header}package expo.modules.absoluteembeddedassets
|
|
1743
|
+
|
|
1744
|
+
import android.content.Context
|
|
1745
|
+
import android.graphics.Bitmap
|
|
1746
|
+
import android.net.Uri
|
|
1747
|
+
import android.util.Log
|
|
1748
|
+
import android.webkit.WebResourceRequest
|
|
1749
|
+
import android.webkit.WebResourceResponse
|
|
1750
|
+
import android.webkit.WebView
|
|
1751
|
+
import androidx.webkit.WebViewAssetLoader
|
|
1752
|
+
import androidx.webkit.WebViewCompat
|
|
1753
|
+
import androidx.webkit.WebViewFeature
|
|
1754
|
+
import androidx.webkit.ScriptHandler
|
|
1755
|
+
import androidx.webkit.WebMessageCompat
|
|
1756
|
+
import com.facebook.react.ReactPackage
|
|
1757
|
+
import com.facebook.react.bridge.NativeModule
|
|
1758
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
1759
|
+
import com.facebook.react.bridge.ReadableMap
|
|
1760
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
1761
|
+
import com.facebook.react.uimanager.ViewManager
|
|
1762
|
+
import com.reactnativecommunity.webview.RNCWebViewClient
|
|
1763
|
+
import com.reactnativecommunity.webview.RNCWebViewManager
|
|
1764
|
+
import com.reactnativecommunity.webview.RNCWebViewWrapper
|
|
1765
|
+
import expo.modules.kotlin.modules.Module
|
|
1766
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
1767
|
+
import java.io.ByteArrayInputStream
|
|
1768
|
+
import java.io.File
|
|
1769
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
1770
|
+
import java.util.WeakHashMap
|
|
1771
|
+
|
|
1772
|
+
private const val HOST = "appassets.androidplatform.net"
|
|
1773
|
+
private const val PREFIX = "/absolutejs/"
|
|
1774
|
+
|
|
1775
|
+
internal object AbsoluteEmbeddedAssets {
|
|
1776
|
+
private val bundles = ConcurrentHashMap<String, Map<String, File>>()
|
|
1777
|
+
|
|
1778
|
+
fun register(context: Context, rootUri: String, paths: List<String>): String {
|
|
1779
|
+
check(WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT) &&
|
|
1780
|
+
WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
|
|
1781
|
+
"Update Android System WebView to load this application."
|
|
1782
|
+
}
|
|
1783
|
+
val uri = Uri.parse(rootUri)
|
|
1784
|
+
require(uri.scheme == "file" && uri.authority.isNullOrEmpty() && uri.query == null && uri.fragment == null)
|
|
1785
|
+
val root = File(requireNotNull(uri.path)).canonicalFile
|
|
1786
|
+
val parent = File(context.filesDir, "absolutejs-web").canonicalFile
|
|
1787
|
+
require(root.parentFile == parent && root.name.matches(Regex("amexpo_[a-f0-9]{64}")))
|
|
1788
|
+
require(paths.isNotEmpty() && paths.size <= 100000 && paths.distinct().size == paths.size)
|
|
1789
|
+
val files = paths.associateWith { path ->
|
|
1790
|
+
require(path.isNotEmpty() && !path.contains('\\\\') && !path.contains('%') &&
|
|
1791
|
+
!path.contains('?') && !path.contains('#') && path.indexOf(0.toChar()) < 0 &&
|
|
1792
|
+
path.split('/').all { it.isNotEmpty() && it != "." && it != ".." })
|
|
1793
|
+
val file = File(root, path).canonicalFile
|
|
1794
|
+
require(file.path.startsWith(root.path + File.separator) && file.isFile)
|
|
1795
|
+
file
|
|
1796
|
+
}
|
|
1797
|
+
require(files.containsKey("index.html") && files.containsKey("absolute-mobile-manifest.json"))
|
|
1798
|
+
bundles[root.name] = files
|
|
1799
|
+
Log.i("AbsoluteJS", "Expo embedded web phase: asset-host-registered")
|
|
1800
|
+
return "https://" + HOST + PREFIX + root.name + "/index.html"
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
private fun empty(status: Int, reason: String) = WebResourceResponse(
|
|
1804
|
+
"text/plain", "UTF-8", status, reason,
|
|
1805
|
+
mapOf("Cache-Control" to "no-store", "X-Content-Type-Options" to "nosniff"),
|
|
1806
|
+
ByteArrayInputStream(ByteArray(0))
|
|
1807
|
+
)
|
|
1808
|
+
|
|
1809
|
+
private fun resource(path: String): WebResourceResponse {
|
|
1810
|
+
val bundle = path.substringBefore('/')
|
|
1811
|
+
val name = path.substringAfter('/', "")
|
|
1812
|
+
val file = bundles[bundle]?.get(name) ?: return empty(404, "Not Found")
|
|
1813
|
+
// Recheck canonical location at read time. Never follow a replaced symlink.
|
|
1814
|
+
if (file.canonicalFile != file || !file.isFile) return empty(404, "Not Found")
|
|
1815
|
+
val mime = when (file.extension.lowercase()) {
|
|
1816
|
+
"html", "htm" -> "text/html"
|
|
1817
|
+
"js", "mjs" -> "text/javascript"
|
|
1818
|
+
"json", "map" -> "application/json"
|
|
1819
|
+
"css" -> "text/css"
|
|
1820
|
+
"svg" -> "image/svg+xml"
|
|
1821
|
+
"wasm" -> "application/wasm"
|
|
1822
|
+
else -> android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension.lowercase()) ?: "application/octet-stream"
|
|
1823
|
+
}
|
|
1824
|
+
return try {
|
|
1825
|
+
WebResourceResponse(mime, "UTF-8", 200, "OK",
|
|
1826
|
+
mapOf("Cache-Control" to "no-store", "X-Content-Type-Options" to "nosniff"), file.inputStream())
|
|
1827
|
+
} catch (_: Exception) { empty(404, "Not Found") }
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
private val loader = WebViewAssetLoader.Builder()
|
|
1831
|
+
.setDomain(HOST).setHttpAllowed(false)
|
|
1832
|
+
.addPathHandler(PREFIX) { path -> resource(path) }.build()
|
|
1833
|
+
|
|
1834
|
+
fun intercept(request: WebResourceRequest, activeBundle: String?): WebResourceResponse? {
|
|
1835
|
+
val uri = request.url
|
|
1836
|
+
if (uri.host != HOST) return null
|
|
1837
|
+
if (uri.path.orEmpty().endsWith("/index.html")) Log.i("AbsoluteJS", "Expo embedded web phase: document-request")
|
|
1838
|
+
if (uri.path.orEmpty().endsWith("/absolute-mobile-bootstrap.js")) Log.i("AbsoluteJS", "Expo embedded web phase: bootstrap-request")
|
|
1839
|
+
// A reserved asset host must never fall through to DNS/network, even for
|
|
1840
|
+
// unknown bundles, wrong ports/schemes, methods, or malformed paths.
|
|
1841
|
+
if (uri.scheme != "https" || uri.port != -1 || uri.userInfo != null ||
|
|
1842
|
+
request.method != "GET") return empty(403, "Forbidden")
|
|
1843
|
+
// Root-relative image/font/CSS URLs retain their web meaning. Scope them
|
|
1844
|
+
// to this WebView's bundle, not the last bundle registered by another view.
|
|
1845
|
+
if (!uri.path.orEmpty().startsWith(PREFIX)) {
|
|
1846
|
+
if (request.isForMainFrame || activeBundle == null) return empty(404, "Not Found")
|
|
1847
|
+
return resource(activeBundle + "/" + uri.path.orEmpty().removePrefix("/"))
|
|
1848
|
+
}
|
|
1849
|
+
return loader.shouldInterceptRequest(uri) ?: empty(404, "Not Found")
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
class AbsoluteEmbeddedAssetsModule : Module() {
|
|
1854
|
+
override fun definition() = ModuleDefinition {
|
|
1855
|
+
Name("AbsoluteEmbeddedAssets")
|
|
1856
|
+
AsyncFunction("registerBundle") { root: String, paths: List<String> ->
|
|
1857
|
+
AbsoluteEmbeddedAssets.register(requireNotNull(appContext.reactContext), root, paths)
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
class AbsoluteEmbeddedWebViewManager : RNCWebViewManager() {
|
|
1863
|
+
private val scripts = WeakHashMap<RNCWebViewWrapper, ScriptHandler>()
|
|
1864
|
+
override fun getName() = "AbsoluteEmbeddedWebView"
|
|
1865
|
+
override fun setNewSource(view: RNCWebViewWrapper, value: ReadableMap?) {
|
|
1866
|
+
Log.i("AbsoluteJS", "Expo embedded web phase: source-assigned")
|
|
1867
|
+
super.setNewSource(view, value)
|
|
1868
|
+
}
|
|
1869
|
+
override fun setMessagingEnabled(view: RNCWebViewWrapper, value: Boolean) {
|
|
1870
|
+
super.setMessagingEnabled(view, value)
|
|
1871
|
+
// Upstream reports sourceOrigin as event.url and accepts subframe messages.
|
|
1872
|
+
// Retain its event dispatch, but bind this bridge to our main-frame origin
|
|
1873
|
+
// and attach the native-observed document URL, never a JS-supplied URL.
|
|
1874
|
+
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) return
|
|
1875
|
+
WebViewCompat.removeWebMessageListener(view.webView, "ReactNativeWebView")
|
|
1876
|
+
if (!value) return
|
|
1877
|
+
WebViewCompat.addWebMessageListener(view.webView, "ReactNativeWebView", setOf("https://" + HOST)) {
|
|
1878
|
+
webView, message, sourceOrigin, isMainFrame, _ ->
|
|
1879
|
+
if (isMainFrame && sourceOrigin.scheme == "https" && sourceOrigin.host == HOST &&
|
|
1880
|
+
message.type == WebMessageCompat.TYPE_STRING) {
|
|
1881
|
+
val url = webView.url
|
|
1882
|
+
val data = message.data
|
|
1883
|
+
if (url != null && data != null) view.webView.onMessage(data, url)
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
override fun setInjectedJavaScriptBeforeContentLoaded(view: RNCWebViewWrapper, value: String?) {
|
|
1888
|
+
scripts.remove(view)?.remove()
|
|
1889
|
+
if (value == null) return
|
|
1890
|
+
// onPageStarted injection races module execution on Android. Register with
|
|
1891
|
+
// Chromium before navigation, scoped to our virtual asset origin only.
|
|
1892
|
+
check(WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
|
|
1893
|
+
"AbsoluteJS requires an Android System WebView with document-start script support."
|
|
1894
|
+
}
|
|
1895
|
+
scripts[view] = WebViewCompat.addDocumentStartJavaScript(view.webView, value, setOf("https://" + HOST))
|
|
1896
|
+
}
|
|
1897
|
+
override fun onDropViewInstance(view: RNCWebViewWrapper) {
|
|
1898
|
+
scripts.remove(view)?.remove()
|
|
1899
|
+
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER))
|
|
1900
|
+
WebViewCompat.removeWebMessageListener(view.webView, "ReactNativeWebView")
|
|
1901
|
+
super.onDropViewInstance(view)
|
|
1902
|
+
}
|
|
1903
|
+
override fun addEventEmitters(context: ThemedReactContext, view: RNCWebViewWrapper) {
|
|
1904
|
+
Log.i("AbsoluteJS", "Expo embedded web phase: asset-host-view-created")
|
|
1905
|
+
view.webView.webViewClient = object : RNCWebViewClient() {
|
|
1906
|
+
@Volatile private var activeBundle: String? = null
|
|
1907
|
+
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
|
|
1908
|
+
Log.i("AbsoluteJS", "Expo embedded web phase: document-started")
|
|
1909
|
+
val uri = Uri.parse(url)
|
|
1910
|
+
activeBundle = if (uri.scheme == "https" && uri.host == HOST && uri.path.orEmpty().startsWith(PREFIX))
|
|
1911
|
+
uri.path!!.removePrefix(PREFIX).substringBefore('/') else null
|
|
1912
|
+
super.onPageStarted(view, url, favicon)
|
|
1913
|
+
}
|
|
1914
|
+
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? =
|
|
1915
|
+
AbsoluteEmbeddedAssets.intercept(request, activeBundle) ?: super.shouldInterceptRequest(view, request)
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
class AbsoluteEmbeddedWebViewPackage : ReactPackage {
|
|
1921
|
+
override fun createNativeModules(context: ReactApplicationContext): List<NativeModule> = emptyList()
|
|
1922
|
+
override fun createViewManagers(context: ReactApplicationContext): List<ViewManager<*, *>> = listOf(AbsoluteEmbeddedWebViewManager())
|
|
1923
|
+
}
|
|
1924
|
+
`;
|
|
1925
|
+
var plugin = `${header}const { withMainApplication } = require('expo/config-plugins');
|
|
1926
|
+
module.exports = config => withMainApplication(config, value => {
|
|
1927
|
+
if (value.modResults.language !== 'kt') throw new Error('AbsoluteJS embedded assets require the managed Kotlin application.');
|
|
1928
|
+
const name = 'expo.modules.absoluteembeddedassets.AbsoluteEmbeddedWebViewPackage';
|
|
1929
|
+
const registration = 'add(' + name + '())';
|
|
1930
|
+
const source = value.modResults.contents;
|
|
1931
|
+
if (!source.includes(registration)) {
|
|
1932
|
+
const anchor = 'PackageList(this).packages.apply {';
|
|
1933
|
+
if (source.split(anchor).length !== 2) throw new Error('Cannot safely register AbsoluteJS embedded WebView in MainApplication.');
|
|
1934
|
+
value.modResults.contents = source.replace(anchor, anchor + '\\n ' + registration);
|
|
1935
|
+
}
|
|
1936
|
+
return value;
|
|
1937
|
+
});
|
|
1938
|
+
`;
|
|
1939
|
+
var runtime2 = `${header}import { requireNativeModule } from 'expo-modules-core';
|
|
1940
|
+
import { Platform, requireNativeComponent } from 'react-native';
|
|
1941
|
+
|
|
1942
|
+
export const absoluteEmbeddedNativeConfig = Platform.OS === 'android'
|
|
1943
|
+
? { component: requireNativeComponent('AbsoluteEmbeddedWebView') }
|
|
1944
|
+
: undefined;
|
|
1945
|
+
|
|
1946
|
+
export const registerAbsoluteEmbeddedBundle = async (rootUri: string, paths: string[], indexUri: string): Promise<string> => {
|
|
1947
|
+
if (Platform.OS !== 'android') return indexUri;
|
|
1948
|
+
const native = requireNativeModule<{ registerBundle(root: string, paths: string[]): Promise<string> }>('AbsoluteEmbeddedAssets');
|
|
1949
|
+
return native.registerBundle(rootUri, paths);
|
|
1950
|
+
};
|
|
1951
|
+
`;
|
|
1952
|
+
var absoluteExpoEmbeddedAssetsFiles = (project) => {
|
|
1953
|
+
const root = join6(project, "modules/absolute-embedded-assets");
|
|
1954
|
+
return new Map([
|
|
1955
|
+
[
|
|
1956
|
+
join6(root, "expo-module.config.json"),
|
|
1957
|
+
`${JSON.stringify({
|
|
1958
|
+
android: {
|
|
1959
|
+
modules: [
|
|
1960
|
+
"expo.modules.absoluteembeddedassets.AbsoluteEmbeddedAssetsModule"
|
|
1961
|
+
]
|
|
1962
|
+
},
|
|
1963
|
+
platforms: ["android"]
|
|
1964
|
+
}, null, 2)}
|
|
1965
|
+
`
|
|
1966
|
+
],
|
|
1967
|
+
[
|
|
1968
|
+
join6(root, "android/build.gradle"),
|
|
1969
|
+
`${header}plugins { id 'com.android.library'; id 'expo-module-gradle-plugin' }
|
|
1970
|
+
group = 'expo.modules.absoluteembeddedassets'
|
|
1971
|
+
version = '0.0.1'
|
|
1972
|
+
android {
|
|
1973
|
+
namespace 'expo.modules.absoluteembeddedassets'
|
|
1974
|
+
defaultConfig { versionCode 1; versionName '0.0.1' }
|
|
1975
|
+
}
|
|
1976
|
+
dependencies {
|
|
1977
|
+
implementation 'com.facebook.react:react-android'
|
|
1978
|
+
implementation project(':react-native-webview')
|
|
1979
|
+
implementation 'androidx.webkit:webkit:1.14.0'
|
|
1980
|
+
}
|
|
1981
|
+
`
|
|
1982
|
+
],
|
|
1983
|
+
[
|
|
1984
|
+
join6(root, "android/src/main/AndroidManifest.xml"),
|
|
1985
|
+
`<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
1986
|
+
`
|
|
1987
|
+
],
|
|
1988
|
+
[
|
|
1989
|
+
join6(root, "android/src/main/java/expo/modules/absoluteembeddedassets/AbsoluteEmbeddedAssetsModule.kt"),
|
|
1990
|
+
android
|
|
1991
|
+
],
|
|
1992
|
+
[join6(project, "plugins/withAbsoluteEmbeddedAssets.js"), plugin],
|
|
1993
|
+
[join6(project, "src/generated/AbsoluteEmbeddedAssets.ts"), runtime2]
|
|
1994
|
+
]);
|
|
1995
|
+
};
|
|
1996
|
+
|
|
1674
1997
|
// src/mobile/branding.ts
|
|
1675
1998
|
import { createHash as createHash2 } from "crypto";
|
|
1676
1999
|
import {
|
|
1677
2000
|
access,
|
|
1678
2001
|
copyFile,
|
|
1679
2002
|
mkdir,
|
|
1680
|
-
readFile,
|
|
2003
|
+
readFile as readFile2,
|
|
1681
2004
|
rename,
|
|
1682
2005
|
rm,
|
|
1683
2006
|
writeFile
|
|
1684
2007
|
} from "fs/promises";
|
|
1685
|
-
import { dirname as
|
|
2008
|
+
import { dirname as dirname3, join as join7, relative as relative2, resolve as resolve4 } from "path";
|
|
1686
2009
|
var ABSOLUTE_MOBILE_BRANDING_FORMAT = 1;
|
|
1687
2010
|
var ABSOLUTE_CAPACITOR_ASSETS_VERSION = "3.0.5";
|
|
1688
2011
|
var ABSOLUTE_CAPACITOR_ASSETS_SPEC = `@capacitor/assets@${ABSOLUTE_CAPACITOR_ASSETS_VERSION}`;
|
|
@@ -1706,7 +2029,7 @@ var exists = async (path) => {
|
|
|
1706
2029
|
}
|
|
1707
2030
|
};
|
|
1708
2031
|
var portableRelative = (root, path) => relative2(root, path).replaceAll("\\", "/");
|
|
1709
|
-
var absoluteMobileBrandingManifestPath = (projectRoot) =>
|
|
2032
|
+
var absoluteMobileBrandingManifestPath = (projectRoot) => join7(resolve4(projectRoot), ".absolutejs", "mobile", "branding", "manifest.json");
|
|
1710
2033
|
var brandingEntries = (branding) => [
|
|
1711
2034
|
{ kind: "icon", path: branding.icon, size: ICON_SIZE },
|
|
1712
2035
|
branding.android.foreground ? {
|
|
@@ -1746,7 +2069,7 @@ var brandingEntries = (branding) => [
|
|
|
1746
2069
|
} : undefined
|
|
1747
2070
|
].filter((entry) => entry !== undefined);
|
|
1748
2071
|
var inspectSource = async (projectRoot, entry) => {
|
|
1749
|
-
const buffer = await
|
|
2072
|
+
const buffer = await readFile2(entry.path);
|
|
1750
2073
|
if (buffer.byteLength > MAX_SOURCE_BYTES)
|
|
1751
2074
|
throw new TypeError(`mobile.branding ${entry.kind} exceeds the 32 MiB source limit.`);
|
|
1752
2075
|
let metadata;
|
|
@@ -1799,7 +2122,7 @@ var createAbsoluteMobileBrandingManifest = async (config, projectRoot) => {
|
|
|
1799
2122
|
};
|
|
1800
2123
|
var writeManifest = async (projectRoot, manifest) => {
|
|
1801
2124
|
const destination = absoluteMobileBrandingManifestPath(projectRoot);
|
|
1802
|
-
await mkdir(
|
|
2125
|
+
await mkdir(dirname3(destination), { recursive: true });
|
|
1803
2126
|
const temporary = `${destination}.${crypto.randomUUID()}.tmp`;
|
|
1804
2127
|
await writeFile(temporary, `${JSON.stringify(manifest, null, 2)}
|
|
1805
2128
|
`, {
|
|
@@ -1809,7 +2132,7 @@ var writeManifest = async (projectRoot, manifest) => {
|
|
|
1809
2132
|
return destination;
|
|
1810
2133
|
};
|
|
1811
2134
|
var copyManaged = async (source, destination) => {
|
|
1812
|
-
await mkdir(
|
|
2135
|
+
await mkdir(dirname3(destination), { recursive: true });
|
|
1813
2136
|
await copyFile(source, destination);
|
|
1814
2137
|
};
|
|
1815
2138
|
var absoluteExpoBrandingConfig = (config) => {
|
|
@@ -1853,10 +2176,10 @@ var writeAbsoluteExpoBrandingInputs = async (config, projectRoot) => {
|
|
|
1853
2176
|
if (!branding)
|
|
1854
2177
|
return;
|
|
1855
2178
|
const manifest = await createAbsoluteMobileBrandingManifest(config, projectRoot);
|
|
1856
|
-
const destination =
|
|
2179
|
+
const destination = join7(config.nativeProjectDirectory, "assets", "absolute-branding");
|
|
1857
2180
|
await rm(destination, { force: true, recursive: true });
|
|
1858
2181
|
await mkdir(destination, { recursive: true });
|
|
1859
|
-
await copyManaged(branding.icon,
|
|
2182
|
+
await copyManaged(branding.icon, join7(destination, "icon.png"));
|
|
1860
2183
|
const copies = [
|
|
1861
2184
|
[branding.android.backgroundImage, "android-background.png"],
|
|
1862
2185
|
[branding.android.foreground, "android-foreground.png"],
|
|
@@ -1866,19 +2189,19 @@ var writeAbsoluteExpoBrandingInputs = async (config, projectRoot) => {
|
|
|
1866
2189
|
[branding.splash.image, "splash-light.png"],
|
|
1867
2190
|
[branding.splash.darkImage, "splash-dark.png"]
|
|
1868
2191
|
];
|
|
1869
|
-
await Promise.all(copies.map(([source, name]) => source ? copyManaged(source,
|
|
2192
|
+
await Promise.all(copies.map(([source, name]) => source ? copyManaged(source, join7(destination, name)) : undefined));
|
|
1870
2193
|
await writeManifest(projectRoot, manifest);
|
|
1871
2194
|
return { manifest, path: destination };
|
|
1872
2195
|
};
|
|
1873
2196
|
var stageCapacitorInputs = async (branding, projectRoot) => {
|
|
1874
|
-
const destination =
|
|
2197
|
+
const destination = join7(projectRoot, ".absolutejs", "mobile", "branding", "input");
|
|
1875
2198
|
await rm(destination, { force: true, recursive: true });
|
|
1876
2199
|
await mkdir(destination, { recursive: true });
|
|
1877
|
-
await copyManaged(branding.icon,
|
|
2200
|
+
await copyManaged(branding.icon, join7(destination, "logo.png"));
|
|
1878
2201
|
if (branding.splash.image)
|
|
1879
|
-
await copyManaged(branding.splash.image,
|
|
2202
|
+
await copyManaged(branding.splash.image, join7(destination, "splash.png"));
|
|
1880
2203
|
if (branding.splash.darkImage)
|
|
1881
|
-
await copyManaged(branding.splash.darkImage,
|
|
2204
|
+
await copyManaged(branding.splash.darkImage, join7(destination, "splash-dark.png"));
|
|
1882
2205
|
return destination;
|
|
1883
2206
|
};
|
|
1884
2207
|
var defaultRunner = async (command, options) => {
|
|
@@ -1904,21 +2227,21 @@ ${stderr}`))
|
|
|
1904
2227
|
throw new TypeError("Capacitor asset generation reported an incomplete projection.");
|
|
1905
2228
|
};
|
|
1906
2229
|
var resizePng = async (source, destination, size) => {
|
|
1907
|
-
const output = await new Bun.Image(await
|
|
1908
|
-
await mkdir(
|
|
2230
|
+
const output = await new Bun.Image(await readFile2(source)).resize(size, size).png({ compressionLevel: 9 }).toBuffer();
|
|
2231
|
+
await mkdir(dirname3(destination), { recursive: true });
|
|
1909
2232
|
await writeFile(destination, output);
|
|
1910
2233
|
};
|
|
1911
2234
|
var applyAndroidLayers = async (branding, nativeDirectory) => {
|
|
1912
|
-
const resources =
|
|
2235
|
+
const resources = join7(nativeDirectory, "android", "app", "src", "main", "res");
|
|
1913
2236
|
await Promise.all(ANDROID_ADAPTIVE_SIZES.flatMap(([density, size]) => [
|
|
1914
2237
|
...branding.android.foreground ? [
|
|
1915
|
-
resizePng(branding.android.foreground,
|
|
2238
|
+
resizePng(branding.android.foreground, join7(resources, `mipmap-${density}`, "ic_launcher_foreground.png"), size)
|
|
1916
2239
|
] : [],
|
|
1917
2240
|
...branding.android.backgroundImage ? [
|
|
1918
|
-
resizePng(branding.android.backgroundImage,
|
|
2241
|
+
resizePng(branding.android.backgroundImage, join7(resources, `mipmap-${density}`, "ic_launcher_background.png"), size)
|
|
1919
2242
|
] : [],
|
|
1920
2243
|
...branding.android.monochrome ? [
|
|
1921
|
-
resizePng(branding.android.monochrome,
|
|
2244
|
+
resizePng(branding.android.monochrome, join7(resources, `mipmap-${density}`, "ic_launcher_monochrome.png"), size)
|
|
1922
2245
|
] : []
|
|
1923
2246
|
]));
|
|
1924
2247
|
if (!branding.android.monochrome)
|
|
@@ -1930,14 +2253,14 @@ var applyAndroidLayers = async (branding, nativeDirectory) => {
|
|
|
1930
2253
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
|
1931
2254
|
</adaptive-icon>
|
|
1932
2255
|
`;
|
|
1933
|
-
await Promise.all(["ic_launcher.xml", "ic_launcher_round.xml"].map((name) => writeFile(
|
|
2256
|
+
await Promise.all(["ic_launcher.xml", "ic_launcher_round.xml"].map((name) => writeFile(join7(resources, "mipmap-anydpi-v26", name), iconXml)));
|
|
1934
2257
|
};
|
|
1935
2258
|
var applyIosAppearances = async (branding, nativeDirectory) => {
|
|
1936
2259
|
if (!branding.ios.darkIcon && !branding.ios.tintedIcon)
|
|
1937
2260
|
return;
|
|
1938
|
-
const assetSet =
|
|
1939
|
-
const contentsPath =
|
|
1940
|
-
const contents = JSON.parse(await
|
|
2261
|
+
const assetSet = join7(nativeDirectory, "ios", "App", "App", "Assets.xcassets", "AppIcon.appiconset");
|
|
2262
|
+
const contentsPath = join7(assetSet, "Contents.json");
|
|
2263
|
+
const contents = JSON.parse(await readFile2(contentsPath, "utf8"));
|
|
1941
2264
|
const images = typeof contents === "object" && contents !== null ? Reflect.get(contents, "images") : undefined;
|
|
1942
2265
|
if (typeof contents !== "object" || contents === null || !Array.isArray(images))
|
|
1943
2266
|
throw new TypeError("Generated iOS AppIcon Contents.json is invalid.");
|
|
@@ -1949,7 +2272,7 @@ var applyIosAppearances = async (branding, nativeDirectory) => {
|
|
|
1949
2272
|
if (!source)
|
|
1950
2273
|
return;
|
|
1951
2274
|
const filename = `AppIcon-1024-${appearance}.png`;
|
|
1952
|
-
await resizePng(source,
|
|
2275
|
+
await resizePng(source, join7(assetSet, filename), ICON_SIZE);
|
|
1953
2276
|
return {
|
|
1954
2277
|
appearances: [{ appearance: "luminosity", value: appearance }],
|
|
1955
2278
|
filename,
|
|
@@ -1971,7 +2294,7 @@ var applyIosAppearances = async (branding, nativeDirectory) => {
|
|
|
1971
2294
|
await writeFile(contentsPath, `${JSON.stringify(contents, null, 2)}
|
|
1972
2295
|
`);
|
|
1973
2296
|
};
|
|
1974
|
-
var nativeBrandingSentinels = (config, platforms) => platforms.map((platform) => platform === "android" ?
|
|
2297
|
+
var nativeBrandingSentinels = (config, platforms) => platforms.map((platform) => platform === "android" ? join7(config.nativeProjectDirectory, "android", "app", "src", "main", "res", "mipmap-xxxhdpi", "ic_launcher.png") : join7(config.nativeProjectDirectory, "ios", "App", "App", "Assets.xcassets", "AppIcon.appiconset", "AppIcon-512@2x.png"));
|
|
1975
2298
|
var requireGeneratedBranding = async (config, platforms) => {
|
|
1976
2299
|
const missing = (await Promise.all(nativeBrandingSentinels(config, platforms).map(async (path) => ({
|
|
1977
2300
|
path,
|
|
@@ -1991,7 +2314,7 @@ var reusableManifestPlatforms = (value, fingerprint) => {
|
|
|
1991
2314
|
var completedManifestPlatforms = async (manifest, manifestPath, generated) => {
|
|
1992
2315
|
let previousPlatforms = [];
|
|
1993
2316
|
try {
|
|
1994
|
-
const previous = JSON.parse(await
|
|
2317
|
+
const previous = JSON.parse(await readFile2(manifestPath, "utf8"));
|
|
1995
2318
|
previousPlatforms = reusableManifestPlatforms(previous, manifest.fingerprint);
|
|
1996
2319
|
} catch {}
|
|
1997
2320
|
const completed = new Set([...generated, ...previousPlatforms]);
|
|
@@ -2016,9 +2339,9 @@ var generateAbsoluteCapacitorBranding = async (config, projectRoot, options = {}
|
|
|
2016
2339
|
"--assetPath",
|
|
2017
2340
|
portableRelative(projectRoot, input),
|
|
2018
2341
|
"--androidProject",
|
|
2019
|
-
portableRelative(projectRoot,
|
|
2342
|
+
portableRelative(projectRoot, join7(config.nativeProjectDirectory, "android")),
|
|
2020
2343
|
"--iosProject",
|
|
2021
|
-
portableRelative(projectRoot,
|
|
2344
|
+
portableRelative(projectRoot, join7(config.nativeProjectDirectory, "ios", "App")),
|
|
2022
2345
|
"--iconBackgroundColor",
|
|
2023
2346
|
branding.android.backgroundColor,
|
|
2024
2347
|
"--splashBackgroundColor",
|
|
@@ -2080,7 +2403,7 @@ var inspectAbsoluteMobileBranding = async (config, projectRoot) => {
|
|
|
2080
2403
|
});
|
|
2081
2404
|
}
|
|
2082
2405
|
try {
|
|
2083
|
-
const current = parseManifestState(JSON.parse(await
|
|
2406
|
+
const current = parseManifestState(JSON.parse(await readFile2(manifestPath, "utf8")));
|
|
2084
2407
|
if (current.fingerprint !== expected.fingerprint)
|
|
2085
2408
|
return brandingInspection({
|
|
2086
2409
|
configured: true,
|
|
@@ -2099,7 +2422,7 @@ var inspectAbsoluteMobileBranding = async (config, projectRoot) => {
|
|
|
2099
2422
|
status: "stale"
|
|
2100
2423
|
});
|
|
2101
2424
|
const nativeSentinels = config.engine === "expo" ? [
|
|
2102
|
-
|
|
2425
|
+
join7(config.nativeProjectDirectory, "assets", "absolute-branding", "icon.png")
|
|
2103
2426
|
] : nativeBrandingSentinels(config, config.platforms);
|
|
2104
2427
|
if ((await Promise.all(nativeSentinels.map((path) => exists(path)))).some((present) => !present))
|
|
2105
2428
|
return brandingInspection({
|
|
@@ -2132,8 +2455,8 @@ var renderAbsoluteMobileBrandingPreview = async (config, projectRoot) => {
|
|
|
2132
2455
|
const { branding } = config;
|
|
2133
2456
|
if (!branding)
|
|
2134
2457
|
throw new TypeError("mobile.branding is not configured.");
|
|
2135
|
-
const destination =
|
|
2136
|
-
const icon = `data:image/png;base64,${(await
|
|
2458
|
+
const destination = join7(projectRoot, ".absolutejs", "mobile", "branding", "preview.html");
|
|
2459
|
+
const icon = `data:image/png;base64,${(await readFile2(branding.icon)).toString("base64")}`;
|
|
2137
2460
|
const appName = escapeHtml(config.appName);
|
|
2138
2461
|
const html = `<!doctype html>
|
|
2139
2462
|
<meta charset="utf-8">
|
|
@@ -2142,7 +2465,7 @@ var renderAbsoluteMobileBrandingPreview = async (config, projectRoot) => {
|
|
|
2142
2465
|
<style>body{font:16px system-ui;margin:2rem;background:#eee;color:#111}main{display:flex;flex-wrap:wrap;gap:2rem}.card{background:white;border-radius:20px;padding:1.5rem;box-shadow:0 8px 30px #0002}.icon{width:192px;height:192px;object-fit:cover}.circle{border-radius:50%}.squircle{border-radius:24%}.splash{width:240px;height:480px;display:grid;place-items:center;background:${branding.splash.backgroundColor}}.splash img{width:${branding.splash.logoScale * 100}%}code{display:block;margin-top:1rem}</style>
|
|
2143
2466
|
<h1>${appName}</h1><main><section class="card"><img class="icon squircle" src="${icon}" alt="Square app icon preview"><code>Apple / legacy Android</code></section><section class="card"><img class="icon circle" src="${icon}" alt="Circular Android icon preview"><code>Android circular mask</code></section><section class="card splash"><img src="${icon}" alt="Launch screen preview"></section></main><p>Fingerprint: <code>${manifest.fingerprint}</code></p>
|
|
2144
2467
|
`;
|
|
2145
|
-
await mkdir(
|
|
2468
|
+
await mkdir(dirname3(destination), { recursive: true });
|
|
2146
2469
|
await writeFile(destination, html);
|
|
2147
2470
|
return destination;
|
|
2148
2471
|
};
|
|
@@ -2227,15 +2550,15 @@ var routeSegments = (route) => {
|
|
|
2227
2550
|
return segment;
|
|
2228
2551
|
});
|
|
2229
2552
|
};
|
|
2230
|
-
var routeFile = (project, route) =>
|
|
2553
|
+
var routeFile = (project, route) => join8(project, "app", ...routeSegments(route), "index.tsx");
|
|
2231
2554
|
var packageDependencies = (plan) => Object.fromEntries(plan.requiredPackages.map((spec) => {
|
|
2232
2555
|
const separator = spec.lastIndexOf("@");
|
|
2233
2556
|
return [spec.slice(0, separator), spec.slice(separator + 1)];
|
|
2234
2557
|
}));
|
|
2235
2558
|
var expoPackage = (auth, branding, sync, devices, updates) => ({
|
|
2236
2559
|
dependencies: {
|
|
2237
|
-
"@absolutejs/devices":
|
|
2238
|
-
"@absolutejs/devices-expo":
|
|
2560
|
+
"@absolutejs/devices": ABSOLUTE_DEVICES_VERSION,
|
|
2561
|
+
"@absolutejs/devices-expo": ABSOLUTE_EXPO_DEVICES_VERSION,
|
|
2239
2562
|
...auth ? {
|
|
2240
2563
|
"@absolutejs/auth": ABSOLUTE_EXPO_AUTH_CORE_VERSION,
|
|
2241
2564
|
[ABSOLUTE_EXPO_AUTH_PACKAGE]: ABSOLUTE_EXPO_AUTH_VERSION
|
|
@@ -2316,24 +2639,24 @@ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
|
|
|
2316
2639
|
const devicePlugins = [
|
|
2317
2640
|
...new Set(devices.capabilities.flatMap((name) => devices.providers[name]?.plugins ?? []))
|
|
2318
2641
|
];
|
|
2319
|
-
const configuredPlugins = devicePlugins.map((
|
|
2320
|
-
if (
|
|
2642
|
+
const configuredPlugins = devicePlugins.map((plugin2) => {
|
|
2643
|
+
if (plugin2 === "expo-image-picker")
|
|
2321
2644
|
return [
|
|
2322
|
-
|
|
2645
|
+
plugin2,
|
|
2323
2646
|
{
|
|
2324
2647
|
cameraPermission: descriptions.NSCameraUsageDescription,
|
|
2325
2648
|
microphonePermission: false,
|
|
2326
2649
|
photosPermission: descriptions.NSPhotoLibraryUsageDescription
|
|
2327
2650
|
}
|
|
2328
2651
|
];
|
|
2329
|
-
if (
|
|
2652
|
+
if (plugin2 === "expo-location")
|
|
2330
2653
|
return [
|
|
2331
|
-
|
|
2654
|
+
plugin2,
|
|
2332
2655
|
{
|
|
2333
2656
|
locationWhenInUsePermission: descriptions.NSLocationWhenInUseUsageDescription
|
|
2334
2657
|
}
|
|
2335
2658
|
];
|
|
2336
|
-
return
|
|
2659
|
+
return plugin2;
|
|
2337
2660
|
});
|
|
2338
2661
|
return {
|
|
2339
2662
|
expo: {
|
|
@@ -2355,7 +2678,7 @@ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
|
|
|
2355
2678
|
package: config.appId,
|
|
2356
2679
|
permissions: requirements.androidPermissions
|
|
2357
2680
|
},
|
|
2358
|
-
experiments: { typedRoutes: true },
|
|
2681
|
+
experiments: { tsconfigPaths: false, typedRoutes: true },
|
|
2359
2682
|
ios: {
|
|
2360
2683
|
...branding?.ios ?? {},
|
|
2361
2684
|
associatedDomains: config.deepLinkHosts.map((host2) => `applinks:${host2}`),
|
|
@@ -2378,6 +2701,7 @@ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
|
|
|
2378
2701
|
["expo-dev-client", { launchMode: "most-recent" }],
|
|
2379
2702
|
...branding ? [branding.splashPlugin] : [],
|
|
2380
2703
|
"./plugins/withAbsoluteActivityResultRecovery",
|
|
2704
|
+
"./plugins/withAbsoluteEmbeddedAssets",
|
|
2381
2705
|
...auth || config.updates ? ["expo-secure-store"] : [],
|
|
2382
2706
|
...sync ? [
|
|
2383
2707
|
"expo-sqlite",
|
|
@@ -2835,7 +3159,7 @@ import {
|
|
|
2835
3159
|
installExpoSyncLifecycle,
|
|
2836
3160
|
registerExpoSyncBackgroundTask
|
|
2837
3161
|
} from '@absolutejs/sync-expo';
|
|
2838
|
-
import
|
|
3162
|
+
import { reloadAppAsync } from 'expo';
|
|
2839
3163
|
import { absoluteExpoAuth, startAbsoluteExpoAuth } from './AbsoluteAuth';
|
|
2840
3164
|
|
|
2841
3165
|
const BACKGROUND_TASK = ${JSON.stringify(backgroundTask)};
|
|
@@ -2894,37 +3218,98 @@ absoluteExpoAuth.onPrincipalChange(principal => {
|
|
|
2894
3218
|
if (!started || principal?.namespace === activeNamespace) return;
|
|
2895
3219
|
const previousNamespace = activeNamespace;
|
|
2896
3220
|
installRuntimeTransport(principal);
|
|
2897
|
-
if (previousNamespace !== undefined) void
|
|
3221
|
+
if (previousNamespace !== undefined) void reloadAppAsync('absolutejs-auth-principal-changed').catch(() => {
|
|
3222
|
+
console.warn('AbsoluteJS could not reset the account runtime. Restart the app before continuing.');
|
|
3223
|
+
});
|
|
2898
3224
|
});
|
|
2899
3225
|
|
|
2900
3226
|
export const createAbsoluteExpoSyncBridge = async (
|
|
2901
3227
|
emit: (event: 'sync.socket' | 'sync.wake', payload: Record<string, unknown>) => void
|
|
2902
3228
|
) => {
|
|
2903
3229
|
await startAbsoluteExpoSync();
|
|
2904
|
-
const
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
3230
|
+
const bind = (namespace: string, generation: number) => {
|
|
3231
|
+
const emitCurrent = (event: 'sync.socket' | 'sync.wake', payload: Record<string, unknown>) => {
|
|
3232
|
+
if (!closed && generation === revision) emit(event, payload);
|
|
3233
|
+
};
|
|
3234
|
+
const transactions = createExpoSyncBridgeHost({ createId: expoSyncRandomId, namespace, store });
|
|
3235
|
+
const sockets = createExpoSyncSocketBridgeHost({
|
|
3236
|
+
allowedOrigin: PRODUCTION_ORIGIN,
|
|
3237
|
+
emit: payload => emitCurrent('sync.socket', payload),
|
|
3238
|
+
socketTicket: async audience => {
|
|
3239
|
+
if (closed || generation !== revision) throw new Error('Expo Sync principal changed.');
|
|
3240
|
+
const ticket = await absoluteExpoAuth.socketTicket(audience);
|
|
3241
|
+
if (closed || generation !== revision) throw new Error('Expo Sync principal changed.');
|
|
3242
|
+
return ticket;
|
|
3243
|
+
}
|
|
3244
|
+
});
|
|
3245
|
+
const removeLifecycle = installExpoSyncLifecycle({
|
|
3246
|
+
client: { reconnect: () => emitCurrent('sync.wake', {}) }
|
|
3247
|
+
});
|
|
3248
|
+
return {
|
|
3249
|
+
close: async () => {
|
|
3250
|
+
removeLifecycle();
|
|
3251
|
+
await Promise.all([transactions.close(), sockets.close()]);
|
|
3252
|
+
},
|
|
3253
|
+
request: (method: string, params: Record<string, unknown>) =>
|
|
3254
|
+
method.startsWith('sync.socket.') ? sockets.request(method, params) : transactions.request(method, params)
|
|
3255
|
+
};
|
|
2908
3256
|
};
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
const
|
|
2916
|
-
|
|
2917
|
-
|
|
3257
|
+
let closed = false;
|
|
3258
|
+
let revoked = false;
|
|
3259
|
+
let boundNamespace: string | undefined;
|
|
3260
|
+
let revision = 0;
|
|
3261
|
+
let bridge: ReturnType<typeof bind> | undefined;
|
|
3262
|
+
let ready = Promise.resolve();
|
|
3263
|
+
const replace = (principal: Awaited<ReturnType<typeof absoluteExpoAuth.principal>>) => {
|
|
3264
|
+
if (closed) return;
|
|
3265
|
+
// A mounted page may bind its first login, but must never inherit another
|
|
3266
|
+
// account's authority. Account changes require the existing runtime reload.
|
|
3267
|
+
if (boundNamespace !== undefined && principal?.namespace !== boundNamespace) revoked = true;
|
|
3268
|
+
const generation = ++revision;
|
|
3269
|
+
const previous = bridge;
|
|
3270
|
+
bridge = undefined;
|
|
3271
|
+
ready = ready.then(async () => {
|
|
3272
|
+
await previous?.close();
|
|
3273
|
+
if (!closed && !revoked && generation === revision && principal) {
|
|
3274
|
+
boundNamespace = principal.namespace;
|
|
3275
|
+
bridge = bind(principal.namespace, generation);
|
|
3276
|
+
}
|
|
3277
|
+
});
|
|
3278
|
+
// Keep background transitions handled; requests still observe setup failures.
|
|
3279
|
+
void ready.catch(() => undefined);
|
|
3280
|
+
};
|
|
3281
|
+
const stop = absoluteExpoAuth.onPrincipalChange(replace);
|
|
3282
|
+
try {
|
|
3283
|
+
const initialRevision = revision;
|
|
3284
|
+
const principal = await absoluteExpoAuth.principal();
|
|
3285
|
+
if (revision === initialRevision) replace(principal);
|
|
3286
|
+
await ready;
|
|
3287
|
+
} catch (error) {
|
|
3288
|
+
stop();
|
|
3289
|
+
closed = true;
|
|
3290
|
+
await bridge?.close();
|
|
3291
|
+
throw error;
|
|
3292
|
+
}
|
|
2918
3293
|
|
|
2919
3294
|
return {
|
|
2920
3295
|
close: async () => {
|
|
2921
|
-
|
|
2922
|
-
|
|
3296
|
+
if (closed) return;
|
|
3297
|
+
closed = true;
|
|
3298
|
+
++revision;
|
|
3299
|
+
stop();
|
|
3300
|
+
const previous = bridge;
|
|
3301
|
+
bridge = undefined;
|
|
3302
|
+
await Promise.all([ready, previous?.close()]);
|
|
2923
3303
|
},
|
|
2924
|
-
request: (method: string, params: Record<string, unknown>) =>
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
3304
|
+
request: async (method: string, params: Record<string, unknown>) => {
|
|
3305
|
+
const generation = revision;
|
|
3306
|
+
await ready;
|
|
3307
|
+
if (closed || revoked || generation !== revision) throw new Error('Expo Sync principal changed.');
|
|
3308
|
+
if (!bridge) throw new Error('Expo Sync requires an authenticated principal.');
|
|
3309
|
+
const result = await bridge.request(method, params);
|
|
3310
|
+
if (closed || revoked || generation !== revision) throw new Error('Expo Sync principal changed.');
|
|
3311
|
+
return result;
|
|
3312
|
+
}
|
|
2928
3313
|
};
|
|
2929
3314
|
};
|
|
2930
3315
|
`;
|
|
@@ -2942,6 +3327,7 @@ import { ActivityIndicator, BackHandler, Platform, Pressable, StyleSheet, Text,
|
|
|
2942
3327
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
2943
3328
|
import { WebView, type WebViewMessageEvent } from 'react-native-webview';
|
|
2944
3329
|
import { materializeAbsoluteWebBundle } from './webAssets';
|
|
3330
|
+
import { absoluteEmbeddedNativeConfig } from './AbsoluteEmbeddedAssets';
|
|
2945
3331
|
import { markAbsoluteEmbeddedWebPhase, markAbsoluteEmbeddedWebReady } from './AbsoluteActivityResultRecovery';
|
|
2946
3332
|
import { createExpoDevicesBridgeHost } from '@absolutejs/devices-expo/bridge';
|
|
2947
3333
|
import { absoluteExpoDevices, beforeAbsoluteExpoDeviceSignOut } from './AbsoluteDevices';
|
|
@@ -3061,7 +3447,16 @@ const bridgeBootstrap = (path: string, safeAreaInsets: { bottom: number; left: n
|
|
|
3061
3447
|
event.preventDefault();
|
|
3062
3448
|
send({ format: 3, kind: 'event', event: 'navigation', path: url.pathname + url.search + url.hash });
|
|
3063
3449
|
}, true);
|
|
3064
|
-
|
|
3450
|
+
let shellRendered = false;
|
|
3451
|
+
addEventListener('absolute:shell-rendered', () => {
|
|
3452
|
+
shellRendered = true;
|
|
3453
|
+
send({ format: 3, kind: 'event', event: 'shell-rendered', path: currentPath });
|
|
3454
|
+
});
|
|
3455
|
+
const startupFailed = () => {
|
|
3456
|
+
if (!shellRendered) send({ format: 3, kind: 'event', event: 'startup-failed', path: currentPath });
|
|
3457
|
+
};
|
|
3458
|
+
addEventListener('error', startupFailed);
|
|
3459
|
+
addEventListener('unhandledrejection', startupFailed);
|
|
3065
3460
|
})(); true;\`;
|
|
3066
3461
|
};
|
|
3067
3462
|
|
|
@@ -3106,11 +3501,18 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3106
3501
|
const canGoBack = useRef(false);
|
|
3107
3502
|
const [runtimeReady, setRuntimeReady] = useState(!AUTH_ENABLED && !SYNC_ENABLED);
|
|
3108
3503
|
const [devicesReady, setDevicesReady] = useState(false);
|
|
3109
|
-
const [startupError, setStartupError] = useState<'assets' | 'devices'>();
|
|
3504
|
+
const [startupError, setStartupError] = useState<'assets' | 'devices' | 'content'>();
|
|
3110
3505
|
const [startupRevision, setStartupRevision] = useState(0);
|
|
3111
3506
|
const activeWebPath = useRef(pathname);
|
|
3112
3507
|
const embeddedReadyReported = useRef(false);
|
|
3113
3508
|
const webSource = useMemo(() => indexUri ? { uri: indexUri } : undefined, [indexUri]);
|
|
3509
|
+
useEffect(() => {
|
|
3510
|
+
if (!indexUri || DEV_ORIGIN) return;
|
|
3511
|
+
const timeout = setTimeout(() => {
|
|
3512
|
+
if (!embeddedReadyReported.current) setStartupError('content');
|
|
3513
|
+
}, 60000);
|
|
3514
|
+
return () => clearTimeout(timeout);
|
|
3515
|
+
}, [indexUri]);
|
|
3114
3516
|
|
|
3115
3517
|
useEffect(() => {
|
|
3116
3518
|
if (!AUTH_ENABLED && !SYNC_ENABLED) return;
|
|
@@ -3121,10 +3523,13 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3121
3523
|
webView.current?.injectJavaScript(\`globalThis.__absoluteExpoReceive(\${JSON.stringify(source)}); true;\`);
|
|
3122
3524
|
});
|
|
3123
3525
|
void ${sync ? "startAbsoluteExpoSync" : "startAbsoluteExpoAuth"}().then(async () => {
|
|
3124
|
-
${sync ? `
|
|
3526
|
+
${sync ? `const bridge = await createAbsoluteExpoSyncBridge((event, payload) => {
|
|
3527
|
+
if (!active) return;
|
|
3125
3528
|
const source = JSON.stringify({ event, format: BRIDGE_FORMAT, kind: 'event', path: activeWebPath.current, payload });
|
|
3126
3529
|
webView.current?.injectJavaScript(\`globalThis.__absoluteExpoReceive(\${JSON.stringify(source)}); true;\`);
|
|
3127
|
-
})
|
|
3530
|
+
});
|
|
3531
|
+
if (!active) { await bridge.close(); return; }
|
|
3532
|
+
syncBridge.current = bridge;` : ""}
|
|
3128
3533
|
if (active) setRuntimeReady(true);
|
|
3129
3534
|
});
|
|
3130
3535
|
return () => {
|
|
@@ -3146,6 +3551,7 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3146
3551
|
markAbsoluteEmbeddedWebPhase('assets-start');
|
|
3147
3552
|
void materializeAbsoluteWebBundle().then(uri => {
|
|
3148
3553
|
markAbsoluteEmbeddedWebPhase('assets-ready');
|
|
3554
|
+
embeddedReadyReported.current = false;
|
|
3149
3555
|
setIndexUri(uri + '?absolutePath=' + encodeURIComponent(pathname));
|
|
3150
3556
|
}).catch(error => {
|
|
3151
3557
|
const phase = error instanceof Error && (error.message === 'assets-root-failed' || error.message === 'assets-directory-failed' || error.message === 'assets-destination-failed' || error.message === 'assets-module-failed' || error.message === 'assets-source-failed' || error.message === 'assets-download-failed' || error.message === 'assets-copy-failed' || error.message === 'assets-read-failed' || error.message === 'assets-write-failed' || error.message === 'assets-finalize-failed')
|
|
@@ -3199,15 +3605,23 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3199
3605
|
try { return new URL(source).origin === origin; } catch { return false; }
|
|
3200
3606
|
};
|
|
3201
3607
|
const onMessage = async (event: WebViewMessageEvent) => {
|
|
3608
|
+
// Bridge authority belongs only to the currently loaded embedded bundle
|
|
3609
|
+
// (or the explicitly configured development origin), never remote pages.
|
|
3610
|
+
const sourceUrl = event.nativeEvent.url;
|
|
3611
|
+
if (!indexUri || typeof sourceUrl !== 'string' || (DEV_ORIGIN ? !hasOrigin(sourceUrl, DEV_ORIGIN) : !sourceUrl.startsWith(indexUri.slice(0, indexUri.lastIndexOf('/') + 1)))) return;
|
|
3202
3612
|
const source = event.nativeEvent.data;
|
|
3203
3613
|
if (new TextEncoder().encode(source).byteLength > MAX_MESSAGE_BYTES) return;
|
|
3204
3614
|
let message: Record<string, unknown>;
|
|
3205
3615
|
try { message = JSON.parse(source); } catch { return; }
|
|
3206
3616
|
if (message.format !== BRIDGE_FORMAT || typeof message.path !== 'string' || !message.path.startsWith('/') || message.path.startsWith('//')) return;
|
|
3207
|
-
if (message.kind === 'event' &&
|
|
3617
|
+
if (message.kind === 'event' && message.event === 'startup-failed') {
|
|
3618
|
+
if (!embeddedReadyReported.current) setStartupError('content');
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
if (message.kind === 'event' && (message.event === 'navigation' || message.event === 'shell-rendered')) {
|
|
3208
3622
|
const target = new URL(message.path, PRODUCTION_ORIGIN);
|
|
3209
3623
|
if (target.origin !== PRODUCTION_ORIGIN) return;
|
|
3210
|
-
if (message.event === '
|
|
3624
|
+
if (message.event === 'shell-rendered' && !DEV_ORIGIN && !embeddedReadyReported.current) {
|
|
3211
3625
|
embeddedReadyReported.current = true;
|
|
3212
3626
|
markAbsoluteEmbeddedWebReady();
|
|
3213
3627
|
}
|
|
@@ -3261,12 +3675,17 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3261
3675
|
</View>;
|
|
3262
3676
|
if (!webSource || !runtimeReady || !devicesReady) return <View style={styles.loading}><ActivityIndicator /></View>;
|
|
3263
3677
|
return <WebView
|
|
3264
|
-
|
|
3265
|
-
|
|
3678
|
+
nativeConfig={!DEV_ORIGIN ? absoluteEmbeddedNativeConfig : undefined}
|
|
3679
|
+
allowFileAccess={Platform.OS !== 'android'}
|
|
3680
|
+
allowFileAccessFromFileURLs={Platform.OS !== 'android'}
|
|
3266
3681
|
allowUniversalAccessFromFileURLs={false}
|
|
3682
|
+
originWhitelist={DEV_ORIGIN ? [DEV_ORIGIN] : [Platform.OS === 'android' ? 'https://appassets.androidplatform.net' : 'file://*']}
|
|
3267
3683
|
allowingReadAccessToURL={webSource.uri.slice(0, webSource.uri.lastIndexOf('/') + 1)}
|
|
3268
3684
|
injectedJavaScriptBeforeContentLoaded={bridgeBootstrap(pathname, safeAreaInsets)}
|
|
3269
3685
|
onMessage={onMessage}
|
|
3686
|
+
onError={() => setStartupError('content')}
|
|
3687
|
+
onRenderProcessGone={() => setStartupError('content')}
|
|
3688
|
+
onContentProcessDidTerminate={() => setStartupError('content')}
|
|
3270
3689
|
onNavigationStateChange={state => {
|
|
3271
3690
|
canGoBack.current = state.canGoBack;
|
|
3272
3691
|
if (!DEV_ORIGIN || !hasOrigin(state.url, DEV_ORIGIN)) return;
|
|
@@ -3274,8 +3693,8 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3274
3693
|
activeWebPath.current = route.pathname + route.search + route.hash;
|
|
3275
3694
|
}}
|
|
3276
3695
|
onShouldStartLoadWithRequest={request => {
|
|
3277
|
-
if (
|
|
3278
|
-
void Linking.openURL(request.url);
|
|
3696
|
+
if (DEV_ORIGIN ? hasOrigin(request.url, DEV_ORIGIN) : request.url.startsWith(webSource.uri.slice(0, webSource.uri.lastIndexOf('/') + 1))) return true;
|
|
3697
|
+
if (/^https?:/.test(request.url)) void Linking.openURL(request.url);
|
|
3279
3698
|
return false;
|
|
3280
3699
|
}}
|
|
3281
3700
|
ref={webView}
|
|
@@ -3478,8 +3897,8 @@ const styles = StyleSheet.create({
|
|
|
3478
3897
|
error: { color: '#b91c1c', fontSize: 16, textAlign: 'center' }
|
|
3479
3898
|
});
|
|
3480
3899
|
`;
|
|
3481
|
-
var nativeWrapperSource = (wrapper, module,
|
|
3482
|
-
import { createAbsoluteNativeRoute, createAbsoluteNativeRouteErrorBoundary } from ${JSON.stringify(portableRelative2(
|
|
3900
|
+
var nativeWrapperSource = (wrapper, module, runtime3, route) => `${EXPO_GENERATED_HEADER}import ApplicationNativeRoute from ${JSON.stringify(portableRelative2(dirname4(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
|
|
3901
|
+
import { createAbsoluteNativeRoute, createAbsoluteNativeRouteErrorBoundary } from ${JSON.stringify(portableRelative2(dirname4(wrapper), runtime3).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
|
|
3483
3902
|
|
|
3484
3903
|
export const ErrorBoundary = createAbsoluteNativeRouteErrorBoundary(${JSON.stringify(route)});
|
|
3485
3904
|
export default createAbsoluteNativeRoute(ApplicationNativeRoute, ${JSON.stringify(route)});
|
|
@@ -3521,9 +3940,9 @@ var expoTsConfig = (projectRoot, project, auth, sync) => ({
|
|
|
3521
3940
|
extends: "expo/tsconfig.base"
|
|
3522
3941
|
});
|
|
3523
3942
|
var writeManagedFile = async (path, source, force) => {
|
|
3524
|
-
await mkdir2(
|
|
3943
|
+
await mkdir2(dirname4(path), { recursive: true });
|
|
3525
3944
|
if (await exists2(path)) {
|
|
3526
|
-
const current = await
|
|
3945
|
+
const current = await readFile3(path, "utf8");
|
|
3527
3946
|
if (current === source)
|
|
3528
3947
|
return false;
|
|
3529
3948
|
if (!force && !current.startsWith(EXPO_GENERATED_HEADER)) {
|
|
@@ -3539,7 +3958,7 @@ var pruneManagedFiles = async (paths) => {
|
|
|
3539
3958
|
const removed = await Promise.all(paths.map(async (path) => {
|
|
3540
3959
|
if (!await exists2(path))
|
|
3541
3960
|
return false;
|
|
3542
|
-
const source = await
|
|
3961
|
+
const source = await readFile3(path, "utf8");
|
|
3543
3962
|
if (!source.startsWith(EXPO_GENERATED_HEADER) && !path.endsWith("expo-module.config.json"))
|
|
3544
3963
|
return false;
|
|
3545
3964
|
await rm2(path, { force: true });
|
|
@@ -3548,12 +3967,12 @@ var pruneManagedFiles = async (paths) => {
|
|
|
3548
3967
|
return removed.filter(Boolean).length;
|
|
3549
3968
|
};
|
|
3550
3969
|
var pruneStaleManagedExpoRoutes = async (project, expected) => {
|
|
3551
|
-
const appDirectory =
|
|
3970
|
+
const appDirectory = join8(project, "app");
|
|
3552
3971
|
if (!await exists2(appDirectory))
|
|
3553
3972
|
return 0;
|
|
3554
3973
|
const files = await walkFiles(appDirectory);
|
|
3555
3974
|
const stale = (await Promise.all(files.map(async (path) => ({
|
|
3556
|
-
managed: path.endsWith(".tsx") && (await
|
|
3975
|
+
managed: path.endsWith(".tsx") && (await readFile3(path, "utf8")).startsWith(EXPO_GENERATED_HEADER),
|
|
3557
3976
|
path
|
|
3558
3977
|
})))).filter(({ managed, path }) => managed && !expected.has(path));
|
|
3559
3978
|
await Promise.all(stale.map(({ path }) => rm2(path, { force: true })));
|
|
@@ -3579,7 +3998,7 @@ export const ABSOLUTE_MOBILE_MANIFEST: AbsoluteMobileManifest | undefined = unde
|
|
|
3579
3998
|
var writeAbsoluteExpoProject = async (config, options) => {
|
|
3580
3999
|
if (config.engine !== "expo")
|
|
3581
4000
|
throw new TypeError("Expo project generation requires mobile.engine: expo.");
|
|
3582
|
-
const projectRoot =
|
|
4001
|
+
const projectRoot = resolve5(options.projectRoot);
|
|
3583
4002
|
const project = config.nativeProjectDirectory;
|
|
3584
4003
|
const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
|
|
3585
4004
|
const devices = resolveAbsoluteDeviceCapabilityPlan(projectRoot, "expo");
|
|
@@ -3596,7 +4015,7 @@ var writeAbsoluteExpoProject = async (config, options) => {
|
|
|
3596
4015
|
if (missing) {
|
|
3597
4016
|
throw new TypeError(`Expo native route ${missing.route} references missing module ${missing.module}.`);
|
|
3598
4017
|
}
|
|
3599
|
-
const marker =
|
|
4018
|
+
const marker = join8(project, EXPO_PROJECT_MARKER);
|
|
3600
4019
|
if (await exists2(project) && !await exists2(marker)) {
|
|
3601
4020
|
const entries = await readdir(project);
|
|
3602
4021
|
if (entries.length > 0 && !options.force) {
|
|
@@ -3609,7 +4028,7 @@ var writeAbsoluteExpoProject = async (config, options) => {
|
|
|
3609
4028
|
const authEnabled = Boolean(auth);
|
|
3610
4029
|
const files = new Map([
|
|
3611
4030
|
[
|
|
3612
|
-
|
|
4031
|
+
join8(project, ".gitignore"),
|
|
3613
4032
|
`.expo/
|
|
3614
4033
|
android/
|
|
3615
4034
|
ios/
|
|
@@ -3617,80 +4036,82 @@ node_modules/
|
|
|
3617
4036
|
`
|
|
3618
4037
|
],
|
|
3619
4038
|
[
|
|
3620
|
-
|
|
4039
|
+
join8(project, "app.json"),
|
|
3621
4040
|
jsonSource(expoAppConfig(config, authEnabled, syncEnabled, devices, runtimeFingerprint))
|
|
3622
4041
|
],
|
|
3623
|
-
[
|
|
4042
|
+
[join8(project, "app.config.js"), expoDynamicAppConfig],
|
|
3624
4043
|
[
|
|
3625
|
-
|
|
4044
|
+
join8(project, "package.json"),
|
|
3626
4045
|
jsonSource(expoPackage(authEnabled, config.branding !== undefined, syncEnabled, devices, config.updates !== undefined))
|
|
3627
4046
|
],
|
|
3628
|
-
[
|
|
4047
|
+
[join8(project, "metro.config.js"), metroConfig(projectRoot)],
|
|
3629
4048
|
[
|
|
3630
|
-
|
|
4049
|
+
join8(project, "plugins", "withAbsoluteDevelopmentCa.js"),
|
|
3631
4050
|
expoDevelopmentCaPlugin
|
|
3632
4051
|
],
|
|
3633
4052
|
[
|
|
3634
|
-
|
|
4053
|
+
join8(project, "plugins", "withAbsoluteActivityResultRecovery.js"),
|
|
3635
4054
|
expoActivityResultRecoveryPlugin
|
|
3636
4055
|
],
|
|
3637
4056
|
[
|
|
3638
|
-
|
|
4057
|
+
join8(project, "tsconfig.json"),
|
|
3639
4058
|
jsonSource(expoTsConfig(projectRoot, project, authEnabled, syncEnabled))
|
|
3640
4059
|
],
|
|
3641
4060
|
[
|
|
3642
|
-
|
|
4061
|
+
join8(project, "app", "_layout.tsx"),
|
|
3643
4062
|
layoutSource(authEnabled, syncEnabled, config.updates !== undefined, config.observability !== undefined)
|
|
3644
4063
|
],
|
|
3645
4064
|
[
|
|
3646
|
-
|
|
4065
|
+
join8(project, "app", "__absolute", "native", "index.tsx"),
|
|
3647
4066
|
nativeDiagnosticSource
|
|
3648
4067
|
],
|
|
3649
4068
|
[
|
|
3650
|
-
|
|
4069
|
+
join8(project, "src", "generated", "AbsoluteDevices.ts"),
|
|
3651
4070
|
devicesRuntimeSource(config, devices, authEnabled)
|
|
3652
4071
|
],
|
|
3653
4072
|
[
|
|
3654
|
-
|
|
4073
|
+
join8(project, "src", "generated", "AbsoluteNativeRoute.tsx"),
|
|
3655
4074
|
nativeRouteRuntimeSource(authEnabled)
|
|
3656
4075
|
],
|
|
3657
4076
|
[
|
|
3658
|
-
|
|
4077
|
+
join8(project, "src", "generated", "AbsoluteWebHost.tsx"),
|
|
3659
4078
|
webHostSource(config, auth, syncEnabled)
|
|
3660
4079
|
]
|
|
3661
4080
|
]);
|
|
3662
4081
|
for (const [path, source] of absoluteExpoActivityResultRecoveryFiles(project))
|
|
3663
4082
|
files.set(path, source);
|
|
4083
|
+
for (const [path, source] of absoluteExpoEmbeddedAssetsFiles(project))
|
|
4084
|
+
files.set(path, source);
|
|
3664
4085
|
if (config.observability) {
|
|
3665
4086
|
for (const [path, source] of absoluteExpoNativeObservabilityFiles(project))
|
|
3666
4087
|
files.set(path, source);
|
|
3667
4088
|
}
|
|
3668
4089
|
if (config.updates) {
|
|
3669
|
-
files.set(
|
|
4090
|
+
files.set(join8(project, "src", "generated", "AbsoluteUpdates.ts"), updatesRuntimeSource(config));
|
|
3670
4091
|
}
|
|
3671
4092
|
const expoCodeSigning = config.updates?.expoCodeSigning;
|
|
3672
4093
|
if (expoCodeSigning)
|
|
3673
|
-
files.set(
|
|
4094
|
+
files.set(join8(project, "certs", "absolute-mobile-update.pem"), expoCodeSigning.certificatePem);
|
|
3674
4095
|
if (auth) {
|
|
3675
|
-
files.set(
|
|
4096
|
+
files.set(join8(project, "src", "generated", "AbsoluteAuth.ts"), authRuntimeSource(auth, config.appId));
|
|
3676
4097
|
}
|
|
3677
4098
|
if (syncSchema) {
|
|
3678
|
-
files.set(
|
|
4099
|
+
files.set(join8(project, "src", "generated", "AbsoluteSync.ts"), syncRuntimeSource(config, syncSchema));
|
|
3679
4100
|
}
|
|
3680
|
-
const webAssetsPath =
|
|
4101
|
+
const webAssetsPath = join8(project, "src", "generated", "webAssets.ts");
|
|
3681
4102
|
if (!await exists2(webAssetsPath)) {
|
|
3682
4103
|
files.set(webAssetsPath, emptyWebAssetsSource);
|
|
3683
4104
|
}
|
|
3684
4105
|
if (!config.expoNativeRoutes["/"]) {
|
|
3685
|
-
files.set(
|
|
4106
|
+
files.set(join8(project, "app", "index.tsx"), webRouteSource);
|
|
3686
4107
|
}
|
|
3687
|
-
files.set(
|
|
3688
|
-
const nativeRouteRuntime =
|
|
4108
|
+
files.set(join8(project, "app", "[...absolute].tsx"), catchAllRouteSource);
|
|
4109
|
+
const nativeRouteRuntime = join8(project, "src", "generated", "AbsoluteNativeRoute.tsx");
|
|
3689
4110
|
for (const [route, module] of routeModules) {
|
|
3690
|
-
const wrapper = route === "/" ?
|
|
4111
|
+
const wrapper = route === "/" ? join8(project, "app", "index.tsx") : routeFile(project, route);
|
|
3691
4112
|
files.set(wrapper, nativeWrapperSource(wrapper, module, nativeRouteRuntime, route));
|
|
3692
4113
|
}
|
|
3693
|
-
const removedRoutes = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${
|
|
4114
|
+
const removedRoutes = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${join8(project, "app")}${sep}`))));
|
|
3694
4115
|
const removedObservability = config.observability ? 0 : await pruneManagedFiles(absoluteExpoNativeObservabilityPaths(project));
|
|
3695
4116
|
const changes = await Promise.all([...files].map(([path, source]) => writeManagedFile(path, source, true)));
|
|
3696
4117
|
await writeAbsoluteExpoBrandingInputs(config, projectRoot);
|
|
@@ -3700,7 +4121,7 @@ node_modules/
|
|
|
3700
4121
|
var walkFiles = async (root, directory = root) => {
|
|
3701
4122
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
3702
4123
|
const nested = await Promise.all(entries.map((entry) => {
|
|
3703
|
-
const path =
|
|
4124
|
+
const path = join8(directory, entry.name);
|
|
3704
4125
|
if (entry.isDirectory())
|
|
3705
4126
|
return walkFiles(root, path);
|
|
3706
4127
|
if (entry.isFile())
|
|
@@ -3733,13 +4154,16 @@ var installStagedDirectory = async (staging, destination) => {
|
|
|
3733
4154
|
throw error;
|
|
3734
4155
|
}
|
|
3735
4156
|
};
|
|
3736
|
-
var assetModuleSource = (assets, archiveAsset, bundleId, manifest) => `${EXPO_GENERATED_HEADER}import { Asset } from 'expo-asset';
|
|
4157
|
+
var assetModuleSource = (assets, archiveAsset, bundleId, archiveMd5, manifest) => `${EXPO_GENERATED_HEADER}import { Asset } from 'expo-asset';
|
|
3737
4158
|
import { Directory, File, Paths } from 'expo-file-system';
|
|
3738
4159
|
import { copyAsync as copyLegacyAsync } from 'expo-file-system/legacy';
|
|
4160
|
+
import { registerAbsoluteEmbeddedBundle } from './AbsoluteEmbeddedAssets';
|
|
3739
4161
|
|
|
3740
4162
|
${nativeDataManifestTypeSource}
|
|
3741
4163
|
declare const require: (path: string) => number;
|
|
3742
4164
|
const BUNDLE_ID = ${JSON.stringify(bundleId)};
|
|
4165
|
+
const ARCHIVE_MD5 = ${JSON.stringify(archiveMd5)};
|
|
4166
|
+
const ARCHIVE_LENGTH = ${assets.reduce((sum, entry) => sum + entry.length, 0)};
|
|
3743
4167
|
export const ABSOLUTE_MOBILE_MANIFEST: AbsoluteMobileManifest = ${JSON.stringify(manifest)};
|
|
3744
4168
|
const ARCHIVE_MODULE = require(${JSON.stringify(archiveAsset)});
|
|
3745
4169
|
const ASSETS = [
|
|
@@ -3756,10 +4180,12 @@ export const materializeAbsoluteWebBundle = async () => {
|
|
|
3756
4180
|
let archive: File;
|
|
3757
4181
|
try { archive = new File(root, '.absolutejs-bundle.absasset'); }
|
|
3758
4182
|
catch { throw new Error('assets-destination-failed'); }
|
|
4183
|
+
if (archive.exists && (archive.size !== ARCHIVE_LENGTH || archive.md5 !== ARCHIVE_MD5)) archive.delete();
|
|
3759
4184
|
if (!archive.exists) {
|
|
3760
4185
|
let asset: Asset;
|
|
3761
4186
|
try { asset = Asset.fromModule(ARCHIVE_MODULE); }
|
|
3762
4187
|
catch { throw new Error('assets-module-failed'); }
|
|
4188
|
+
if (asset.type !== 'absasset' || asset.hash !== ARCHIVE_MD5) throw new Error('assets-identity-failed');
|
|
3763
4189
|
try { if (!asset.localUri) asset = await asset.downloadAsync(); }
|
|
3764
4190
|
catch { throw new Error('assets-download-failed'); }
|
|
3765
4191
|
if (!asset.localUri) throw new Error('assets-download-failed');
|
|
@@ -3772,9 +4198,16 @@ export const materializeAbsoluteWebBundle = async () => {
|
|
|
3772
4198
|
catch { throw new Error('assets-copy-failed'); }
|
|
3773
4199
|
}
|
|
3774
4200
|
}
|
|
4201
|
+
// Checksums detect accidental corruption/registry mismatches, not authenticity;
|
|
4202
|
+
// authenticity comes from the signed application/update containing this table.
|
|
4203
|
+
if (archive.size !== ARCHIVE_LENGTH || archive.md5 !== ARCHIVE_MD5) {
|
|
4204
|
+
archive.delete();
|
|
4205
|
+
throw new Error('assets-integrity-failed');
|
|
4206
|
+
}
|
|
3775
4207
|
let contents: Uint8Array;
|
|
3776
4208
|
try { contents = await archive.bytes(); }
|
|
3777
4209
|
catch { throw new Error('assets-read-failed'); }
|
|
4210
|
+
if (contents.byteLength !== ARCHIVE_LENGTH) throw new Error('assets-integrity-failed');
|
|
3778
4211
|
for (const entry of ASSETS) {
|
|
3779
4212
|
const parts = entry.path.split('/');
|
|
3780
4213
|
const name = parts.pop();
|
|
@@ -3788,33 +4221,34 @@ export const materializeAbsoluteWebBundle = async () => {
|
|
|
3788
4221
|
let destination: File;
|
|
3789
4222
|
try {
|
|
3790
4223
|
destination = new File(directory, name);
|
|
3791
|
-
if (destination.exists && destination.size === entry.length) continue;
|
|
4224
|
+
if (destination.exists && destination.size === entry.length && destination.md5 === entry.md5) continue;
|
|
3792
4225
|
if (destination.exists) destination.delete();
|
|
3793
4226
|
} catch { throw new Error('assets-destination-failed'); }
|
|
3794
4227
|
try { destination.write(contents.slice(entry.offset, entry.offset + entry.length)); }
|
|
3795
4228
|
catch { throw new Error('assets-write-failed'); }
|
|
4229
|
+
if (destination.size !== entry.length || destination.md5 !== entry.md5) throw new Error('assets-integrity-failed');
|
|
3796
4230
|
}
|
|
3797
4231
|
try { archive.delete(); }
|
|
3798
4232
|
catch { throw new Error('assets-finalize-failed'); }
|
|
3799
4233
|
|
|
3800
|
-
try { return new File(root, 'index.html').uri; }
|
|
4234
|
+
try { return await registerAbsoluteEmbeddedBundle(root.uri, ASSETS.map(entry => entry.path), new File(root, 'index.html').uri); }
|
|
3801
4235
|
catch { throw new Error('assets-finalize-failed'); }
|
|
3802
4236
|
};
|
|
3803
4237
|
`;
|
|
3804
4238
|
var syncAbsoluteExpoWebAssets = async (config) => {
|
|
3805
4239
|
if (config.engine !== "expo")
|
|
3806
4240
|
throw new TypeError("Expo asset sync requires mobile.engine: expo.");
|
|
3807
|
-
const marker =
|
|
4241
|
+
const marker = join8(config.nativeProjectDirectory, EXPO_PROJECT_MARKER);
|
|
3808
4242
|
if (!await exists2(marker)) {
|
|
3809
4243
|
throw new TypeError("Expo asset sync requires an AbsoluteJS-managed Expo project. Run mobile init first.");
|
|
3810
4244
|
}
|
|
3811
|
-
const manifestPath =
|
|
3812
|
-
const manifest = JSON.parse(await
|
|
4245
|
+
const manifestPath = join8(config.bundleDirectory, "absolute-mobile-manifest.json");
|
|
4246
|
+
const manifest = JSON.parse(await readFile3(manifestPath, "utf8"));
|
|
3813
4247
|
const nativeDataManifest = expoNativeDataManifest(manifest);
|
|
3814
4248
|
const { appBuild } = nativeDataManifest;
|
|
3815
4249
|
const files = await walkFiles(config.bundleDirectory);
|
|
3816
4250
|
const bundleHash = createHash3("sha256");
|
|
3817
|
-
const filesWithContents = await Promise.all(files.map(async (file) => ({ contents: await
|
|
4251
|
+
const filesWithContents = await Promise.all(files.map(async (file) => ({ contents: await readFile3(file), file })));
|
|
3818
4252
|
filesWithContents.forEach(({ contents, file }) => {
|
|
3819
4253
|
bundleHash.update(relative3(config.bundleDirectory, file).replaceAll("\\", "/"));
|
|
3820
4254
|
bundleHash.update("\x00");
|
|
@@ -3822,16 +4256,18 @@ var syncAbsoluteExpoWebAssets = async (config) => {
|
|
|
3822
4256
|
bundleHash.update("\x00");
|
|
3823
4257
|
});
|
|
3824
4258
|
const bundleId = `amexpo_${bundleHash.digest("hex")}`;
|
|
3825
|
-
const destination =
|
|
3826
|
-
await mkdir2(
|
|
3827
|
-
const staging = await mkdtemp(
|
|
4259
|
+
const destination = join8(config.nativeProjectDirectory, "assets", "absolute");
|
|
4260
|
+
await mkdir2(dirname4(destination), { recursive: true });
|
|
4261
|
+
const staging = await mkdtemp(join8(dirname4(destination), `.${basename(destination)}.stage-`));
|
|
3828
4262
|
let assets;
|
|
3829
4263
|
let archiveAsset;
|
|
4264
|
+
let archiveMd5;
|
|
3830
4265
|
try {
|
|
3831
4266
|
let offset = 0;
|
|
3832
4267
|
assets = filesWithContents.map(({ contents, file }) => {
|
|
3833
4268
|
const entry = {
|
|
3834
4269
|
length: contents.byteLength,
|
|
4270
|
+
md5: createHash3("md5").update(contents).digest("hex"),
|
|
3835
4271
|
offset,
|
|
3836
4272
|
path: relative3(config.bundleDirectory, file).replaceAll("\\", "/")
|
|
3837
4273
|
};
|
|
@@ -3839,16 +4275,18 @@ var syncAbsoluteExpoWebAssets = async (config) => {
|
|
|
3839
4275
|
return entry;
|
|
3840
4276
|
});
|
|
3841
4277
|
const name = `bundle${EXPO_ASSET_EXTENSION}`;
|
|
3842
|
-
|
|
3843
|
-
|
|
4278
|
+
const archiveContents = Buffer.concat(filesWithContents.map(({ contents }) => contents));
|
|
4279
|
+
archiveMd5 = createHash3("md5").update(archiveContents).digest("hex");
|
|
4280
|
+
await writeFile2(join8(staging, name), archiveContents);
|
|
4281
|
+
archiveAsset = portableRelative2(join8(config.nativeProjectDirectory, "src", "generated"), join8(destination, name));
|
|
3844
4282
|
await installStagedDirectory(staging, destination);
|
|
3845
4283
|
} catch (error) {
|
|
3846
4284
|
await rm2(staging, { force: true, recursive: true });
|
|
3847
4285
|
throw error;
|
|
3848
4286
|
}
|
|
3849
|
-
const generated =
|
|
3850
|
-
await writeManagedFile(generated, assetModuleSource(assets, archiveAsset, bundleId, nativeDataManifest), true);
|
|
4287
|
+
const generated = join8(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
|
|
4288
|
+
await writeManagedFile(generated, assetModuleSource(assets, archiveAsset, bundleId, archiveMd5, nativeDataManifest), true);
|
|
3851
4289
|
return { appBuild, assets: assets.length, bundleId, path: destination };
|
|
3852
4290
|
};
|
|
3853
4291
|
|
|
3854
|
-
export { discoverAbsoluteSyncSchema, absoluteDeviceNativeRequirements, assertAbsoluteDeviceCapabilityPackages, resolveAbsoluteDeviceCapabilityPlan, ABSOLUTE_MOBILE_UPDATE_FORMAT, absoluteMobileUpdateSigningPayload, canonicalizeAbsoluteMobileUpdate, parseAbsoluteMobileUpdateManifest, unsignedAbsoluteMobileUpdate, resolveAbsoluteMobileUpdateRuntime, ABSOLUTE_CAPACITOR_ASSETS_SPEC, generateAbsoluteCapacitorBranding, inspectAbsoluteMobileBranding, renderAbsoluteMobileBrandingPreview, writeAbsoluteExpoProject, syncAbsoluteExpoWebAssets };
|
|
4292
|
+
export { ABSOLUTE_NATIVE_EXACT_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, assertNativePackageVersions, packageNameFromSpec, packagesNeedingExactInstall, discoverAbsoluteSyncSchema, absoluteDeviceNativeRequirements, assertAbsoluteDeviceCapabilityPackages, resolveAbsoluteDeviceCapabilityPlan, ABSOLUTE_MOBILE_UPDATE_FORMAT, absoluteMobileUpdateSigningPayload, canonicalizeAbsoluteMobileUpdate, parseAbsoluteMobileUpdateManifest, unsignedAbsoluteMobileUpdate, resolveAbsoluteMobileUpdateRuntime, ABSOLUTE_CAPACITOR_ASSETS_SPEC, generateAbsoluteCapacitorBranding, inspectAbsoluteMobileBranding, renderAbsoluteMobileBrandingPreview, writeAbsoluteExpoProject, syncAbsoluteExpoWebAssets };
|