@absolutejs/absolute 0.20.0-beta.115 → 0.20.0-beta.116
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 +7 -0
- package/changelog.json +15 -1
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/{compile-ya4kd2sg.js → compile-kbhxgjat.js} +2 -2
- package/dist/cli/{dev-5cqmf7za.js → dev-daa7tdht.js} +1 -1
- package/dist/cli/{expoProject-f5j20xms.js → expoProject-kcfcmfba.js} +1 -1
- package/dist/cli/{index-6mbm4n3g.js → index-82wpxhvv.js} +1 -1
- package/dist/cli/{index-dr21p6ae.js → index-bhcxtf1x.js} +389 -78
- package/dist/cli/{index-5fmgn0nx.js → index-vjepnxr0.js} +1 -1
- package/dist/cli/index.js +5 -5
- package/dist/cli/{mobile-fg2226vv.js → mobile-03zwncxx.js} +22 -20
- package/dist/cli/{start-jt61f40d.js → start-r606d5v9.js} +3 -3
- package/dist/mobile/index.js +486 -175
- package/dist/mobile/index.js.map +6 -5
- package/dist/mobile/remoteMacAgentEntry.js +475 -209
- package/dist/mobile/shellBootstrap.js +6 -1
- package/dist/react/hooks/index.js.map +2 -2
- package/dist/src/mobile/expoEmbeddedAssets.d.ts +1 -0
- package/dist/src/mobile/updateRuntime.d.ts +1 -0
- package/package.json +3 -1
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
writeFile as writeFile2
|
|
23
23
|
} from "fs/promises";
|
|
24
24
|
import { createHash as createHash3 } from "crypto";
|
|
25
|
-
import { basename, dirname as dirname3, join as
|
|
25
|
+
import { basename, dirname as dirname3, join as join7, relative as relative3, resolve as resolve4, sep } from "path";
|
|
26
26
|
|
|
27
27
|
// src/mobile/syncSchema.ts
|
|
28
28
|
import { readFileSync } from "fs";
|
|
@@ -902,6 +902,7 @@ var createAbsoluteMobileUpdateRuntimeDescriptor = (options) => ({
|
|
|
902
902
|
},
|
|
903
903
|
deviceCapabilities: options.deviceCapabilities,
|
|
904
904
|
engine: options.config.engine,
|
|
905
|
+
...options.config.engine === "expo" ? { expoEmbeddedAssetsAbi: 1 } : {},
|
|
905
906
|
format: ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT,
|
|
906
907
|
shellAbi: ABSOLUTE_MOBILE_SHELL_ABI,
|
|
907
908
|
syncSchema: options.syncSchema ?? null,
|
|
@@ -1671,6 +1672,265 @@ var absoluteExpoActivityResultRecoveryFiles = (project) => new Map([
|
|
|
1671
1672
|
]
|
|
1672
1673
|
]);
|
|
1673
1674
|
|
|
1675
|
+
// src/mobile/expoEmbeddedAssets.ts
|
|
1676
|
+
import { join as join5 } from "path";
|
|
1677
|
+
var header = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
|
|
1678
|
+
`;
|
|
1679
|
+
var android = `${header}package expo.modules.absoluteembeddedassets
|
|
1680
|
+
|
|
1681
|
+
import android.content.Context
|
|
1682
|
+
import android.graphics.Bitmap
|
|
1683
|
+
import android.net.Uri
|
|
1684
|
+
import android.util.Log
|
|
1685
|
+
import android.webkit.WebResourceRequest
|
|
1686
|
+
import android.webkit.WebResourceResponse
|
|
1687
|
+
import android.webkit.WebView
|
|
1688
|
+
import androidx.webkit.WebViewAssetLoader
|
|
1689
|
+
import androidx.webkit.WebViewCompat
|
|
1690
|
+
import androidx.webkit.WebViewFeature
|
|
1691
|
+
import androidx.webkit.ScriptHandler
|
|
1692
|
+
import androidx.webkit.WebMessageCompat
|
|
1693
|
+
import com.facebook.react.ReactPackage
|
|
1694
|
+
import com.facebook.react.bridge.NativeModule
|
|
1695
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
1696
|
+
import com.facebook.react.bridge.ReadableMap
|
|
1697
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
1698
|
+
import com.facebook.react.uimanager.ViewManager
|
|
1699
|
+
import com.reactnativecommunity.webview.RNCWebViewClient
|
|
1700
|
+
import com.reactnativecommunity.webview.RNCWebViewManager
|
|
1701
|
+
import com.reactnativecommunity.webview.RNCWebViewWrapper
|
|
1702
|
+
import expo.modules.kotlin.modules.Module
|
|
1703
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
1704
|
+
import java.io.ByteArrayInputStream
|
|
1705
|
+
import java.io.File
|
|
1706
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
1707
|
+
import java.util.WeakHashMap
|
|
1708
|
+
|
|
1709
|
+
private const val HOST = "appassets.androidplatform.net"
|
|
1710
|
+
private const val PREFIX = "/absolutejs/"
|
|
1711
|
+
|
|
1712
|
+
internal object AbsoluteEmbeddedAssets {
|
|
1713
|
+
private val bundles = ConcurrentHashMap<String, Map<String, File>>()
|
|
1714
|
+
|
|
1715
|
+
fun register(context: Context, rootUri: String, paths: List<String>): String {
|
|
1716
|
+
check(WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT) &&
|
|
1717
|
+
WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
|
|
1718
|
+
"Update Android System WebView to load this application."
|
|
1719
|
+
}
|
|
1720
|
+
val uri = Uri.parse(rootUri)
|
|
1721
|
+
require(uri.scheme == "file" && uri.authority.isNullOrEmpty() && uri.query == null && uri.fragment == null)
|
|
1722
|
+
val root = File(requireNotNull(uri.path)).canonicalFile
|
|
1723
|
+
val parent = File(context.filesDir, "absolutejs-web").canonicalFile
|
|
1724
|
+
require(root.parentFile == parent && root.name.matches(Regex("amexpo_[a-f0-9]{64}")))
|
|
1725
|
+
require(paths.isNotEmpty() && paths.size <= 100000 && paths.distinct().size == paths.size)
|
|
1726
|
+
val files = paths.associateWith { path ->
|
|
1727
|
+
require(path.isNotEmpty() && !path.contains('\\\\') && !path.contains('%') &&
|
|
1728
|
+
!path.contains('?') && !path.contains('#') && path.indexOf(0.toChar()) < 0 &&
|
|
1729
|
+
path.split('/').all { it.isNotEmpty() && it != "." && it != ".." })
|
|
1730
|
+
val file = File(root, path).canonicalFile
|
|
1731
|
+
require(file.path.startsWith(root.path + File.separator) && file.isFile)
|
|
1732
|
+
file
|
|
1733
|
+
}
|
|
1734
|
+
require(files.containsKey("index.html") && files.containsKey("absolute-mobile-manifest.json"))
|
|
1735
|
+
bundles[root.name] = files
|
|
1736
|
+
Log.i("AbsoluteJS", "Expo embedded web phase: asset-host-registered")
|
|
1737
|
+
return "https://" + HOST + PREFIX + root.name + "/index.html"
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
private fun empty(status: Int, reason: String) = WebResourceResponse(
|
|
1741
|
+
"text/plain", "UTF-8", status, reason,
|
|
1742
|
+
mapOf("Cache-Control" to "no-store", "X-Content-Type-Options" to "nosniff"),
|
|
1743
|
+
ByteArrayInputStream(ByteArray(0))
|
|
1744
|
+
)
|
|
1745
|
+
|
|
1746
|
+
private fun resource(path: String): WebResourceResponse {
|
|
1747
|
+
val bundle = path.substringBefore('/')
|
|
1748
|
+
val name = path.substringAfter('/', "")
|
|
1749
|
+
val file = bundles[bundle]?.get(name) ?: return empty(404, "Not Found")
|
|
1750
|
+
// Recheck canonical location at read time. Never follow a replaced symlink.
|
|
1751
|
+
if (file.canonicalFile != file || !file.isFile) return empty(404, "Not Found")
|
|
1752
|
+
val mime = when (file.extension.lowercase()) {
|
|
1753
|
+
"html", "htm" -> "text/html"
|
|
1754
|
+
"js", "mjs" -> "text/javascript"
|
|
1755
|
+
"json", "map" -> "application/json"
|
|
1756
|
+
"css" -> "text/css"
|
|
1757
|
+
"svg" -> "image/svg+xml"
|
|
1758
|
+
"wasm" -> "application/wasm"
|
|
1759
|
+
else -> android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension.lowercase()) ?: "application/octet-stream"
|
|
1760
|
+
}
|
|
1761
|
+
return try {
|
|
1762
|
+
WebResourceResponse(mime, "UTF-8", 200, "OK",
|
|
1763
|
+
mapOf("Cache-Control" to "no-store", "X-Content-Type-Options" to "nosniff"), file.inputStream())
|
|
1764
|
+
} catch (_: Exception) { empty(404, "Not Found") }
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
private val loader = WebViewAssetLoader.Builder()
|
|
1768
|
+
.setDomain(HOST).setHttpAllowed(false)
|
|
1769
|
+
.addPathHandler(PREFIX) { path -> resource(path) }.build()
|
|
1770
|
+
|
|
1771
|
+
fun intercept(request: WebResourceRequest, activeBundle: String?): WebResourceResponse? {
|
|
1772
|
+
val uri = request.url
|
|
1773
|
+
if (uri.host != HOST) return null
|
|
1774
|
+
if (uri.path.orEmpty().endsWith("/index.html")) Log.i("AbsoluteJS", "Expo embedded web phase: document-request")
|
|
1775
|
+
if (uri.path.orEmpty().endsWith("/absolute-mobile-bootstrap.js")) Log.i("AbsoluteJS", "Expo embedded web phase: bootstrap-request")
|
|
1776
|
+
// A reserved asset host must never fall through to DNS/network, even for
|
|
1777
|
+
// unknown bundles, wrong ports/schemes, methods, or malformed paths.
|
|
1778
|
+
if (uri.scheme != "https" || uri.port != -1 || uri.userInfo != null ||
|
|
1779
|
+
request.method != "GET") return empty(403, "Forbidden")
|
|
1780
|
+
// Root-relative image/font/CSS URLs retain their web meaning. Scope them
|
|
1781
|
+
// to this WebView's bundle, not the last bundle registered by another view.
|
|
1782
|
+
if (!uri.path.orEmpty().startsWith(PREFIX)) {
|
|
1783
|
+
if (request.isForMainFrame || activeBundle == null) return empty(404, "Not Found")
|
|
1784
|
+
return resource(activeBundle + "/" + uri.path.orEmpty().removePrefix("/"))
|
|
1785
|
+
}
|
|
1786
|
+
return loader.shouldInterceptRequest(uri) ?: empty(404, "Not Found")
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
class AbsoluteEmbeddedAssetsModule : Module() {
|
|
1791
|
+
override fun definition() = ModuleDefinition {
|
|
1792
|
+
Name("AbsoluteEmbeddedAssets")
|
|
1793
|
+
AsyncFunction("registerBundle") { root: String, paths: List<String> ->
|
|
1794
|
+
AbsoluteEmbeddedAssets.register(requireNotNull(appContext.reactContext), root, paths)
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
class AbsoluteEmbeddedWebViewManager : RNCWebViewManager() {
|
|
1800
|
+
private val scripts = WeakHashMap<RNCWebViewWrapper, ScriptHandler>()
|
|
1801
|
+
override fun getName() = "AbsoluteEmbeddedWebView"
|
|
1802
|
+
override fun setNewSource(view: RNCWebViewWrapper, value: ReadableMap?) {
|
|
1803
|
+
Log.i("AbsoluteJS", "Expo embedded web phase: source-assigned")
|
|
1804
|
+
super.setNewSource(view, value)
|
|
1805
|
+
}
|
|
1806
|
+
override fun setMessagingEnabled(view: RNCWebViewWrapper, value: Boolean) {
|
|
1807
|
+
super.setMessagingEnabled(view, value)
|
|
1808
|
+
// Upstream reports sourceOrigin as event.url and accepts subframe messages.
|
|
1809
|
+
// Retain its event dispatch, but bind this bridge to our main-frame origin
|
|
1810
|
+
// and attach the native-observed document URL, never a JS-supplied URL.
|
|
1811
|
+
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) return
|
|
1812
|
+
WebViewCompat.removeWebMessageListener(view.webView, "ReactNativeWebView")
|
|
1813
|
+
if (!value) return
|
|
1814
|
+
WebViewCompat.addWebMessageListener(view.webView, "ReactNativeWebView", setOf("https://" + HOST)) {
|
|
1815
|
+
webView, message, sourceOrigin, isMainFrame, _ ->
|
|
1816
|
+
if (isMainFrame && sourceOrigin.scheme == "https" && sourceOrigin.host == HOST &&
|
|
1817
|
+
message.type == WebMessageCompat.TYPE_STRING) {
|
|
1818
|
+
val url = webView.url
|
|
1819
|
+
val data = message.data
|
|
1820
|
+
if (url != null && data != null) view.webView.onMessage(data, url)
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
override fun setInjectedJavaScriptBeforeContentLoaded(view: RNCWebViewWrapper, value: String?) {
|
|
1825
|
+
scripts.remove(view)?.remove()
|
|
1826
|
+
if (value == null) return
|
|
1827
|
+
// onPageStarted injection races module execution on Android. Register with
|
|
1828
|
+
// Chromium before navigation, scoped to our virtual asset origin only.
|
|
1829
|
+
check(WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
|
|
1830
|
+
"AbsoluteJS requires an Android System WebView with document-start script support."
|
|
1831
|
+
}
|
|
1832
|
+
scripts[view] = WebViewCompat.addDocumentStartJavaScript(view.webView, value, setOf("https://" + HOST))
|
|
1833
|
+
}
|
|
1834
|
+
override fun onDropViewInstance(view: RNCWebViewWrapper) {
|
|
1835
|
+
scripts.remove(view)?.remove()
|
|
1836
|
+
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER))
|
|
1837
|
+
WebViewCompat.removeWebMessageListener(view.webView, "ReactNativeWebView")
|
|
1838
|
+
super.onDropViewInstance(view)
|
|
1839
|
+
}
|
|
1840
|
+
override fun addEventEmitters(context: ThemedReactContext, view: RNCWebViewWrapper) {
|
|
1841
|
+
Log.i("AbsoluteJS", "Expo embedded web phase: asset-host-view-created")
|
|
1842
|
+
view.webView.webViewClient = object : RNCWebViewClient() {
|
|
1843
|
+
@Volatile private var activeBundle: String? = null
|
|
1844
|
+
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
|
|
1845
|
+
Log.i("AbsoluteJS", "Expo embedded web phase: document-started")
|
|
1846
|
+
val uri = Uri.parse(url)
|
|
1847
|
+
activeBundle = if (uri.scheme == "https" && uri.host == HOST && uri.path.orEmpty().startsWith(PREFIX))
|
|
1848
|
+
uri.path!!.removePrefix(PREFIX).substringBefore('/') else null
|
|
1849
|
+
super.onPageStarted(view, url, favicon)
|
|
1850
|
+
}
|
|
1851
|
+
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? =
|
|
1852
|
+
AbsoluteEmbeddedAssets.intercept(request, activeBundle) ?: super.shouldInterceptRequest(view, request)
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
class AbsoluteEmbeddedWebViewPackage : ReactPackage {
|
|
1858
|
+
override fun createNativeModules(context: ReactApplicationContext): List<NativeModule> = emptyList()
|
|
1859
|
+
override fun createViewManagers(context: ReactApplicationContext): List<ViewManager<*, *>> = listOf(AbsoluteEmbeddedWebViewManager())
|
|
1860
|
+
}
|
|
1861
|
+
`;
|
|
1862
|
+
var plugin = `${header}const { withMainApplication } = require('expo/config-plugins');
|
|
1863
|
+
module.exports = config => withMainApplication(config, value => {
|
|
1864
|
+
if (value.modResults.language !== 'kt') throw new Error('AbsoluteJS embedded assets require the managed Kotlin application.');
|
|
1865
|
+
const name = 'expo.modules.absoluteembeddedassets.AbsoluteEmbeddedWebViewPackage';
|
|
1866
|
+
const registration = 'add(' + name + '())';
|
|
1867
|
+
const source = value.modResults.contents;
|
|
1868
|
+
if (!source.includes(registration)) {
|
|
1869
|
+
const anchor = 'PackageList(this).packages.apply {';
|
|
1870
|
+
if (source.split(anchor).length !== 2) throw new Error('Cannot safely register AbsoluteJS embedded WebView in MainApplication.');
|
|
1871
|
+
value.modResults.contents = source.replace(anchor, anchor + '\\n ' + registration);
|
|
1872
|
+
}
|
|
1873
|
+
return value;
|
|
1874
|
+
});
|
|
1875
|
+
`;
|
|
1876
|
+
var runtime2 = `${header}import { requireNativeModule } from 'expo-modules-core';
|
|
1877
|
+
import { Platform, requireNativeComponent } from 'react-native';
|
|
1878
|
+
|
|
1879
|
+
export const absoluteEmbeddedNativeConfig = Platform.OS === 'android'
|
|
1880
|
+
? { component: requireNativeComponent('AbsoluteEmbeddedWebView') }
|
|
1881
|
+
: undefined;
|
|
1882
|
+
|
|
1883
|
+
export const registerAbsoluteEmbeddedBundle = async (rootUri: string, paths: string[], indexUri: string): Promise<string> => {
|
|
1884
|
+
if (Platform.OS !== 'android') return indexUri;
|
|
1885
|
+
const native = requireNativeModule<{ registerBundle(root: string, paths: string[]): Promise<string> }>('AbsoluteEmbeddedAssets');
|
|
1886
|
+
return native.registerBundle(rootUri, paths);
|
|
1887
|
+
};
|
|
1888
|
+
`;
|
|
1889
|
+
var absoluteExpoEmbeddedAssetsFiles = (project) => {
|
|
1890
|
+
const root = join5(project, "modules/absolute-embedded-assets");
|
|
1891
|
+
return new Map([
|
|
1892
|
+
[
|
|
1893
|
+
join5(root, "expo-module.config.json"),
|
|
1894
|
+
`${JSON.stringify({
|
|
1895
|
+
android: {
|
|
1896
|
+
modules: [
|
|
1897
|
+
"expo.modules.absoluteembeddedassets.AbsoluteEmbeddedAssetsModule"
|
|
1898
|
+
]
|
|
1899
|
+
},
|
|
1900
|
+
platforms: ["android"]
|
|
1901
|
+
}, null, 2)}
|
|
1902
|
+
`
|
|
1903
|
+
],
|
|
1904
|
+
[
|
|
1905
|
+
join5(root, "android/build.gradle"),
|
|
1906
|
+
`${header}plugins { id 'com.android.library'; id 'expo-module-gradle-plugin' }
|
|
1907
|
+
group = 'expo.modules.absoluteembeddedassets'
|
|
1908
|
+
version = '0.0.1'
|
|
1909
|
+
android {
|
|
1910
|
+
namespace 'expo.modules.absoluteembeddedassets'
|
|
1911
|
+
defaultConfig { versionCode 1; versionName '0.0.1' }
|
|
1912
|
+
}
|
|
1913
|
+
dependencies {
|
|
1914
|
+
implementation 'com.facebook.react:react-android'
|
|
1915
|
+
implementation project(':react-native-webview')
|
|
1916
|
+
implementation 'androidx.webkit:webkit:1.14.0'
|
|
1917
|
+
}
|
|
1918
|
+
`
|
|
1919
|
+
],
|
|
1920
|
+
[
|
|
1921
|
+
join5(root, "android/src/main/AndroidManifest.xml"),
|
|
1922
|
+
`<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
|
|
1923
|
+
`
|
|
1924
|
+
],
|
|
1925
|
+
[
|
|
1926
|
+
join5(root, "android/src/main/java/expo/modules/absoluteembeddedassets/AbsoluteEmbeddedAssetsModule.kt"),
|
|
1927
|
+
android
|
|
1928
|
+
],
|
|
1929
|
+
[join5(project, "plugins/withAbsoluteEmbeddedAssets.js"), plugin],
|
|
1930
|
+
[join5(project, "src/generated/AbsoluteEmbeddedAssets.ts"), runtime2]
|
|
1931
|
+
]);
|
|
1932
|
+
};
|
|
1933
|
+
|
|
1674
1934
|
// src/mobile/branding.ts
|
|
1675
1935
|
import { createHash as createHash2 } from "crypto";
|
|
1676
1936
|
import {
|
|
@@ -1682,7 +1942,7 @@ import {
|
|
|
1682
1942
|
rm,
|
|
1683
1943
|
writeFile
|
|
1684
1944
|
} from "fs/promises";
|
|
1685
|
-
import { dirname as dirname2, join as
|
|
1945
|
+
import { dirname as dirname2, join as join6, relative as relative2, resolve as resolve3 } from "path";
|
|
1686
1946
|
var ABSOLUTE_MOBILE_BRANDING_FORMAT = 1;
|
|
1687
1947
|
var ABSOLUTE_CAPACITOR_ASSETS_VERSION = "3.0.5";
|
|
1688
1948
|
var ABSOLUTE_CAPACITOR_ASSETS_SPEC = `@capacitor/assets@${ABSOLUTE_CAPACITOR_ASSETS_VERSION}`;
|
|
@@ -1706,7 +1966,7 @@ var exists = async (path) => {
|
|
|
1706
1966
|
}
|
|
1707
1967
|
};
|
|
1708
1968
|
var portableRelative = (root, path) => relative2(root, path).replaceAll("\\", "/");
|
|
1709
|
-
var absoluteMobileBrandingManifestPath = (projectRoot) =>
|
|
1969
|
+
var absoluteMobileBrandingManifestPath = (projectRoot) => join6(resolve3(projectRoot), ".absolutejs", "mobile", "branding", "manifest.json");
|
|
1710
1970
|
var brandingEntries = (branding) => [
|
|
1711
1971
|
{ kind: "icon", path: branding.icon, size: ICON_SIZE },
|
|
1712
1972
|
branding.android.foreground ? {
|
|
@@ -1853,10 +2113,10 @@ var writeAbsoluteExpoBrandingInputs = async (config, projectRoot) => {
|
|
|
1853
2113
|
if (!branding)
|
|
1854
2114
|
return;
|
|
1855
2115
|
const manifest = await createAbsoluteMobileBrandingManifest(config, projectRoot);
|
|
1856
|
-
const destination =
|
|
2116
|
+
const destination = join6(config.nativeProjectDirectory, "assets", "absolute-branding");
|
|
1857
2117
|
await rm(destination, { force: true, recursive: true });
|
|
1858
2118
|
await mkdir(destination, { recursive: true });
|
|
1859
|
-
await copyManaged(branding.icon,
|
|
2119
|
+
await copyManaged(branding.icon, join6(destination, "icon.png"));
|
|
1860
2120
|
const copies = [
|
|
1861
2121
|
[branding.android.backgroundImage, "android-background.png"],
|
|
1862
2122
|
[branding.android.foreground, "android-foreground.png"],
|
|
@@ -1866,19 +2126,19 @@ var writeAbsoluteExpoBrandingInputs = async (config, projectRoot) => {
|
|
|
1866
2126
|
[branding.splash.image, "splash-light.png"],
|
|
1867
2127
|
[branding.splash.darkImage, "splash-dark.png"]
|
|
1868
2128
|
];
|
|
1869
|
-
await Promise.all(copies.map(([source, name]) => source ? copyManaged(source,
|
|
2129
|
+
await Promise.all(copies.map(([source, name]) => source ? copyManaged(source, join6(destination, name)) : undefined));
|
|
1870
2130
|
await writeManifest(projectRoot, manifest);
|
|
1871
2131
|
return { manifest, path: destination };
|
|
1872
2132
|
};
|
|
1873
2133
|
var stageCapacitorInputs = async (branding, projectRoot) => {
|
|
1874
|
-
const destination =
|
|
2134
|
+
const destination = join6(projectRoot, ".absolutejs", "mobile", "branding", "input");
|
|
1875
2135
|
await rm(destination, { force: true, recursive: true });
|
|
1876
2136
|
await mkdir(destination, { recursive: true });
|
|
1877
|
-
await copyManaged(branding.icon,
|
|
2137
|
+
await copyManaged(branding.icon, join6(destination, "logo.png"));
|
|
1878
2138
|
if (branding.splash.image)
|
|
1879
|
-
await copyManaged(branding.splash.image,
|
|
2139
|
+
await copyManaged(branding.splash.image, join6(destination, "splash.png"));
|
|
1880
2140
|
if (branding.splash.darkImage)
|
|
1881
|
-
await copyManaged(branding.splash.darkImage,
|
|
2141
|
+
await copyManaged(branding.splash.darkImage, join6(destination, "splash-dark.png"));
|
|
1882
2142
|
return destination;
|
|
1883
2143
|
};
|
|
1884
2144
|
var defaultRunner = async (command, options) => {
|
|
@@ -1909,16 +2169,16 @@ var resizePng = async (source, destination, size) => {
|
|
|
1909
2169
|
await writeFile(destination, output);
|
|
1910
2170
|
};
|
|
1911
2171
|
var applyAndroidLayers = async (branding, nativeDirectory) => {
|
|
1912
|
-
const resources =
|
|
2172
|
+
const resources = join6(nativeDirectory, "android", "app", "src", "main", "res");
|
|
1913
2173
|
await Promise.all(ANDROID_ADAPTIVE_SIZES.flatMap(([density, size]) => [
|
|
1914
2174
|
...branding.android.foreground ? [
|
|
1915
|
-
resizePng(branding.android.foreground,
|
|
2175
|
+
resizePng(branding.android.foreground, join6(resources, `mipmap-${density}`, "ic_launcher_foreground.png"), size)
|
|
1916
2176
|
] : [],
|
|
1917
2177
|
...branding.android.backgroundImage ? [
|
|
1918
|
-
resizePng(branding.android.backgroundImage,
|
|
2178
|
+
resizePng(branding.android.backgroundImage, join6(resources, `mipmap-${density}`, "ic_launcher_background.png"), size)
|
|
1919
2179
|
] : [],
|
|
1920
2180
|
...branding.android.monochrome ? [
|
|
1921
|
-
resizePng(branding.android.monochrome,
|
|
2181
|
+
resizePng(branding.android.monochrome, join6(resources, `mipmap-${density}`, "ic_launcher_monochrome.png"), size)
|
|
1922
2182
|
] : []
|
|
1923
2183
|
]));
|
|
1924
2184
|
if (!branding.android.monochrome)
|
|
@@ -1930,13 +2190,13 @@ var applyAndroidLayers = async (branding, nativeDirectory) => {
|
|
|
1930
2190
|
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
|
|
1931
2191
|
</adaptive-icon>
|
|
1932
2192
|
`;
|
|
1933
|
-
await Promise.all(["ic_launcher.xml", "ic_launcher_round.xml"].map((name) => writeFile(
|
|
2193
|
+
await Promise.all(["ic_launcher.xml", "ic_launcher_round.xml"].map((name) => writeFile(join6(resources, "mipmap-anydpi-v26", name), iconXml)));
|
|
1934
2194
|
};
|
|
1935
2195
|
var applyIosAppearances = async (branding, nativeDirectory) => {
|
|
1936
2196
|
if (!branding.ios.darkIcon && !branding.ios.tintedIcon)
|
|
1937
2197
|
return;
|
|
1938
|
-
const assetSet =
|
|
1939
|
-
const contentsPath =
|
|
2198
|
+
const assetSet = join6(nativeDirectory, "ios", "App", "App", "Assets.xcassets", "AppIcon.appiconset");
|
|
2199
|
+
const contentsPath = join6(assetSet, "Contents.json");
|
|
1940
2200
|
const contents = JSON.parse(await readFile(contentsPath, "utf8"));
|
|
1941
2201
|
const images = typeof contents === "object" && contents !== null ? Reflect.get(contents, "images") : undefined;
|
|
1942
2202
|
if (typeof contents !== "object" || contents === null || !Array.isArray(images))
|
|
@@ -1949,7 +2209,7 @@ var applyIosAppearances = async (branding, nativeDirectory) => {
|
|
|
1949
2209
|
if (!source)
|
|
1950
2210
|
return;
|
|
1951
2211
|
const filename = `AppIcon-1024-${appearance}.png`;
|
|
1952
|
-
await resizePng(source,
|
|
2212
|
+
await resizePng(source, join6(assetSet, filename), ICON_SIZE);
|
|
1953
2213
|
return {
|
|
1954
2214
|
appearances: [{ appearance: "luminosity", value: appearance }],
|
|
1955
2215
|
filename,
|
|
@@ -1971,7 +2231,7 @@ var applyIosAppearances = async (branding, nativeDirectory) => {
|
|
|
1971
2231
|
await writeFile(contentsPath, `${JSON.stringify(contents, null, 2)}
|
|
1972
2232
|
`);
|
|
1973
2233
|
};
|
|
1974
|
-
var nativeBrandingSentinels = (config, platforms) => platforms.map((platform) => platform === "android" ?
|
|
2234
|
+
var nativeBrandingSentinels = (config, platforms) => platforms.map((platform) => platform === "android" ? join6(config.nativeProjectDirectory, "android", "app", "src", "main", "res", "mipmap-xxxhdpi", "ic_launcher.png") : join6(config.nativeProjectDirectory, "ios", "App", "App", "Assets.xcassets", "AppIcon.appiconset", "AppIcon-512@2x.png"));
|
|
1975
2235
|
var requireGeneratedBranding = async (config, platforms) => {
|
|
1976
2236
|
const missing = (await Promise.all(nativeBrandingSentinels(config, platforms).map(async (path) => ({
|
|
1977
2237
|
path,
|
|
@@ -2016,9 +2276,9 @@ var generateAbsoluteCapacitorBranding = async (config, projectRoot, options = {}
|
|
|
2016
2276
|
"--assetPath",
|
|
2017
2277
|
portableRelative(projectRoot, input),
|
|
2018
2278
|
"--androidProject",
|
|
2019
|
-
portableRelative(projectRoot,
|
|
2279
|
+
portableRelative(projectRoot, join6(config.nativeProjectDirectory, "android")),
|
|
2020
2280
|
"--iosProject",
|
|
2021
|
-
portableRelative(projectRoot,
|
|
2281
|
+
portableRelative(projectRoot, join6(config.nativeProjectDirectory, "ios", "App")),
|
|
2022
2282
|
"--iconBackgroundColor",
|
|
2023
2283
|
branding.android.backgroundColor,
|
|
2024
2284
|
"--splashBackgroundColor",
|
|
@@ -2099,7 +2359,7 @@ var inspectAbsoluteMobileBranding = async (config, projectRoot) => {
|
|
|
2099
2359
|
status: "stale"
|
|
2100
2360
|
});
|
|
2101
2361
|
const nativeSentinels = config.engine === "expo" ? [
|
|
2102
|
-
|
|
2362
|
+
join6(config.nativeProjectDirectory, "assets", "absolute-branding", "icon.png")
|
|
2103
2363
|
] : nativeBrandingSentinels(config, config.platforms);
|
|
2104
2364
|
if ((await Promise.all(nativeSentinels.map((path) => exists(path)))).some((present) => !present))
|
|
2105
2365
|
return brandingInspection({
|
|
@@ -2132,7 +2392,7 @@ var renderAbsoluteMobileBrandingPreview = async (config, projectRoot) => {
|
|
|
2132
2392
|
const { branding } = config;
|
|
2133
2393
|
if (!branding)
|
|
2134
2394
|
throw new TypeError("mobile.branding is not configured.");
|
|
2135
|
-
const destination =
|
|
2395
|
+
const destination = join6(projectRoot, ".absolutejs", "mobile", "branding", "preview.html");
|
|
2136
2396
|
const icon = `data:image/png;base64,${(await readFile(branding.icon)).toString("base64")}`;
|
|
2137
2397
|
const appName = escapeHtml(config.appName);
|
|
2138
2398
|
const html = `<!doctype html>
|
|
@@ -2227,7 +2487,7 @@ var routeSegments = (route) => {
|
|
|
2227
2487
|
return segment;
|
|
2228
2488
|
});
|
|
2229
2489
|
};
|
|
2230
|
-
var routeFile = (project, route) =>
|
|
2490
|
+
var routeFile = (project, route) => join7(project, "app", ...routeSegments(route), "index.tsx");
|
|
2231
2491
|
var packageDependencies = (plan) => Object.fromEntries(plan.requiredPackages.map((spec) => {
|
|
2232
2492
|
const separator = spec.lastIndexOf("@");
|
|
2233
2493
|
return [spec.slice(0, separator), spec.slice(separator + 1)];
|
|
@@ -2316,24 +2576,24 @@ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
|
|
|
2316
2576
|
const devicePlugins = [
|
|
2317
2577
|
...new Set(devices.capabilities.flatMap((name) => devices.providers[name]?.plugins ?? []))
|
|
2318
2578
|
];
|
|
2319
|
-
const configuredPlugins = devicePlugins.map((
|
|
2320
|
-
if (
|
|
2579
|
+
const configuredPlugins = devicePlugins.map((plugin2) => {
|
|
2580
|
+
if (plugin2 === "expo-image-picker")
|
|
2321
2581
|
return [
|
|
2322
|
-
|
|
2582
|
+
plugin2,
|
|
2323
2583
|
{
|
|
2324
2584
|
cameraPermission: descriptions.NSCameraUsageDescription,
|
|
2325
2585
|
microphonePermission: false,
|
|
2326
2586
|
photosPermission: descriptions.NSPhotoLibraryUsageDescription
|
|
2327
2587
|
}
|
|
2328
2588
|
];
|
|
2329
|
-
if (
|
|
2589
|
+
if (plugin2 === "expo-location")
|
|
2330
2590
|
return [
|
|
2331
|
-
|
|
2591
|
+
plugin2,
|
|
2332
2592
|
{
|
|
2333
2593
|
locationWhenInUsePermission: descriptions.NSLocationWhenInUseUsageDescription
|
|
2334
2594
|
}
|
|
2335
2595
|
];
|
|
2336
|
-
return
|
|
2596
|
+
return plugin2;
|
|
2337
2597
|
});
|
|
2338
2598
|
return {
|
|
2339
2599
|
expo: {
|
|
@@ -2355,7 +2615,7 @@ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
|
|
|
2355
2615
|
package: config.appId,
|
|
2356
2616
|
permissions: requirements.androidPermissions
|
|
2357
2617
|
},
|
|
2358
|
-
experiments: { typedRoutes: true },
|
|
2618
|
+
experiments: { tsconfigPaths: false, typedRoutes: true },
|
|
2359
2619
|
ios: {
|
|
2360
2620
|
...branding?.ios ?? {},
|
|
2361
2621
|
associatedDomains: config.deepLinkHosts.map((host2) => `applinks:${host2}`),
|
|
@@ -2378,6 +2638,7 @@ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
|
|
|
2378
2638
|
["expo-dev-client", { launchMode: "most-recent" }],
|
|
2379
2639
|
...branding ? [branding.splashPlugin] : [],
|
|
2380
2640
|
"./plugins/withAbsoluteActivityResultRecovery",
|
|
2641
|
+
"./plugins/withAbsoluteEmbeddedAssets",
|
|
2381
2642
|
...auth || config.updates ? ["expo-secure-store"] : [],
|
|
2382
2643
|
...sync ? [
|
|
2383
2644
|
"expo-sqlite",
|
|
@@ -2942,6 +3203,7 @@ import { ActivityIndicator, BackHandler, Platform, Pressable, StyleSheet, Text,
|
|
|
2942
3203
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
2943
3204
|
import { WebView, type WebViewMessageEvent } from 'react-native-webview';
|
|
2944
3205
|
import { materializeAbsoluteWebBundle } from './webAssets';
|
|
3206
|
+
import { absoluteEmbeddedNativeConfig } from './AbsoluteEmbeddedAssets';
|
|
2945
3207
|
import { markAbsoluteEmbeddedWebPhase, markAbsoluteEmbeddedWebReady } from './AbsoluteActivityResultRecovery';
|
|
2946
3208
|
import { createExpoDevicesBridgeHost } from '@absolutejs/devices-expo/bridge';
|
|
2947
3209
|
import { absoluteExpoDevices, beforeAbsoluteExpoDeviceSignOut } from './AbsoluteDevices';
|
|
@@ -3061,7 +3323,16 @@ const bridgeBootstrap = (path: string, safeAreaInsets: { bottom: number; left: n
|
|
|
3061
3323
|
event.preventDefault();
|
|
3062
3324
|
send({ format: 3, kind: 'event', event: 'navigation', path: url.pathname + url.search + url.hash });
|
|
3063
3325
|
}, true);
|
|
3064
|
-
|
|
3326
|
+
let shellRendered = false;
|
|
3327
|
+
addEventListener('absolute:shell-rendered', () => {
|
|
3328
|
+
shellRendered = true;
|
|
3329
|
+
send({ format: 3, kind: 'event', event: 'shell-rendered', path: currentPath });
|
|
3330
|
+
});
|
|
3331
|
+
const startupFailed = () => {
|
|
3332
|
+
if (!shellRendered) send({ format: 3, kind: 'event', event: 'startup-failed', path: currentPath });
|
|
3333
|
+
};
|
|
3334
|
+
addEventListener('error', startupFailed);
|
|
3335
|
+
addEventListener('unhandledrejection', startupFailed);
|
|
3065
3336
|
})(); true;\`;
|
|
3066
3337
|
};
|
|
3067
3338
|
|
|
@@ -3106,11 +3377,18 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3106
3377
|
const canGoBack = useRef(false);
|
|
3107
3378
|
const [runtimeReady, setRuntimeReady] = useState(!AUTH_ENABLED && !SYNC_ENABLED);
|
|
3108
3379
|
const [devicesReady, setDevicesReady] = useState(false);
|
|
3109
|
-
const [startupError, setStartupError] = useState<'assets' | 'devices'>();
|
|
3380
|
+
const [startupError, setStartupError] = useState<'assets' | 'devices' | 'content'>();
|
|
3110
3381
|
const [startupRevision, setStartupRevision] = useState(0);
|
|
3111
3382
|
const activeWebPath = useRef(pathname);
|
|
3112
3383
|
const embeddedReadyReported = useRef(false);
|
|
3113
3384
|
const webSource = useMemo(() => indexUri ? { uri: indexUri } : undefined, [indexUri]);
|
|
3385
|
+
useEffect(() => {
|
|
3386
|
+
if (!indexUri || DEV_ORIGIN) return;
|
|
3387
|
+
const timeout = setTimeout(() => {
|
|
3388
|
+
if (!embeddedReadyReported.current) setStartupError('content');
|
|
3389
|
+
}, 60000);
|
|
3390
|
+
return () => clearTimeout(timeout);
|
|
3391
|
+
}, [indexUri]);
|
|
3114
3392
|
|
|
3115
3393
|
useEffect(() => {
|
|
3116
3394
|
if (!AUTH_ENABLED && !SYNC_ENABLED) return;
|
|
@@ -3146,6 +3424,7 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3146
3424
|
markAbsoluteEmbeddedWebPhase('assets-start');
|
|
3147
3425
|
void materializeAbsoluteWebBundle().then(uri => {
|
|
3148
3426
|
markAbsoluteEmbeddedWebPhase('assets-ready');
|
|
3427
|
+
embeddedReadyReported.current = false;
|
|
3149
3428
|
setIndexUri(uri + '?absolutePath=' + encodeURIComponent(pathname));
|
|
3150
3429
|
}).catch(error => {
|
|
3151
3430
|
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 +3478,23 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3199
3478
|
try { return new URL(source).origin === origin; } catch { return false; }
|
|
3200
3479
|
};
|
|
3201
3480
|
const onMessage = async (event: WebViewMessageEvent) => {
|
|
3481
|
+
// Bridge authority belongs only to the currently loaded embedded bundle
|
|
3482
|
+
// (or the explicitly configured development origin), never remote pages.
|
|
3483
|
+
const sourceUrl = event.nativeEvent.url;
|
|
3484
|
+
if (!indexUri || typeof sourceUrl !== 'string' || (DEV_ORIGIN ? !hasOrigin(sourceUrl, DEV_ORIGIN) : !sourceUrl.startsWith(indexUri.slice(0, indexUri.lastIndexOf('/') + 1)))) return;
|
|
3202
3485
|
const source = event.nativeEvent.data;
|
|
3203
3486
|
if (new TextEncoder().encode(source).byteLength > MAX_MESSAGE_BYTES) return;
|
|
3204
3487
|
let message: Record<string, unknown>;
|
|
3205
3488
|
try { message = JSON.parse(source); } catch { return; }
|
|
3206
3489
|
if (message.format !== BRIDGE_FORMAT || typeof message.path !== 'string' || !message.path.startsWith('/') || message.path.startsWith('//')) return;
|
|
3207
|
-
if (message.kind === 'event' &&
|
|
3490
|
+
if (message.kind === 'event' && message.event === 'startup-failed') {
|
|
3491
|
+
if (!embeddedReadyReported.current) setStartupError('content');
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
if (message.kind === 'event' && (message.event === 'navigation' || message.event === 'shell-rendered')) {
|
|
3208
3495
|
const target = new URL(message.path, PRODUCTION_ORIGIN);
|
|
3209
3496
|
if (target.origin !== PRODUCTION_ORIGIN) return;
|
|
3210
|
-
if (message.event === '
|
|
3497
|
+
if (message.event === 'shell-rendered' && !DEV_ORIGIN && !embeddedReadyReported.current) {
|
|
3211
3498
|
embeddedReadyReported.current = true;
|
|
3212
3499
|
markAbsoluteEmbeddedWebReady();
|
|
3213
3500
|
}
|
|
@@ -3261,12 +3548,17 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3261
3548
|
</View>;
|
|
3262
3549
|
if (!webSource || !runtimeReady || !devicesReady) return <View style={styles.loading}><ActivityIndicator /></View>;
|
|
3263
3550
|
return <WebView
|
|
3264
|
-
|
|
3265
|
-
|
|
3551
|
+
nativeConfig={!DEV_ORIGIN ? absoluteEmbeddedNativeConfig : undefined}
|
|
3552
|
+
allowFileAccess={Platform.OS !== 'android'}
|
|
3553
|
+
allowFileAccessFromFileURLs={Platform.OS !== 'android'}
|
|
3266
3554
|
allowUniversalAccessFromFileURLs={false}
|
|
3555
|
+
originWhitelist={DEV_ORIGIN ? [DEV_ORIGIN] : [Platform.OS === 'android' ? 'https://appassets.androidplatform.net' : 'file://*']}
|
|
3267
3556
|
allowingReadAccessToURL={webSource.uri.slice(0, webSource.uri.lastIndexOf('/') + 1)}
|
|
3268
3557
|
injectedJavaScriptBeforeContentLoaded={bridgeBootstrap(pathname, safeAreaInsets)}
|
|
3269
3558
|
onMessage={onMessage}
|
|
3559
|
+
onError={() => setStartupError('content')}
|
|
3560
|
+
onRenderProcessGone={() => setStartupError('content')}
|
|
3561
|
+
onContentProcessDidTerminate={() => setStartupError('content')}
|
|
3270
3562
|
onNavigationStateChange={state => {
|
|
3271
3563
|
canGoBack.current = state.canGoBack;
|
|
3272
3564
|
if (!DEV_ORIGIN || !hasOrigin(state.url, DEV_ORIGIN)) return;
|
|
@@ -3274,8 +3566,8 @@ export function AbsoluteWebHost({ path }: AbsoluteWebHostProps = {}) {
|
|
|
3274
3566
|
activeWebPath.current = route.pathname + route.search + route.hash;
|
|
3275
3567
|
}}
|
|
3276
3568
|
onShouldStartLoadWithRequest={request => {
|
|
3277
|
-
if (
|
|
3278
|
-
void Linking.openURL(request.url);
|
|
3569
|
+
if (DEV_ORIGIN ? hasOrigin(request.url, DEV_ORIGIN) : request.url.startsWith(webSource.uri.slice(0, webSource.uri.lastIndexOf('/') + 1))) return true;
|
|
3570
|
+
if (/^https?:/.test(request.url)) void Linking.openURL(request.url);
|
|
3279
3571
|
return false;
|
|
3280
3572
|
}}
|
|
3281
3573
|
ref={webView}
|
|
@@ -3478,8 +3770,8 @@ const styles = StyleSheet.create({
|
|
|
3478
3770
|
error: { color: '#b91c1c', fontSize: 16, textAlign: 'center' }
|
|
3479
3771
|
});
|
|
3480
3772
|
`;
|
|
3481
|
-
var nativeWrapperSource = (wrapper, module,
|
|
3482
|
-
import { createAbsoluteNativeRoute, createAbsoluteNativeRouteErrorBoundary } from ${JSON.stringify(portableRelative2(dirname3(wrapper),
|
|
3773
|
+
var nativeWrapperSource = (wrapper, module, runtime3, route) => `${EXPO_GENERATED_HEADER}import ApplicationNativeRoute from ${JSON.stringify(portableRelative2(dirname3(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
|
|
3774
|
+
import { createAbsoluteNativeRoute, createAbsoluteNativeRouteErrorBoundary } from ${JSON.stringify(portableRelative2(dirname3(wrapper), runtime3).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
|
|
3483
3775
|
|
|
3484
3776
|
export const ErrorBoundary = createAbsoluteNativeRouteErrorBoundary(${JSON.stringify(route)});
|
|
3485
3777
|
export default createAbsoluteNativeRoute(ApplicationNativeRoute, ${JSON.stringify(route)});
|
|
@@ -3548,7 +3840,7 @@ var pruneManagedFiles = async (paths) => {
|
|
|
3548
3840
|
return removed.filter(Boolean).length;
|
|
3549
3841
|
};
|
|
3550
3842
|
var pruneStaleManagedExpoRoutes = async (project, expected) => {
|
|
3551
|
-
const appDirectory =
|
|
3843
|
+
const appDirectory = join7(project, "app");
|
|
3552
3844
|
if (!await exists2(appDirectory))
|
|
3553
3845
|
return 0;
|
|
3554
3846
|
const files = await walkFiles(appDirectory);
|
|
@@ -3596,7 +3888,7 @@ var writeAbsoluteExpoProject = async (config, options) => {
|
|
|
3596
3888
|
if (missing) {
|
|
3597
3889
|
throw new TypeError(`Expo native route ${missing.route} references missing module ${missing.module}.`);
|
|
3598
3890
|
}
|
|
3599
|
-
const marker =
|
|
3891
|
+
const marker = join7(project, EXPO_PROJECT_MARKER);
|
|
3600
3892
|
if (await exists2(project) && !await exists2(marker)) {
|
|
3601
3893
|
const entries = await readdir(project);
|
|
3602
3894
|
if (entries.length > 0 && !options.force) {
|
|
@@ -3609,7 +3901,7 @@ var writeAbsoluteExpoProject = async (config, options) => {
|
|
|
3609
3901
|
const authEnabled = Boolean(auth);
|
|
3610
3902
|
const files = new Map([
|
|
3611
3903
|
[
|
|
3612
|
-
|
|
3904
|
+
join7(project, ".gitignore"),
|
|
3613
3905
|
`.expo/
|
|
3614
3906
|
android/
|
|
3615
3907
|
ios/
|
|
@@ -3617,80 +3909,82 @@ node_modules/
|
|
|
3617
3909
|
`
|
|
3618
3910
|
],
|
|
3619
3911
|
[
|
|
3620
|
-
|
|
3912
|
+
join7(project, "app.json"),
|
|
3621
3913
|
jsonSource(expoAppConfig(config, authEnabled, syncEnabled, devices, runtimeFingerprint))
|
|
3622
3914
|
],
|
|
3623
|
-
[
|
|
3915
|
+
[join7(project, "app.config.js"), expoDynamicAppConfig],
|
|
3624
3916
|
[
|
|
3625
|
-
|
|
3917
|
+
join7(project, "package.json"),
|
|
3626
3918
|
jsonSource(expoPackage(authEnabled, config.branding !== undefined, syncEnabled, devices, config.updates !== undefined))
|
|
3627
3919
|
],
|
|
3628
|
-
[
|
|
3920
|
+
[join7(project, "metro.config.js"), metroConfig(projectRoot)],
|
|
3629
3921
|
[
|
|
3630
|
-
|
|
3922
|
+
join7(project, "plugins", "withAbsoluteDevelopmentCa.js"),
|
|
3631
3923
|
expoDevelopmentCaPlugin
|
|
3632
3924
|
],
|
|
3633
3925
|
[
|
|
3634
|
-
|
|
3926
|
+
join7(project, "plugins", "withAbsoluteActivityResultRecovery.js"),
|
|
3635
3927
|
expoActivityResultRecoveryPlugin
|
|
3636
3928
|
],
|
|
3637
3929
|
[
|
|
3638
|
-
|
|
3930
|
+
join7(project, "tsconfig.json"),
|
|
3639
3931
|
jsonSource(expoTsConfig(projectRoot, project, authEnabled, syncEnabled))
|
|
3640
3932
|
],
|
|
3641
3933
|
[
|
|
3642
|
-
|
|
3934
|
+
join7(project, "app", "_layout.tsx"),
|
|
3643
3935
|
layoutSource(authEnabled, syncEnabled, config.updates !== undefined, config.observability !== undefined)
|
|
3644
3936
|
],
|
|
3645
3937
|
[
|
|
3646
|
-
|
|
3938
|
+
join7(project, "app", "__absolute", "native", "index.tsx"),
|
|
3647
3939
|
nativeDiagnosticSource
|
|
3648
3940
|
],
|
|
3649
3941
|
[
|
|
3650
|
-
|
|
3942
|
+
join7(project, "src", "generated", "AbsoluteDevices.ts"),
|
|
3651
3943
|
devicesRuntimeSource(config, devices, authEnabled)
|
|
3652
3944
|
],
|
|
3653
3945
|
[
|
|
3654
|
-
|
|
3946
|
+
join7(project, "src", "generated", "AbsoluteNativeRoute.tsx"),
|
|
3655
3947
|
nativeRouteRuntimeSource(authEnabled)
|
|
3656
3948
|
],
|
|
3657
3949
|
[
|
|
3658
|
-
|
|
3950
|
+
join7(project, "src", "generated", "AbsoluteWebHost.tsx"),
|
|
3659
3951
|
webHostSource(config, auth, syncEnabled)
|
|
3660
3952
|
]
|
|
3661
3953
|
]);
|
|
3662
3954
|
for (const [path, source] of absoluteExpoActivityResultRecoveryFiles(project))
|
|
3663
3955
|
files.set(path, source);
|
|
3956
|
+
for (const [path, source] of absoluteExpoEmbeddedAssetsFiles(project))
|
|
3957
|
+
files.set(path, source);
|
|
3664
3958
|
if (config.observability) {
|
|
3665
3959
|
for (const [path, source] of absoluteExpoNativeObservabilityFiles(project))
|
|
3666
3960
|
files.set(path, source);
|
|
3667
3961
|
}
|
|
3668
3962
|
if (config.updates) {
|
|
3669
|
-
files.set(
|
|
3963
|
+
files.set(join7(project, "src", "generated", "AbsoluteUpdates.ts"), updatesRuntimeSource(config));
|
|
3670
3964
|
}
|
|
3671
3965
|
const expoCodeSigning = config.updates?.expoCodeSigning;
|
|
3672
3966
|
if (expoCodeSigning)
|
|
3673
|
-
files.set(
|
|
3967
|
+
files.set(join7(project, "certs", "absolute-mobile-update.pem"), expoCodeSigning.certificatePem);
|
|
3674
3968
|
if (auth) {
|
|
3675
|
-
files.set(
|
|
3969
|
+
files.set(join7(project, "src", "generated", "AbsoluteAuth.ts"), authRuntimeSource(auth, config.appId));
|
|
3676
3970
|
}
|
|
3677
3971
|
if (syncSchema) {
|
|
3678
|
-
files.set(
|
|
3972
|
+
files.set(join7(project, "src", "generated", "AbsoluteSync.ts"), syncRuntimeSource(config, syncSchema));
|
|
3679
3973
|
}
|
|
3680
|
-
const webAssetsPath =
|
|
3974
|
+
const webAssetsPath = join7(project, "src", "generated", "webAssets.ts");
|
|
3681
3975
|
if (!await exists2(webAssetsPath)) {
|
|
3682
3976
|
files.set(webAssetsPath, emptyWebAssetsSource);
|
|
3683
3977
|
}
|
|
3684
3978
|
if (!config.expoNativeRoutes["/"]) {
|
|
3685
|
-
files.set(
|
|
3979
|
+
files.set(join7(project, "app", "index.tsx"), webRouteSource);
|
|
3686
3980
|
}
|
|
3687
|
-
files.set(
|
|
3688
|
-
const nativeRouteRuntime =
|
|
3981
|
+
files.set(join7(project, "app", "[...absolute].tsx"), catchAllRouteSource);
|
|
3982
|
+
const nativeRouteRuntime = join7(project, "src", "generated", "AbsoluteNativeRoute.tsx");
|
|
3689
3983
|
for (const [route, module] of routeModules) {
|
|
3690
|
-
const wrapper = route === "/" ?
|
|
3984
|
+
const wrapper = route === "/" ? join7(project, "app", "index.tsx") : routeFile(project, route);
|
|
3691
3985
|
files.set(wrapper, nativeWrapperSource(wrapper, module, nativeRouteRuntime, route));
|
|
3692
3986
|
}
|
|
3693
|
-
const removedRoutes = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${
|
|
3987
|
+
const removedRoutes = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${join7(project, "app")}${sep}`))));
|
|
3694
3988
|
const removedObservability = config.observability ? 0 : await pruneManagedFiles(absoluteExpoNativeObservabilityPaths(project));
|
|
3695
3989
|
const changes = await Promise.all([...files].map(([path, source]) => writeManagedFile(path, source, true)));
|
|
3696
3990
|
await writeAbsoluteExpoBrandingInputs(config, projectRoot);
|
|
@@ -3700,7 +3994,7 @@ node_modules/
|
|
|
3700
3994
|
var walkFiles = async (root, directory = root) => {
|
|
3701
3995
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
3702
3996
|
const nested = await Promise.all(entries.map((entry) => {
|
|
3703
|
-
const path =
|
|
3997
|
+
const path = join7(directory, entry.name);
|
|
3704
3998
|
if (entry.isDirectory())
|
|
3705
3999
|
return walkFiles(root, path);
|
|
3706
4000
|
if (entry.isFile())
|
|
@@ -3733,13 +4027,16 @@ var installStagedDirectory = async (staging, destination) => {
|
|
|
3733
4027
|
throw error;
|
|
3734
4028
|
}
|
|
3735
4029
|
};
|
|
3736
|
-
var assetModuleSource = (assets, archiveAsset, bundleId, manifest) => `${EXPO_GENERATED_HEADER}import { Asset } from 'expo-asset';
|
|
4030
|
+
var assetModuleSource = (assets, archiveAsset, bundleId, archiveMd5, manifest) => `${EXPO_GENERATED_HEADER}import { Asset } from 'expo-asset';
|
|
3737
4031
|
import { Directory, File, Paths } from 'expo-file-system';
|
|
3738
4032
|
import { copyAsync as copyLegacyAsync } from 'expo-file-system/legacy';
|
|
4033
|
+
import { registerAbsoluteEmbeddedBundle } from './AbsoluteEmbeddedAssets';
|
|
3739
4034
|
|
|
3740
4035
|
${nativeDataManifestTypeSource}
|
|
3741
4036
|
declare const require: (path: string) => number;
|
|
3742
4037
|
const BUNDLE_ID = ${JSON.stringify(bundleId)};
|
|
4038
|
+
const ARCHIVE_MD5 = ${JSON.stringify(archiveMd5)};
|
|
4039
|
+
const ARCHIVE_LENGTH = ${assets.reduce((sum, entry) => sum + entry.length, 0)};
|
|
3743
4040
|
export const ABSOLUTE_MOBILE_MANIFEST: AbsoluteMobileManifest = ${JSON.stringify(manifest)};
|
|
3744
4041
|
const ARCHIVE_MODULE = require(${JSON.stringify(archiveAsset)});
|
|
3745
4042
|
const ASSETS = [
|
|
@@ -3756,10 +4053,12 @@ export const materializeAbsoluteWebBundle = async () => {
|
|
|
3756
4053
|
let archive: File;
|
|
3757
4054
|
try { archive = new File(root, '.absolutejs-bundle.absasset'); }
|
|
3758
4055
|
catch { throw new Error('assets-destination-failed'); }
|
|
4056
|
+
if (archive.exists && (archive.size !== ARCHIVE_LENGTH || archive.md5 !== ARCHIVE_MD5)) archive.delete();
|
|
3759
4057
|
if (!archive.exists) {
|
|
3760
4058
|
let asset: Asset;
|
|
3761
4059
|
try { asset = Asset.fromModule(ARCHIVE_MODULE); }
|
|
3762
4060
|
catch { throw new Error('assets-module-failed'); }
|
|
4061
|
+
if (asset.type !== 'absasset' || asset.hash !== ARCHIVE_MD5) throw new Error('assets-identity-failed');
|
|
3763
4062
|
try { if (!asset.localUri) asset = await asset.downloadAsync(); }
|
|
3764
4063
|
catch { throw new Error('assets-download-failed'); }
|
|
3765
4064
|
if (!asset.localUri) throw new Error('assets-download-failed');
|
|
@@ -3772,9 +4071,16 @@ export const materializeAbsoluteWebBundle = async () => {
|
|
|
3772
4071
|
catch { throw new Error('assets-copy-failed'); }
|
|
3773
4072
|
}
|
|
3774
4073
|
}
|
|
4074
|
+
// Checksums detect accidental corruption/registry mismatches, not authenticity;
|
|
4075
|
+
// authenticity comes from the signed application/update containing this table.
|
|
4076
|
+
if (archive.size !== ARCHIVE_LENGTH || archive.md5 !== ARCHIVE_MD5) {
|
|
4077
|
+
archive.delete();
|
|
4078
|
+
throw new Error('assets-integrity-failed');
|
|
4079
|
+
}
|
|
3775
4080
|
let contents: Uint8Array;
|
|
3776
4081
|
try { contents = await archive.bytes(); }
|
|
3777
4082
|
catch { throw new Error('assets-read-failed'); }
|
|
4083
|
+
if (contents.byteLength !== ARCHIVE_LENGTH) throw new Error('assets-integrity-failed');
|
|
3778
4084
|
for (const entry of ASSETS) {
|
|
3779
4085
|
const parts = entry.path.split('/');
|
|
3780
4086
|
const name = parts.pop();
|
|
@@ -3788,27 +4094,28 @@ export const materializeAbsoluteWebBundle = async () => {
|
|
|
3788
4094
|
let destination: File;
|
|
3789
4095
|
try {
|
|
3790
4096
|
destination = new File(directory, name);
|
|
3791
|
-
if (destination.exists && destination.size === entry.length) continue;
|
|
4097
|
+
if (destination.exists && destination.size === entry.length && destination.md5 === entry.md5) continue;
|
|
3792
4098
|
if (destination.exists) destination.delete();
|
|
3793
4099
|
} catch { throw new Error('assets-destination-failed'); }
|
|
3794
4100
|
try { destination.write(contents.slice(entry.offset, entry.offset + entry.length)); }
|
|
3795
4101
|
catch { throw new Error('assets-write-failed'); }
|
|
4102
|
+
if (destination.size !== entry.length || destination.md5 !== entry.md5) throw new Error('assets-integrity-failed');
|
|
3796
4103
|
}
|
|
3797
4104
|
try { archive.delete(); }
|
|
3798
4105
|
catch { throw new Error('assets-finalize-failed'); }
|
|
3799
4106
|
|
|
3800
|
-
try { return new File(root, 'index.html').uri; }
|
|
4107
|
+
try { return await registerAbsoluteEmbeddedBundle(root.uri, ASSETS.map(entry => entry.path), new File(root, 'index.html').uri); }
|
|
3801
4108
|
catch { throw new Error('assets-finalize-failed'); }
|
|
3802
4109
|
};
|
|
3803
4110
|
`;
|
|
3804
4111
|
var syncAbsoluteExpoWebAssets = async (config) => {
|
|
3805
4112
|
if (config.engine !== "expo")
|
|
3806
4113
|
throw new TypeError("Expo asset sync requires mobile.engine: expo.");
|
|
3807
|
-
const marker =
|
|
4114
|
+
const marker = join7(config.nativeProjectDirectory, EXPO_PROJECT_MARKER);
|
|
3808
4115
|
if (!await exists2(marker)) {
|
|
3809
4116
|
throw new TypeError("Expo asset sync requires an AbsoluteJS-managed Expo project. Run mobile init first.");
|
|
3810
4117
|
}
|
|
3811
|
-
const manifestPath =
|
|
4118
|
+
const manifestPath = join7(config.bundleDirectory, "absolute-mobile-manifest.json");
|
|
3812
4119
|
const manifest = JSON.parse(await readFile2(manifestPath, "utf8"));
|
|
3813
4120
|
const nativeDataManifest = expoNativeDataManifest(manifest);
|
|
3814
4121
|
const { appBuild } = nativeDataManifest;
|
|
@@ -3822,16 +4129,18 @@ var syncAbsoluteExpoWebAssets = async (config) => {
|
|
|
3822
4129
|
bundleHash.update("\x00");
|
|
3823
4130
|
});
|
|
3824
4131
|
const bundleId = `amexpo_${bundleHash.digest("hex")}`;
|
|
3825
|
-
const destination =
|
|
4132
|
+
const destination = join7(config.nativeProjectDirectory, "assets", "absolute");
|
|
3826
4133
|
await mkdir2(dirname3(destination), { recursive: true });
|
|
3827
|
-
const staging = await mkdtemp(
|
|
4134
|
+
const staging = await mkdtemp(join7(dirname3(destination), `.${basename(destination)}.stage-`));
|
|
3828
4135
|
let assets;
|
|
3829
4136
|
let archiveAsset;
|
|
4137
|
+
let archiveMd5;
|
|
3830
4138
|
try {
|
|
3831
4139
|
let offset = 0;
|
|
3832
4140
|
assets = filesWithContents.map(({ contents, file }) => {
|
|
3833
4141
|
const entry = {
|
|
3834
4142
|
length: contents.byteLength,
|
|
4143
|
+
md5: createHash3("md5").update(contents).digest("hex"),
|
|
3835
4144
|
offset,
|
|
3836
4145
|
path: relative3(config.bundleDirectory, file).replaceAll("\\", "/")
|
|
3837
4146
|
};
|
|
@@ -3839,15 +4148,17 @@ var syncAbsoluteExpoWebAssets = async (config) => {
|
|
|
3839
4148
|
return entry;
|
|
3840
4149
|
});
|
|
3841
4150
|
const name = `bundle${EXPO_ASSET_EXTENSION}`;
|
|
3842
|
-
|
|
3843
|
-
|
|
4151
|
+
const archiveContents = Buffer.concat(filesWithContents.map(({ contents }) => contents));
|
|
4152
|
+
archiveMd5 = createHash3("md5").update(archiveContents).digest("hex");
|
|
4153
|
+
await writeFile2(join7(staging, name), archiveContents);
|
|
4154
|
+
archiveAsset = portableRelative2(join7(config.nativeProjectDirectory, "src", "generated"), join7(destination, name));
|
|
3844
4155
|
await installStagedDirectory(staging, destination);
|
|
3845
4156
|
} catch (error) {
|
|
3846
4157
|
await rm2(staging, { force: true, recursive: true });
|
|
3847
4158
|
throw error;
|
|
3848
4159
|
}
|
|
3849
|
-
const generated =
|
|
3850
|
-
await writeManagedFile(generated, assetModuleSource(assets, archiveAsset, bundleId, nativeDataManifest), true);
|
|
4160
|
+
const generated = join7(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
|
|
4161
|
+
await writeManagedFile(generated, assetModuleSource(assets, archiveAsset, bundleId, archiveMd5, nativeDataManifest), true);
|
|
3851
4162
|
return { appBuild, assets: assets.length, bundleId, path: destination };
|
|
3852
4163
|
};
|
|
3853
4164
|
|