first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

2
node_modules/@expo/log-box/.eslintrc.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');

61
node_modules/@expo/log-box/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,61 @@
# Changelog
## Unpublished
### 🛠 Breaking changes
### 🎉 New features
### 🐛 Bug fixes
### 💡 Others
## 55.0.7 — 2026-02-16
### 🐛 Bug fixes
- Add missing `Content-Type: application/json` to `/symbolicate` requests ([#43074](https://github.com/expo/expo/pull/43074) by [@kitten](https://github.com/kitten))
## 55.0.6 — 2026-02-08
### 🐛 Bug fixes
- Migrate away from react-native-web to fix styles in SPA output. ([#42853](https://github.com/expo/expo/pull/42853) by [@EvanBacon](https://github.com/EvanBacon))
## 55.0.5 — 2026-02-03
_This version does not introduce any user-facing changes._
## 55.0.4 — 2026-02-03
### 🐛 Bug fixes
- Drop `react-native-web` and `react-dom` peers ([#42728](https://github.com/expo/expo/pull/42728) by [@kitten](https://github.com/kitten))
## 55.0.3 — 2026-01-27
### 🐛 Bug fixes
- absolute position web content to prevent modifying body layout. ([#42565](https://github.com/expo/expo/pull/42565) by [@EvanBacon](https://github.com/EvanBacon))
## 55.0.2 — 2026-01-26
_This version does not introduce any user-facing changes._
## 55.0.1 — 2026-01-22
_This version does not introduce any user-facing changes._
## 55.0.0 — 2026-01-21
### 🎉 New features
- Add error overlay for Expo apps (initial release) ([#39958](https://github.com/expo/expo/pull/39958) by [@krystofwoldrich](https://github.com/krystofwoldrich))
### 🐛 Bug fixes
- Fix missing backdrop blur on Web ([#40737](https://github.com/expo/expo/pull/40737) by [@krystofwoldrich](https://github.com/krystofwoldrich))
### ⚠️ Notices
- Added support for React Native 0.83.x. ([#41564](https://github.com/expo/expo/pull/41564) by [@gabrieldonadel](https://github.com/gabrieldonadel))

91
node_modules/@expo/log-box/ExpoLogBox.podspec generated vendored Normal file
View File

@@ -0,0 +1,91 @@
require 'json'
module EnvHelper
def self.env_true?(value)
ENV[value] == '1' || ENV[value]&.downcase == 'true'
end
end
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
# Should ExpoLogBox.bundle be packaged with the app?
isEnabled = EnvHelper.env_true?("EXPO_UNSTABLE_LOG_BOX")
# Should the UI be loaded from development server?
isDevelop = EnvHelper.env_true?("EXPO_DEVELOP_LOG_BOX")
# Should the RedBox replacement WebView be inspectable?
isDebug = EnvHelper.env_true?("EXPO_DEBUG_LOG_BOX")
shouldBuild = File.exist?(File.join(__dir__, '.bundle-on-demand'))
Pod::Spec.new do |s|
s.name = 'ExpoLogBox'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platforms = {
:ios => '15.1',
:osx => '11.0',
:tvos => '15.1'
}
s.source = { git: 'https://github.com/expo/expo.git' }
s.static_framework = true
extraCompilerFlags = '$(inherited)' \
+ (isDevelop ? " -DEXPO_DEVELOP_LOG_BOX" : "") \
+ (isDebug ? " -DEXPO_DEBUG_LOG_BOX" : "") \
+ (isEnabled ? " -DEXPO_UNSTABLE_LOG_BOX" : "")
s.compiler_flags = extraCompilerFlags
s.pod_target_xcconfig = {
'OTHER_SWIFT_FLAGS' => extraCompilerFlags,
}
s.dependency "React-Core"
s.source_files = 'ios/**/*.{h,m,mm,swift}'
if isEnabled
if shouldBuild
build_bundle_script = {
:name => 'Build ExpoLogBox Bundle',
:script => %Q{
echo "Building ExpoLogBox.bundle..."
#{__dir__}/scripts/with-node.sh #{__dir__}/scripts/build-bundle.mjs
},
:execution_position => :before_compile,
# NOTE(@krystofwoldrich): Ideally we would specify `__dir__/**/*`, but Xcode doesn't support patterns
:input_files => ["#{__dir__}/package.json"],
:output_files => ["#{__dir__}/dist/ExpoLogBox.bundle"],
}
end
copy_bundle_script = {
:name => 'Prepare ExpoLogBox Resources',
# NOTE(@krystofwoldrich): We might want to add a flag to always include the ExpoLogBox.bundle to cover unusual configurations.
:script => %Q{
echo "Preparing ExpoLogBox.bundle..."
source="#{__dir__}/dist/ExpoLogBox.bundle/"
dest="${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoLogBox.bundle/"
if [ "${CONFIGURATION}" = "Debug" ]; then
echo "Copying ${source} to ${dest}"
mkdir -p "${dest}"
rsync -a "${source}" "${dest}"
fi
},
:execution_position => :before_compile,
:input_files => ["#{__dir__}/dist/ExpoLogBox.bundle"],
# NOTE: `s.resource_bundles` produces the ExpoLogBox.bundle and so it can't be defined here.
# :output_files => ["${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoLogBox.bundle"],
}
# :always_out_of_date is only available in CocoaPods 1.13.0 and later
if Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.13.0')
# always run the script without warning
copy_bundle_script[:always_out_of_date] = "1"
end
s.script_phases = shouldBuild ? [build_bundle_script, copy_bundle_script] : [copy_bundle_script]
s.resource_bundles = {
'ExpoLogBox' => [],
}
end
end

13
node_modules/@expo/log-box/README.md generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# @expo/log-box
Error overlay for universal Expo apps. Replaces default RedBox and LogBox with Expo LogBox implementation.
## Usage
> `expo` users do not need to install this package, it is already included.
```js
EXPO_UNSTABLE_LOG_BOX=1 npx expo start
EXPO_UNSTABLE_LOG_BOX=1 npx expo run ios
EXPO_UNSTABLE_LOG_BOX=1 npx expo run android
```

81
node_modules/@expo/log-box/android/build.gradle generated vendored Normal file
View File

@@ -0,0 +1,81 @@
import org.apache.tools.ant.taskdefs.condition.Os
plugins {
id 'com.android.library'
id 'expo-module-gradle-plugin'
}
group = 'host.exp.exponent'
version = '55.0.7'
expoModule {
canBePublished false
}
def isEnabled = System.getenv('EXPO_UNSTABLE_LOG_BOX') == '1' || System.getenv('EXPO_UNSTABLE_LOG_BOX')?.toLowerCase() == 'true'
android {
namespace "expo.modules.logbox"
defaultConfig {
versionCode 1
versionName "55.0.7"
buildConfigField "boolean", "EXPO_UNSTABLE_LOG_BOX", isEnabled ? "true" : "false"
}
sourceSets {
main {
java.srcDirs += "src/main"
}
if (isEnabled) {
debug {
// dist contains only ExpoLogBox.bundle
assets.srcDirs += "${projectDir}/../dist"
}
}
}
aaptOptions {
ignoreAssetsPattern '!._expo'
}
}
dependencies {
implementation 'com.facebook.react:react-android'
implementation 'com.google.android.material:material:1.13.0'
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
implementation 'com.google.code.gson:gson:2.8.6'
}
def expoLogBoxDirFile = project.projectDir.parentFile
def expoLogBoxDir = expoLogBoxDirFile.absolutePath
def config = project.hasProperty("react") ? project.react : [];
def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
afterEvaluate {
def distDirFile = file("${expoLogBoxDir}/dist")
def bundleOnDemandFlagFile = file("${expoLogBoxDir}/.bundle-on-demand")
if (bundleOnDemandFlagFile.exists()) {
def createExpoLogBoxBundleTask = tasks.register('createExpoLogBoxBundle', Exec) {
description = 'expo-log-box: Create bundle.'
// Setup inputs and output so Gradle can cache the ExpoLogBox.bundle
inputs.dir project.fileTree(expoLogBoxDirFile) {
exclude 'dist/**'
exclude 'android/**'
}
outputs.dir distDirFile
// Generate the bundle
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine("cmd", "/c", *nodeExecutableAndArgs, "$expoLogBoxDir/scripts/build-bundle.mjs")
} else {
commandLine(*nodeExecutableAndArgs, "$expoLogBoxDir/scripts/build-bundle.mjs")
}
}
// Generate bundle at preBuild
tasks.getByName('preBuild').dependsOn(createExpoLogBoxBundleTask)
}
}

View File

@@ -0,0 +1,3 @@
<manifest>
</manifest>

View File

@@ -0,0 +1,137 @@
/*
* Copyright © 2024 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* https://github.com/krystofwoldrich/react-native/blob/7db31e2fca0f828aa6bf489ae6dc4adef9b7b7c3/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt
*/
package expo.modules.logbox
import android.content.Context
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.SurfaceDelegate
import com.facebook.react.common.SurfaceDelegateFactory
import com.facebook.react.devsupport.DevSupportManagerBase
import com.facebook.react.devsupport.ReactInstanceDevHelper
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener
import com.facebook.react.devsupport.interfaces.DevLoadingViewManager
import com.facebook.react.devsupport.interfaces.DevSupportManager
import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager
import com.facebook.react.devsupport.interfaces.RedBoxHandler
import com.facebook.react.packagerconnection.RequestHandler
import com.facebook.react.devsupport.StackTraceHelper.convertJavaStackTrace
import com.facebook.react.devsupport.StackTraceHelper.convertJsStackTrace
import com.facebook.react.devsupport.interfaces.StackFrame
class ExpoLogBoxDevSupportManager(
applicationContext: Context,
reactInstanceManagerHelper: ReactInstanceDevHelper,
packagerPathForJSBundleName: String?,
enableOnCreate: Boolean,
redBoxHandler: RedBoxHandler?,
devBundleDownloadListener: DevBundleDownloadListener?,
minNumShakes: Int,
customPackagerCommandHandlers: Map<String, RequestHandler>?,
surfaceDelegateFactory: SurfaceDelegateFactory?,
devLoadingViewManager: DevLoadingViewManager?,
pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?
) :
ExpoBridgelessDevSupportManager(
applicationContext,
reactInstanceManagerHelper,
packagerPathForJSBundleName,
enableOnCreate,
redBoxHandler,
devBundleDownloadListener,
minNumShakes,
customPackagerCommandHandlers,
surfaceDelegateFactory,
devLoadingViewManager,
pausedInDebuggerOverlayManager
) {
private var redBoxSurfaceDelegate: SurfaceDelegate? = null
override fun hideRedboxDialog() {
redBoxSurfaceDelegate?.hide()
}
override fun showNewJavaError(message: String?, e: Throwable) {
showNewError(message, convertJavaStackTrace(e))
}
override fun showNewJSError(message: String?, details: ReadableArray?, errorCookie: Int) {
showNewError(message, convertJsStackTrace(details))
}
private fun showNewError(message: String?, stack: Array<StackFrame>) {
UiThreadUtil.runOnUiThread {
lastErrorTitle = message
lastErrorStack = stack
if (redBoxSurfaceDelegate == null) {
this.redBoxSurfaceDelegate =
createSurfaceDelegate("RedBox")
?: ExpoLogBoxSurfaceDelegate(this@ExpoLogBoxDevSupportManager).apply {
createContentView("RedBox")
}
}
if (redBoxSurfaceDelegate?.isShowing() == true) {
// Sometimes errors cause multiple errors to be thrown in JS in quick succession. Only
// show the first and most actionable one.
return@runOnUiThread
}
redBoxSurfaceDelegate?.show()
}
}
}
/**
* [Source](https://github.com/krystofwoldrich/react-native/blob/7db31e2fca0f828aa6bf489ae6dc4adef9b7b7c3/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/BridgelessDevSupportManager.kt#L29)
*
* An implementation of [DevSupportManager] that extends the functionality in
* [DevSupportManagerBase] with some additional, more flexible APIs for asynchronously loading the
* JS bundle.
*/
open class ExpoBridgelessDevSupportManager(
applicationContext: Context,
reactInstanceManagerHelper: ReactInstanceDevHelper,
packagerPathForJSBundleName: String?,
enableOnCreate: Boolean,
redBoxHandler: RedBoxHandler?,
devBundleDownloadListener: DevBundleDownloadListener?,
minNumShakes: Int,
customPackagerCommandHandlers: Map<String, RequestHandler>?,
surfaceDelegateFactory: SurfaceDelegateFactory?,
devLoadingViewManager: DevLoadingViewManager?,
pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?
) :
DevSupportManagerBase(
applicationContext,
reactInstanceManagerHelper,
packagerPathForJSBundleName,
enableOnCreate,
redBoxHandler,
devBundleDownloadListener,
minNumShakes,
customPackagerCommandHandlers,
surfaceDelegateFactory,
devLoadingViewManager,
pausedInDebuggerOverlayManager
) {
override val uniqueTag: String
get() = "Bridgeless"
override fun handleReloadJS() {
UiThreadUtil.assertOnUiThread()
// dismiss RedBox if it exists
hideRedboxDialog()
reactInstanceDevHelper.reload("BridgelessDevSupportManager.handleReloadJS()")
}
}

View File

@@ -0,0 +1,24 @@
package expo.modules.logbox
import android.app.Activity
import android.content.Context
import android.os.Bundle
import com.facebook.react.ReactApplication
import expo.modules.core.interfaces.Package
import expo.modules.core.interfaces.ReactActivityLifecycleListener
class ExpoLogBoxPackage : Package {
override fun createReactActivityLifecycleListeners(activityContext: Context?): List<ReactActivityLifecycleListener> {
if (!BuildConfig.DEBUG || !BuildConfig.EXPO_UNSTABLE_LOG_BOX) {
return emptyList()
}
return listOf(
object : ReactActivityLifecycleListener {
override fun onCreate(activity: Activity, savedInstanceState: Bundle?) {
injectExpoLogBoxDevSupportManager((activity.application as ReactApplication).reactHost)
}
}
)
}
}

View File

@@ -0,0 +1,31 @@
package expo.modules.logbox
import java.lang.reflect.Field
import java.lang.reflect.Modifier
fun <T> Class<out T>.setProtectedDeclaredField(obj: T, filedName: String, newValue: Any, predicate: (Any?) -> Boolean = { true }) {
val field = getDeclaredField(filedName)
val modifiersField = Field::class.java.getDeclaredField("accessFlags")
field.isAccessible = true
modifiersField.isAccessible = true
modifiersField.setInt(
field,
field.modifiers and Modifier.FINAL.inv()
)
if (!predicate.invoke(field.get(obj))) {
return
}
field.set(obj, newValue)
}
fun <T, U> Class<out T>.getProtectedFieldValue(obj: T, filedName: String): U {
val field = getDeclaredField(filedName)
field.isAccessible = true
@Suppress("UNCHECKED_CAST")
return field.get(obj) as U
}

View File

@@ -0,0 +1,60 @@
package expo.modules.logbox
import android.util.Log
import com.facebook.react.ReactHost
import com.facebook.react.devsupport.DevSupportManagerBase
import com.facebook.react.devsupport.interfaces.DevSupportManager
import com.facebook.react.runtime.ReactHostImpl
fun injectExpoLogBoxDevSupportManager(reactHost: ReactHost?) {
val currentDevSupportManager = reactHost?.devSupportManager
if (currentDevSupportManager == null) {
Log.w(
"ExpoLogBox",
"ReactHost initialized without a dev support manager, ExpoLogBox can't be initialized."
)
return
} else if (currentDevSupportManager is ExpoLogBoxDevSupportManager) {
Log.i(
"ExpoLogBox",
"DevSupportManager is already `ExpoDevSupportManagerWithLogBoxOverride`, skipping initialization."
)
return
}
// NOTE(@krystofwoldrich): This will overwrite expo-dev-client dev support manager
try {
val newDevSupportManager =
createExpoLogBoxBridgelessDevSupportManager(
currentDevSupportManager
)
ReactHostImpl::class.java.setProtectedDeclaredField(
reactHost,
"devSupportManager",
newDevSupportManager
)
} catch (e: Exception) {
Log.i("ExpoLogBox", "Couldn't inject `ExpoDevSupportManagerWithLogBoxOverride`.", e)
}
}
fun createExpoLogBoxBridgelessDevSupportManager(
currentDevSupportManager: DevSupportManager,
devManagerClass: Class<*> = DevSupportManagerBase::class.java
): ExpoLogBoxDevSupportManager {
return ExpoLogBoxDevSupportManager(
applicationContext = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "applicationContext"),
reactInstanceManagerHelper = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "reactInstanceDevHelper"),
packagerPathForJSBundleName = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "jsAppBundleName"),
enableOnCreate = true,
redBoxHandler = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "redBoxHandler"),
devBundleDownloadListener = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "devBundleDownloadListener"),
minNumShakes = 1,
customPackagerCommandHandlers = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "customPackagerCommandHandlers"),
surfaceDelegateFactory = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "surfaceDelegateFactory"),
devLoadingViewManager = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "devLoadingViewManager"),
pausedInDebuggerOverlayManager = devManagerClass.getProtectedFieldValue(currentDevSupportManager, "pausedInDebuggerOverlayManager")
)
}

View File

@@ -0,0 +1,149 @@
package expo.modules.logbox
import android.app.Activity
import android.app.Dialog
import android.widget.FrameLayout
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.bridge.ReactContext
import com.facebook.react.common.SurfaceDelegate
import com.facebook.react.devsupport.interfaces.DevSupportManager
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.io.IOException
class ExpoLogBoxSurfaceDelegate(private val devSupportManager: DevSupportManager) :
SurfaceDelegate {
private var dialog: Dialog? = null
override fun createContentView(appKey: String) = Unit
override fun isContentViewReady(): Boolean {
return true
}
override fun destroyContentView() = Unit
override fun show() {
val context: Activity = devSupportManager.currentActivity ?: return
if (context.isFinishing) {
devSupportManager.currentReactContext?.let { reactContext ->
/**
* If the activity isn't available, try again after the next onHostResume(). onHostResume()
* is when the activity gets attached to the react native.
*/
runAfterHostResume(reactContext) { this.show() }
return
}
return
}
dialog = Dialog(context, android.R.style.Theme_NoTitleBar)
val rootContainer = FrameLayout(context).apply {
fitsSystemWindows = true
}
val errorMessage = devSupportManager.lastErrorTitle
val errorStack = devSupportManager.lastErrorStack?.map { frame ->
mapOf(
// Expected to match https://github.com/expo/expo/blob/5ed042a3547571fa70cf1d53db7c12b4bb966a90/packages/%40expo/log-box/src/devServerEndpoints.ts#L3
"file" to frame.file,
"methodName" to frame.method,
"arguments" to emptyArray<String>(),
"lineNumber" to frame.line,
"column" to frame.column,
"collapse" to frame.isCollapsed
)
}
val webViewWrapper = ExpoLogBoxWebViewWrapper(
actions = ExpoLogBoxWebViewWrapper.Actions(
onReload = ExpoLogBoxWebViewWrapper.Actions.OnReload(onReload),
fetchTextAsync = ExpoLogBoxWebViewWrapper.Actions.FetchTextAsync(fetchTextAsync)
),
props = mapOf(
"platform" to "android",
"nativeLogs" to arrayOf(
mapOf(
"message" to errorMessage,
"stack" to errorStack
)
)
),
context
)
rootContainer.addView(webViewWrapper.webView)
dialog?.setContentView(rootContainer)
dialog?.show()
}
override fun hide() {
// Build Errors are generally not dismissable
// NOTE: The hide function is also called also when application goes to background
// which causes the error disappear and leave the app on black screen
dialog?.dismiss()
}
override fun isShowing(): Boolean {
return dialog?.isShowing == true
}
private val onReload = {
devSupportManager.handleReloadJS()
}
private val fetchTextAsync = {
url: String,
method: String,
body: String,
onResult: (String) -> Unit,
onFailure: (Exception) -> Unit
->
val client = OkHttpClient()
val requestBody = if (method.uppercase() != "GET") {
body.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
} else {
null
}
val request = Request.Builder()
.url(url)
.method(method.uppercase(), requestBody)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
onFailure(e)
}
override fun onResponse(call: Call, response: Response) {
response.use {
val responseBody = it.body?.string() ?: "{}"
onResult(responseBody)
}
}
})
}
companion object {
private fun runAfterHostResume(reactContext: ReactContext, runnable: Runnable) {
reactContext.addLifecycleEventListener(
object : LifecycleEventListener {
override fun onHostResume() {
runnable.run()
reactContext.removeLifecycleEventListener(this)
}
override fun onHostPause() = Unit
override fun onHostDestroy() = Unit
}
)
}
}
}

View File

@@ -0,0 +1,224 @@
// Keep in sync with webview-wrapper.tsx
// https://github.com/expo/expo/blob/main/packages/expo/src/dom/webview-wrapper.tsx
package expo.modules.logbox
import android.app.Activity
import android.graphics.Bitmap
import android.graphics.Color
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebView.setWebContentsDebuggingEnabled
import android.webkit.WebViewClient
import com.facebook.react.modules.systeminfo.AndroidInfoHelpers
import com.google.gson.Gson
import com.google.gson.JsonObject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class ExpoLogBoxWebViewWrapper(
val actions: Actions,
val props: Map<String, Any>,
val context: Activity
) {
val webView: WebView = WebView(context).apply {
setBackgroundColor(Color.BLACK)
settings.javaScriptEnabled = true
setWebContentsDebuggingEnabled(true)
// This interface is defined by the Expo DOM Components WebView Wrapper
// and must always be the same as [add link]
addJavascriptInterface(
object : Any() {
@JavascriptInterface
fun postMessage(rawMessage: String) {
processMessageFromWebView(rawMessage)
}
},
"ReactNativeWebView"
)
webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
initializeLogBoxDomEnvironment()
}
}
// TODO: use build config to specify the dev url
// webView.loadUrl("http://10.0.2.2:8090/")
loadUrl("file:///android_asset/ExpoLogBox.bundle/index.html")
}
private fun initializeLogBoxDomEnvironment() {
val initialProps = mapOf(
"names" to actions.getNames(),
"props" to props
)
val gson = Gson()
val jsonObject = gson.toJson(initialProps)
val devServerOrigin = "http://${AndroidInfoHelpers.getServerHost(context)}"
val script = """
var process=globalThis.process||{};process.env=process.env||{};
process.env.EXPO_DEV_SERVER_ORIGIN='$devServerOrigin';
window.$$${"EXPO_DOM_HOST_OS"} = 'android';
window.$$${"EXPO_INITIAL_PROPS"} = $jsonObject;
""".trimIndent()
webView.post {
webView.evaluateJavascript(script, null)
}
}
private fun processMessageFromWebView(rawMessage: String) {
val gson = Gson()
val jsonObject = gson.fromJson(rawMessage, JsonObject::class.java)
val messageType = jsonObject.getAsJsonPrimitive("type")
if (messageType.isString && messageType.asString == NATIVE_ACTION) {
val data = jsonObject.getAsJsonObject("data")
val actionId = data.getAsJsonPrimitive("actionId")
val uid = data.getAsJsonPrimitive("uid")
val args = data.getAsJsonArray("args")
if (!actionId.isString || !uid.isString || !args.isJsonArray) {
return
}
when (actionId.asString) {
"onReload" -> {
actions.onReload.action()
}
"fetchTextAsync" -> {
CoroutineScope(Dispatchers.Default).launch {
val url = when {
args.get(0).isJsonPrimitive &&
args.get(0).asJsonPrimitive.isString
-> args.get(0).asJsonPrimitive.asString
else -> null
}
val options = args.get(1).asJsonObject
val method = when {
options.has("method") &&
options.get("method").isJsonPrimitive &&
options.getAsJsonPrimitive("method").isString
-> options.getAsJsonPrimitive("method").asString
else -> null
}
val body = when {
options.has("body") &&
options.get("body").isJsonPrimitive &&
options.getAsJsonPrimitive("body").isString
-> options.getAsJsonPrimitive("body").asString
else -> null
}
if (url != null) {
actions.fetchTextAsync.action(
url,
method ?: "GET",
body ?: "",
{ result ->
sendReturn(result, uid.asString, actionId.asString)
},
{ exception ->
sendReturn(exception, uid.asString, actionId.asString)
}
)
}
}
}
}
}
}
fun sendReturn(result: Any, uid: String, actionId: String) {
sendReturn(
mapOf(
"type" to NATIVE_ACTION_RESULT,
"data" to mapOf(
"uid" to uid,
"actionId" to actionId,
"result" to result
)
)
)
}
fun sendReturn(exception: Exception, uid: String, actionId: String) {
sendReturn(
mapOf(
"type" to NATIVE_ACTION_RESULT,
"data" to mapOf(
"uid" to uid,
"actionId" to actionId,
"error" to mapOf(
"message" to "$exception"
)
)
)
)
}
fun sendReturn(data: Map<String, Any>) {
sendReturn(
Gson().toJson(
mapOf(
"detail" to data
)
)
)
}
fun sendReturn(value: String) {
val injectedJavascript = """
;
(function() {
try {
console.log("received", $value)
window.dispatchEvent(new CustomEvent("${DOM_EVENT}", $value));
} catch (e) {
console.log('error', e)
}
})();
true;
"""
webView.post {
webView.evaluateJavascript(injectedJavascript, null)
}
}
companion object {
private const val DOM_EVENT = "$\$dom_event"
private const val NATIVE_ACTION_RESULT = "$\$native_action_result"
private const val NATIVE_ACTION = "$\$native_action"
}
data class Actions(
val onReload: OnReload,
val fetchTextAsync: FetchTextAsync
) {
fun getNames(): Array<String> {
return arrayOf(
onReload.name,
fetchTextAsync.name
)
}
data class OnReload(
val action: () -> Unit,
val name: String = "onReload"
)
data class FetchTextAsync(
val action: (
url: String,
method: String,
body: String,
onResult: (String) -> Unit,
onFailure: (Exception) -> Unit
) -> Unit,
val name: String = "fetchTextAsync"
)
}
}

14
node_modules/@expo/log-box/app.json generated vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"expo": {
"name": "ExpoLogBox",
"slug": "expo-log-box",
"web": {
"output": "single"
},
"experiments": {
"reactCompiler": true,
"autolinkingModuleResolution": true,
"baseUrl": "."
}
}
}

60
node_modules/@expo/log-box/app/App.tsx generated vendored Normal file
View File

@@ -0,0 +1,60 @@
import * as React from 'react';
import { LogBoxLog } from '../src/Data/LogBoxLog';
import { parseLogBoxException } from '../src/Data/parseLogBoxLog';
import LogBoxPolyfillDOM from '../src/logbox-dom-polyfill';
import { View, Text } from 'react-native';
const logs: LogBoxLog[] = [
new LogBoxLog(parseLogBoxException({
message: "Test error",
originalMessage: undefined,
name: undefined,
componentStack: undefined,
id: -1,
isComponentError: false,
isFatal: false,
stack: [],
})),
];
/**
* Empty App skeleton used as a workaround to prebuilt the Expo LogBox DOM Component.
* (DOM Components are build during `expo export:embed`)
*
* Also used for the DOM Component UI preview via `yarn start`.
*/
export default function App() {
const [showSandboxWarning, setSandboxWarningVisibility] = React.useState(true);
return (
<>
{ showSandboxWarning &&
<View style={{ position: 'absolute', padding: 16, backgroundColor: '#E9D502', zIndex: 10000, borderRadius: 8, top: 16, left: 16, flexDirection: 'row', gap: 8 }}>
<Text style={{fontFamily: "BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"}} >This is @expo/log-box development sandbox.</Text>
<Text style={{fontWeight: 'bold'}} onPress={() => setSandboxWarningVisibility(false)}>Close</Text>
</View>
}
<LogBoxPolyfillDOM
logs={logs}
// LogBoxData actions props
onDismiss={() => { throw new Error("`onDismiss` placeholder, should never be called."); }}
onChangeSelectedIndex={() => { throw new Error("`onChangeSelectedIndex` placeholder, should never be called."); }}
// Environment polyfill props
platform={process.env.EXPO_OS}
devServerUrl={undefined}
// Common actions props
fetchTextAsync={() => Promise.reject(new Error('`fetchTextAsync` placeholder, should never be called.'))}
// LogBox UI actions props
onMinimize={() => { throw new Error("`onMinimize` placeholder, should never be called."); }}
onReload={() => { throw new Error('`onReload` placeholder, should never be called.'); }}
onCopyText={() => { throw new Error('`onCopyText` placeholder, should never be called.'); }}
// DOM props
dom={{}}
/>
</>
);
}

8
node_modules/@expo/log-box/app/index.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
import { registerRootComponent } from 'expo';
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

BIN
node_modules/@expo/log-box/assets/alert-triangle.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

BIN
node_modules/@expo/log-box/assets/loader.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

47
node_modules/@expo/log-box/build/Data/Types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,47 @@
import { type StackFrame as UpstreamStackFrame } from 'stacktrace-parser';
export type Message = {
content: string;
substitutions: {
length: number;
offset: number;
}[];
};
export type Category = string;
export type CodeFrame = {
content: string;
location?: {
row: number;
column: number;
[key: string]: any;
} | null;
fileName: string;
collapse?: boolean;
};
export type SymbolicationStatus = 'NONE' | 'PENDING' | 'COMPLETE' | 'FAILED';
export type LogLevel = 'error' | 'fatal' | 'syntax' | 'resolution' | 'static';
export type StackType = 'stack' | 'component';
export type LogBoxLogData = {
level: LogLevel;
type?: string;
message: Message;
stack: MetroStackFrame[];
category: string;
componentStack: MetroStackFrame[];
codeFrame: Partial<Record<StackType, CodeFrame>>;
isComponentError: boolean;
isMissingModuleError?: string;
extraData?: Record<string, unknown>;
};
export type LogBoxLogDataLegacy = {
level: LogLevel;
type?: string;
message: Message;
stack: MetroStackFrame[];
category: string;
componentStack: CodeFrame[];
codeFrame?: CodeFrame;
isComponentError: boolean;
};
export type MetroStackFrame = Omit<UpstreamStackFrame, 'arguments'> & {
collapse?: boolean;
};

2
node_modules/@expo/log-box/build/Data/Types.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

2
node_modules/@expo/log-box/build/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export { parseWebBuildErrors } from './utils/parseWebBuildErrors';
export { withoutANSIColorStyles } from './utils/withoutANSIStyles';

9
node_modules/@expo/log-box/build/utils.js generated vendored Normal file
View File

@@ -0,0 +1,9 @@
"use strict";
// This is the main export for `@expo/log-box/utils` used in the `@expo/cli` and `expo/async-require/hmr`
// This needs to be transpiled to CJS for use in the Expo CLI
Object.defineProperty(exports, "__esModule", { value: true });
exports.withoutANSIColorStyles = exports.parseWebBuildErrors = void 0;
var parseWebBuildErrors_1 = require("./utils/parseWebBuildErrors");
Object.defineProperty(exports, "parseWebBuildErrors", { enumerable: true, get: function () { return parseWebBuildErrors_1.parseWebBuildErrors; } });
var withoutANSIStyles_1 = require("./utils/withoutANSIStyles");
Object.defineProperty(exports, "withoutANSIColorStyles", { enumerable: true, get: function () { return withoutANSIStyles_1.withoutANSIColorStyles; } });

View File

@@ -0,0 +1,11 @@
export type ParsedBuildError = {
content: string;
fileName: string;
row: number;
column: number;
codeFrame: string;
missingModule?: string;
};
export declare function parseMetroError(message: string): ParsedBuildError | null;
export declare function parseBabelTransformError(message: string): ParsedBuildError | null;
export declare function parseBabelCodeFrameError(message: string): ParsedBuildError | null;

View File

@@ -0,0 +1,63 @@
"use strict";
// Related Metro error's formatting (for the portion of the function parsing Metro errors)
// https://github.com/facebook/metro/blob/34bb8913ec4b5b02690b39d2246599faf094f721/packages/metro/src/lib/formatBundlingError.js#L36
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseMetroError = parseMetroError;
exports.parseBabelTransformError = parseBabelTransformError;
exports.parseBabelCodeFrameError = parseBabelCodeFrameError;
const BABEL_TRANSFORM_ERROR_FORMAT = /^(?:TransformError )?(?:SyntaxError: |ReferenceError: )(.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/;
const BABEL_CODE_FRAME_ERROR_FORMAT =
// Adjusted from original to not capture import stack a part of the code frame
/^(?:TransformError )?(?:.*):? (?:.*?)([/|\\].*): ([\s\S]+?)\n((?:[ >]*\d+[\s|]+[^\n]*\n?)+|\u{001b}\[[0-9;]*m(?:.*\n?)+?(?=\n\n|\n[^\u{001b}\s]|$))/mu;
const METRO_ERROR_FORMAT = /^(?:(?:InternalError )?Metro has encountered an error:) (.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/u;
const UNABLE_TO_RESOLVE_MODULE_ERROR_FORMAT = /(?:\w )?Unable to resolve module (.*) from/;
function parseMetroError(message) {
const e = message.match(METRO_ERROR_FORMAT);
if (!e) {
return null;
}
const [, content, fileName, row, column, codeFrame] = e;
return {
content,
fileName,
row: parseInt(row, 10),
column: parseInt(column, 10),
codeFrame,
};
}
function parseBabelTransformError(message) {
const e = message.match(BABEL_TRANSFORM_ERROR_FORMAT);
if (!e) {
return null;
}
// Transform errors are thrown from inside the Babel transformer.
const [, fileName, content, row, column, codeFrame] = e;
return {
content,
fileName,
row: parseInt(row, 10),
column: parseInt(column, 10),
codeFrame,
};
}
function parseBabelCodeFrameError(message) {
const e = message.match(BABEL_CODE_FRAME_ERROR_FORMAT);
if (!e) {
return null;
}
// Codeframe errors are thrown from any use of buildCodeFrameError.
const [, fileName, content, codeFrame] = e;
//TODO: In the future we should send metadata from @expo/cli, but at the moment
// parsing the message is the only way that work across all LogBox scenarios
// (build web, build ios, build android, hmr web, hmr native).
const [, missingModule] = message.match(UNABLE_TO_RESOLVE_MODULE_ERROR_FORMAT) || [];
const messageContent = missingModule ? `Unable to resolve module ${missingModule}` : content;
return {
content: messageContent,
fileName,
row: -1,
column: -1,
codeFrame,
missingModule,
};
}

View File

@@ -0,0 +1,14 @@
import { LogBoxLogDataLegacy, MetroStackFrame } from '../Data/Types';
/**
* Called in expo/cli, the return value is injected into the static error page which is bundled
* instead of the app when the web build fails.
*/
export declare function parseWebBuildErrors({ error, projectRoot, parseErrorStack, }: {
error: Error & {
type?: unknown;
};
projectRoot: string;
parseErrorStack: (projectRoot: string, stack?: string) => (MetroStackFrame & {
collapse?: boolean;
})[];
}): LogBoxLogDataLegacy;

View File

@@ -0,0 +1,72 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseWebBuildErrors = parseWebBuildErrors;
const metroBuildErrorsFormat_1 = require("./metroBuildErrorsFormat");
/**
* Called in expo/cli, the return value is injected into the static error page which is bundled
* instead of the app when the web build fails.
*/
function parseWebBuildErrors({ error, projectRoot, parseErrorStack, }) {
// NOTE: Ideally this will be merged with the parseWebHmrBuildErrors function
// Remap direct Metro Node.js errors to a format that will appear more client-friendly in the logbox UI.
let stack;
if (isTransformError(error) && error.filename) {
// Syntax errors in static rendering.
stack = [
{
// Avoid using node:path to be compatible with web and RN runtime.
file: `${projectRoot}/${error.filename}`,
methodName: '<unknown>',
// TODO: Import stack
lineNumber: error.lineNumber,
column: error.column,
},
];
}
else if ('originModulePath' in error &&
typeof error.originModulePath === 'string' &&
'targetModuleName' in error &&
typeof error.targetModuleName === 'string' &&
'cause' in error) {
const { codeFrame } = (0, metroBuildErrorsFormat_1.parseBabelCodeFrameError)(error.message) || {};
// We are purposely not using the parsed fileName or content here
// because we have the original data in the error object.
const content = `Unable to resolve module ${error.targetModuleName}`;
return {
level: 'resolution',
// TODO: Add import stacks
stack: [],
isComponentError: false,
componentStack: [],
codeFrame: codeFrame
? {
fileName: error.originModulePath,
location: null, // We are not given the location.
content: codeFrame,
}
: undefined,
message: {
content,
substitutions: [],
},
category: `${error.originModulePath}-${1}-${1}`,
};
}
else {
stack = parseErrorStack(projectRoot, error.stack);
}
return {
level: 'static',
message: {
content: error.message,
substitutions: [],
},
isComponentError: false,
stack,
category: 'static',
componentStack: [],
};
}
function isTransformError(error) {
return error.type === 'TransformError';
}

View File

@@ -0,0 +1 @@
export declare function withoutANSIColorStyles(message: any): any;

View File

@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.withoutANSIColorStyles = withoutANSIColorStyles;
function withoutANSIColorStyles(message) {
if (typeof message !== 'string') {
return message;
}
return message.replace(
// eslint-disable-next-line no-control-regex
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '');
}

View File

@@ -0,0 +1 @@
._5PHPda_line{flex-direction:row;display:flex}._5PHPda_text{white-space:pre}

View File

@@ -0,0 +1 @@
.xWJzLq_fileIcon{display:none}.xWJzLq_copyButton{cursor:pointer;white-space:nowrap;box-shadow:none;border-width:0;border-left:1px solid var(--expo-log-color-border);background-color:#0000;background-color:var(--expo-log-color-background);border-radius:0;justify-content:flex-end;align-items:center;gap:6px;height:40px;padding-left:16px;padding-right:16px;font-size:10px;font-weight:500;transition:all .2s ease-in-out;display:inline-flex}.xWJzLq_copyButton:hover{background-color:var(--expo-log-secondary-system-background-hover)}.xWJzLq_copyButtonIcon{width:1.1rem;height:1.1rem;color:var(--expo-log-secondary-label);margin-bottom:-1px;transition:all .2s ease-in-out;transform:translateZ(0)}.xWJzLq_copyButton:hover .xWJzLq_copyButtonIcon{transform-origin:0 100%;transform:scale(1.1)}.xWJzLq_copyButtonText{color:var(--expo-log-color-label);letter-spacing:-.003rem;font-size:14px;font-weight:400;line-height:1;font-family:var(--expo-log-font-family);align-items:center;display:none}.xWJzLq_headerScrollText{color:inherit;letter-spacing:-.009rem;align-items:center;gap:8px;font-size:15px;font-weight:600;line-height:1.25;display:flex;position:relative;overflow-x:auto}.xWJzLq_headerScrollText::-webkit-scrollbar{display:none}.xWJzLq_header{background-color:var(--expo-log-color-background);border-bottom:1px solid var(--expo-log-color-border);border-top-left-radius:6px;border-top-right-radius:6px;justify-content:space-between;min-height:40px;display:flex;position:relative;overflow:hidden}.xWJzLq_headerIconWrapper{display:none}.xWJzLq_headerText{overflow-wrap:break-word;font-family:var(--expo-log-font-mono);white-space:nowrap;color:var(--expo-log-color-label);padding-left:12px;padding-right:16px}.xWJzLq_blurGradientRL{background:linear-gradient(to left,var(--expo-log-color-background)0%,#0000 100%);pointer-events:none;width:20px;position:absolute;top:0;bottom:0;right:0}.xWJzLq_blurGradientLR{background:linear-gradient(to right,var(--expo-log-color-background)0%,#0000 100%);opacity:0;pointer-events:none;border-top-left-radius:6px;width:20px;position:absolute;top:0;bottom:0;left:0}@media screen and (width>=768px){.xWJzLq_fileIcon{display:block}.xWJzLq_headerText{padding-left:0}.xWJzLq_headerIconWrapper{min-width:1rem;height:1rem;padding-left:16px;display:block}.xWJzLq_header{padding-left:0}.xWJzLq_copyButtonText{display:flex}}

View File

@@ -0,0 +1 @@
.MTrB7G_container{bottom:calc(6px + env(safe-area-inset-bottom,0px));max-width:320px;display:flex;position:fixed;left:10px;right:10px}.MTrB7G_toast{border:1px solid var(--expo-log-color-danger);cursor:pointer;height:48px;font-family:var(--expo-log-font-family);background-color:#632e2c;border-radius:6px;flex-direction:row;flex:1;justify-content:center;align-items:center;gap:8px;margin-bottom:4px;padding:0 10px;transition:background-color .2s;display:flex;overflow:hidden}@media (hover:hover){.MTrB7G_toast:hover{background-color:#924340}}.MTrB7G_message{-webkit-user-select:none;user-select:none;color:var(--expo-log-color-label);font-family:var(--expo-log-font-family);white-space:nowrap;text-overflow:ellipsis;flex:1;padding-left:8px;font-size:14px;line-height:22px;display:block;overflow:hidden}.MTrB7G_count{background:var(--expo-log-color-danger);border-radius:6px;justify-content:center;align-items:center;min-width:30px;height:30px;display:flex}.MTrB7G_countText{color:var(--expo-log-color-label);font-family:var(--expo-log-font-family);text-align:center;text-shadow:0 0 3px #333c;font-size:14px;font-weight:600;line-height:18px}.MTrB7G_dismissButton{cursor:pointer;background:0 0;border:none;margin:-12px -10px -12px -5px;padding:12px 10px}.MTrB7G_dismissContent{background-color:#ffffff1a;border-radius:25px;justify-content:center;align-items:center;width:20px;height:20px;transition:opacity .2s;display:flex}@media (hover:hover){.MTrB7G_dismissButton:hover .MTrB7G_dismissContent{opacity:.8}}.MTrB7G_dismissButton:active .MTrB7G_dismissContent{opacity:.5}.MTrB7G_dismissIcon{color:#fff;width:12px;height:12px}

View File

@@ -0,0 +1 @@
.M1LNVW_container{-webkit-user-select:none;user-select:none;flex-direction:row;justify-content:space-between;align-items:center;height:48px;min-height:48px;padding:0 1rem;display:flex;position:relative}.M1LNVW_leftGroup{flex-direction:row;align-items:stretch;gap:16px;display:flex;position:relative}.M1LNVW_headerControls{flex-direction:row;align-items:center;gap:8px;display:flex}.M1LNVW_divider{border-left:1px solid var(--expo-log-color-border);width:0}.M1LNVW_navGroup{flex-direction:row;align-items:center;gap:8px;display:flex}.M1LNVW_sdkBadge{border:1px solid var(--expo-log-color-border);color:var(--expo-log-secondary-label);border-radius:999px;flex-direction:row;gap:6px;padding:4px 12px;display:flex}.M1LNVW_sdkIcon{width:14px}.M1LNVW_sdkText{color:var(--expo-log-secondary-label);font-family:var(--expo-log-font-family);font-size:.75rem}.M1LNVW_pageButton{aspect-ratio:1;background-color:var(--expo-log-secondary-system-background);cursor:pointer;border:none;border-radius:50%;outline:none;justify-content:center;align-items:center;width:24px;height:24px;padding:2px;transition:background-color .2s;display:flex}.M1LNVW_pageButton[aria-disabled=true]{cursor:not-allowed}.M1LNVW_pageButton:not([aria-disabled]):hover{background-color:var(--expo-log-secondary-system-background-hover)}

View File

@@ -0,0 +1 @@
@keyframes tHUwfW_spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.tHUwfW_spinner{animation:2s linear infinite tHUwfW_spin}.tHUwfW_root{cursor:pointer;background-color:#0000;border:none;border-radius:12px;flex-direction:row;align-items:center;height:24px;padding:0 8px;display:flex}@media (hover:hover){.tHUwfW_root:hover{background-color:#333}}.tHUwfW_root:active{background-color:#3339}.tHUwfW_image{width:16px;height:14px;margin-right:4px}.tHUwfW_text{font-size:12px;line-height:16px}

View File

@@ -0,0 +1 @@
:root{--expo-log-color-border:#313538;--expo-log-color-label:#edeef0;--expo-log-color-background:#111113;--expo-log-secondary-label:#ebebf599;--expo-log-secondary-system-background:#1c1c1f;--expo-log-secondary-system-grouped-background:#18191b;--expo-log-secondary-system-background-hover:#272a2d;--expo-log-color-danger:#ec5e5e;--expo-log-syntax-red:#ec5e5e;--expo-log-syntax-orange:#ffa163;--expo-log-font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;--expo-log-font-mono:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}@supports (color:color(display-p3 0 0 0)){:root{--expo-log-secondary-system-grouped-background:color(display-p3 .095 .098 .105);--expo-log-secondary-system-background-hover:color(display-p3 .156 .163 .176);--expo-log-color-danger:color(display-p3 .861 .403 .387);--expo-log-syntax-red:color(display-p3 .861 .403 .387);--expo-log-syntax-orange:color(display-p3 1 .63 .38)}}._6gbvKG_overlayIos{top:0}._6gbvKG_overlayAndroid{top:var(--android-safe-area-inset-top,env(safe-area-inset-top))}._6gbvKG_overlayWeb{top:calc(10vh + env(safe-area-inset-top,2vh))}._6gbvKG_overlay{z-index:9900;-webkit-font-smoothing:antialiased;flex-direction:column;align-content:center;align-items:center;display:flex;position:fixed;bottom:0;left:0;right:0}._6gbvKG_containerTopRadius{border-top-left-radius:20px;border-top-right-radius:20px}._6gbvKG_container{--expo-log-max-width:960px;max-width:var(--expo-log-max-width);background-color:var(--expo-log-color-background);opacity:0;flex-direction:column;flex:1;width:100%;margin:0 auto;animation:.2s ease-out forwards _6gbvKG_fadeInUp;display:flex;position:relative;overflow-x:hidden;transform:translateY(20px)}._6gbvKG_content{opacity:0;background-color:var(--expo-log-color-background);flex-direction:column;flex:1;width:100%;height:100%;animation:.2s ease-out forwards _6gbvKG_fadeInUp;display:flex}._6gbvKG_scroll{background-color:var(--expo-log-color-background);padding-top:16px;padding-bottom:calc(var(--expo-log-footer-height) + 1rem);flex-direction:column;flex:1;gap:.5rem;display:flex;position:relative;overflow:hidden auto}._6gbvKG_bg{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);pointer-events:all;z-index:-1;opacity:0;background-color:#000c;animation:.15s ease-out forwards _6gbvKG_data-expo-log-backdrop-fade-in;position:fixed;inset:0}._6gbvKG_bgExit{opacity:1;animation:.15s ease-in forwards _6gbvKG_fadeOut}._6gbvKG_containerExit{opacity:1;animation:.15s ease-in forwards _6gbvKG_fadeOutUp;transform:translateY(0)}@keyframes _6gbvKG_data-expo-log-backdrop-fade-in{to{opacity:1}}@keyframes _6gbvKG_fadeOut{to{opacity:0}}@keyframes _6gbvKG_fadeOutUp{to{opacity:0;transform:translateY(20px)}}@keyframes _6gbvKG_fadeInUp{to{opacity:1;transform:translateY(0)}}._6gbvKG_footer{min-height:var(--expo-log-footer-height);padding-bottom:var(--android-safe-area-inset-bottom,env(safe-area-inset-bottom));background-color:var(--expo-log-secondary-system-background);display:flex;position:absolute;bottom:0;left:0;right:0;overflow:hidden}._6gbvKG_headerBlur{background:linear-gradient(to top,transparent,var(--expo-log-color-background));-webkit-mask-image:linear-gradient(to bottom,var(--expo-log-color-background)50%,transparent);mask-image:linear-gradient(to bottom,var(--expo-log-color-background)50%,transparent);-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:1;pointer-events:none;opacity:0;height:48px;min-height:48px;position:absolute;top:47px;left:0;right:0}@media screen and (width>=576px){._6gbvKG_overlay{top:10vh;bottom:10vh}._6gbvKG_container{border:1px solid var(--expo-log-color-border);border-radius:10px}}@media (width>=576px){._6gbvKG_container{--expo-log-max-width:540px}}@media (width>=768px){._6gbvKG_container{--expo-log-max-width:720px}}@media (width>=992px){._6gbvKG_container{--expo-log-max-width:960px}}

View File

@@ -0,0 +1 @@
.LSZLAG_root{flex-direction:column;gap:6px;margin-top:5px;display:flex}.LSZLAG_header{justify-content:space-between;align-items:center;margin-bottom:4px;display:flex}.LSZLAG_headerLeft{align-items:center;gap:8px;display:flex}.LSZLAG_headerIcon{width:1rem;height:1rem;color:var(--expo-log-color-label)}.LSZLAG_headerTitle{font-family:var(--expo-log-font-family);color:var(--expo-log-color-label);margin:0;font-size:18px;font-weight:600}.LSZLAG_badge{font-family:var(--expo-log-font-family);color:var(--expo-log-color-label);aspect-ratio:1;background-color:#ebebf51a;border-radius:50px;justify-content:center;align-items:center;width:22px;height:22px;font-size:12px;display:flex}.LSZLAG_collapseButton{cursor:pointer;background:0 0;border:none;padding:0}.LSZLAG_collapseContent{color:#ebebf599;-webkit-user-select:none;user-select:none;border-radius:8px;outline-color:#0000;justify-content:center;align-items:center;gap:2px;padding:6px;transition:background-color .3s;display:flex}@media (hover:hover){.LSZLAG_collapseButton:hover .LSZLAG_collapseContent{background-color:#ebebf51a}}.LSZLAG_collapseTitle{font-family:var(--expo-log-font-family);-webkit-user-select:none;user-select:none;color:#ebebf599;font-size:14px;display:none}@media screen and (width>=320px){.LSZLAG_collapseTitle{display:block}}.LSZLAG_transition{transition:height .3s,opacity .3s;overflow:hidden}.LSZLAG_warningBox{background-color:var(--expo-log-secondary-system-background);border:1px solid var(--expo-log-color-border);border-radius:5px;padding:10px 15px}.LSZLAG_warningText{font-family:var(--expo-log-font-family);color:var(--expo-log-color-label);opacity:.7;font-size:13px;font-weight:400}.LSZLAG_list{flex-direction:column;gap:2px;display:flex}.LSZLAG_stackFrame{color:var(--expo-log-color-label);cursor:pointer;background-color:#0000;border-radius:8px;outline-color:#0000;flex-direction:column;flex:1;padding:6px 12px;transition:background-color .3s;display:flex}.LSZLAG_stackFrame[data-collapsed]{opacity:.4}.LSZLAG_stackFrame[aria-disabled=true]{color:var(--expo-log-color-label);cursor:unset;background-color:#0000}@media (hover:hover){.LSZLAG_stackFrame:hover{outline-color:var(--expo-log-color-label);background-color:#ebebf51a}}.LSZLAG_stackFrameTitle{overflow-wrap:break-word;font-size:14px;font-weight:400;line-height:1.5}.LSZLAG_stackFrameFile{opacity:.7;overflow-wrap:break-word;font-size:12px;font-weight:300}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<style id="expo-dom-component-style">
/* These styles make the body full-height */
html,
body {
-webkit-overflow-scrolling: touch; /* Enables smooth momentum scrolling */
}
/* These styles make the root element full-height */
#root {
display: flex;
flex: 1;
}
</style>
<link rel="preload" href="./_expo/static/css/AnsiHighlight.module-fedf6e48c823efc704595e66ae8d0af9.css" as="style"><link rel="stylesheet" href="./_expo/static/css/AnsiHighlight.module-fedf6e48c823efc704595e66ae8d0af9.css"><link rel="preload" href="./_expo/static/css/CodeFrame.module-1043e1883ad0fc007eff2839de5af950.css" as="style"><link rel="stylesheet" href="./_expo/static/css/CodeFrame.module-1043e1883ad0fc007eff2839de5af950.css"><link rel="preload" href="./_expo/static/css/Overlay.module-3d2d7fe1840eb1870c5916f377f0e5cd.css" as="style"><link rel="stylesheet" href="./_expo/static/css/Overlay.module-3d2d7fe1840eb1870c5916f377f0e5cd.css"><link rel="preload" href="./_expo/static/css/Header.module-a592345edc49abc03b030327ee8ec5e3.css" as="style"><link rel="stylesheet" href="./_expo/static/css/Header.module-a592345edc49abc03b030327ee8ec5e3.css"><link rel="preload" href="./_expo/static/css/LogBoxInspectorSourceMapStatus.module-0324e0bf2289db98d4d06d90697b72b1.css" as="style"><link rel="stylesheet" href="./_expo/static/css/LogBoxInspectorSourceMapStatus.module-0324e0bf2289db98d4d06d90697b72b1.css"><link rel="preload" href="./_expo/static/css/StackTraceList.module-5da402f75ccb2c1af9d4205add24827c.css" as="style"><link rel="stylesheet" href="./_expo/static/css/StackTraceList.module-5da402f75ccb2c1af9d4205add24827c.css"><link rel="preload" href="./_expo/static/css/ErrorToast.module-63c79167d7edf9981f4d6d656b33e302.css" as="style"><link rel="stylesheet" href="./_expo/static/css/ErrorToast.module-63c79167d7edf9981f4d6d656b33e302.css"></head>
<body>
<noscript>DOM Components require <code>javaScriptEnabled</code></noscript>
<!-- Root element for the DOM component. -->
<div id="root"></div>
<script src="./_expo/static/js/web/entry-d41d8cd98f00b204e9800998ecf8427e.js" defer></script>
</body>
</html>

9
node_modules/@expo/log-box/expo-module.config.json generated vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"platforms": ["apple", "android"],
"apple": {
"podspecPath": "ExpoLogBox.podspec"
},
"android": {
"modules": []
}
}

View File

@@ -0,0 +1,126 @@
#if canImport(UIKit) && EXPO_UNSTABLE_LOG_BOX
import UIKit
import WebKit
import React
@objc public class ExpoLogBoxScreenProvider: NSObject {
@objc public static func makeHostingController(message: String?, stack: [RCTJSStackFrame]?) -> UIViewController {
return ExpoLogBoxController(message: message, stack:stack)
}
}
struct Colors {
static let background = UIColor(red: 17/255.0,green: 17/255.0,blue: 19/255.0,alpha: 1.0)
}
class ExpoLogBoxController: UIViewController, ExpoLogBoxNativeActionsProtocol {
private var message: String
private var stack: [Dictionary<String, Any>]
init(message: String?, stack: [RCTJSStackFrame]?) {
self.message = message ?? "Error without message."
self.stack = stack?.map { frame in
return [
"file": frame.file ?? "unknown",
"methodName": frame.methodName ?? "unknown",
"arguments": [],
"lineNumber": frame.lineNumber,
"column": frame.column,
"collapse": frame.collapse,
]
} ?? []
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
self.message = "If you see this message this is an issue in ExpoLogBox."
self.stack = []
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Colors.background
isModalInPresentation = true
let webViewWrapper = ExpoLogBoxWebViewWrapper(nativeActions: self, props: [
"platform": "ios",
"nativeLogs": [
[
"message": self.message,
"stack": self.stack,
],
]
])
let webView = webViewWrapper.prepareWebView()
view.addSubview(webView)
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
#if EXPO_DEVELOP_LOG_BOX
// TODO: In the @expo/log-box add `yarn dev` which will return the same as
// http://localhost:8081/_expo/@dom/logbox-polyfill-dom.tsx?file=file:///user/repos/expo/expo/packages/@expo/log-box/src/logbox-polyfill-dom.tsx
// let myURL = URL(string:"http://localhost:8082/_expo/@dom/logbox-polyfill-dom.tsx?file=file:///Users/krystofwoldrich/repos/expo/expo/packages/@expo/log-box/src/logbox-polyfill-dom.tsx")
let myURL = URL(string:"http://localhost:8090")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
#else
let bundleURL = Bundle.main.url(forResource: "ExpoLogBox", withExtension: "bundle")
let bundle = Bundle(url: bundleURL!)
let url = bundle!.url(forResource: "index", withExtension: "html")
webView.loadFileURL(url!, allowingReadAccessTo: url!.deletingLastPathComponent())
#endif
}
func onReload() {
DispatchQueue.main.async {
RCTTriggerReloadCommandListeners("ExpoRedBoxSwap:Reload")
}
dismiss(animated: true)
}
func fetchTextAsync(url: String, method: String?, body: String?) async -> String {
let finalMethod = method ?? "GET"
let finalBody = body ?? ""
guard let url = URL(string: url) else {
print("Invalid URL: \(url)")
return "{}" // Return empty JSON object for invalid URL
}
var request = URLRequest(url: url)
request.httpMethod = finalMethod.uppercased()
// Set Content-Type for POST/PUT requests with body
if !finalBody.isEmpty && (finalMethod.uppercased() == "POST" || finalMethod.uppercased() == "PUT") {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = body!.data(using: .utf8)
}
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Invalid response status: \(String(describing: response))")
return "{}"
}
guard let jsonString = String(data: data, encoding: .utf8) else {
print("Failed to convert data to UTF-8 string")
return "{}"
}
return jsonString
} catch {
print("Request failed: \(error.localizedDescription)")
return "{}"
}
}
}
#endif

View File

@@ -0,0 +1,206 @@
// Keep in sync with webview-wrapper.tsx
// https://github.com/expo/expo/blob/main/packages/expo/src/dom/webview-wrapper.tsx
#if canImport(UIKit) && EXPO_UNSTABLE_LOG_BOX
import UIKit
import WebKit
import React
protocol ExpoLogBoxNativeActionsProtocol {
func onReload() -> Void
func fetchTextAsync(url: String, method: String?, body: String?) async -> String
}
private class ExpoLogBoxNativeActions: ExpoLogBoxNativeActionsProtocol {
func onReload() -> Void {
fatalError()
}
func fetchTextAsync(url: String, method: String?, body: String?) async -> String {
fatalError()
}
static let onReloadName = "onReload"
static let fetchTextAsyncName = "fetchTextAsync"
static let names = [
onReloadName,
fetchTextAsyncName,
]
}
private struct Constants {
static let DOM_EVENT = "$$dom_event"
static let NATIVE_ACTION_RESULT = "$$native_action_result"
static let NATIVE_ACTION = "$$native_action"
}
class ExpoLogBoxWebViewWrapper: NSObject, WKScriptMessageHandler {
private let nativeMessageHandlerName = "nativeHandler"
private let nativeActions: ExpoLogBoxNativeActionsProtocol
private let props: [String: Any]
private let webView: WKWebView
init(nativeActions: ExpoLogBoxNativeActionsProtocol, props: [String: Any]) {
self.nativeActions = nativeActions
self.props = props
self.webView = WKWebView(frame: .zero)
}
func prepareWebView() -> WKWebView {
let initProps: [String: Any] = [
"names": ExpoLogBoxNativeActions.names,
"props": props
]
guard let initPropsObject = try? JSONSerialization.data(withJSONObject: initProps, options: []),
let initPropsStringified = String(data: initPropsObject, encoding: .utf8) else {
fatalError("Failed to serialize initProps. This is an issue in ExpoLogBox. Please report it.")
}
let devServerOrigin: String? = {
guard let bundleUrl = RCTBundleURLProvider.sharedSettings()
.jsBundleURL(forBundleRoot: "unused.name"),
let scheme = bundleUrl.scheme,
let host = bundleUrl.host,
let port = bundleUrl.port
else {
return nil
}
return "\(scheme)://\(host):\(port)"
}()
let devServerOriginJsValue: String = devServerOrigin.map { "'\($0)'" } ?? "undefined"
let injectJavascript = """
var process = globalThis.process || {};
process.env = process.env || {};
process.env.EXPO_DEV_SERVER_ORIGIN = \(devServerOriginJsValue);
window.$$EXPO_DOM_HOST_OS = 'ios';
window.$$EXPO_INITIAL_PROPS = \(initPropsStringified);
window.ReactNativeWebView = {};
window.ReactNativeWebView.postMessage = (message) => {
window.webkit.messageHandlers.\(nativeMessageHandlerName).postMessage(
JSON.parse(message)
);
};
"""
webView.configuration.userContentController.addUserScript(WKUserScript(
source: injectJavascript,
injectionTime: .atDocumentStart,
forMainFrameOnly: true
))
if #available(iOS 16.4, *) {
#if EXPO_DEBUG_LOG_BOX || EXPO_DEVELOP_LOG_BOX
webView.isInspectable = true
#else
webView.isInspectable = false
#endif
}
webView.configuration.userContentController.add(self, name: nativeMessageHandlerName)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.isOpaque = false
return webView
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
Task.detached {
await self.handleWebViewMessageAsync(message: message)
}
}
func handleWebViewMessageAsync(message: WKScriptMessage) async {
guard message.name == nativeMessageHandlerName,
let messageBody = message.body as? [String: Any],
let messageType = messageBody["type"] as? String else {
return
}
let data = messageBody["data"] as? [String: Any] ?? [:]
if (messageType == Constants.NATIVE_ACTION) {
guard let actionId = data["actionId"] as? String,
let uid = data["uid"] as? String,
let args = data["args"] as? [Any] else {
print("ExpoLogBoxDomRuntimeError native actions is missing actionId or uid.")
return
}
do {
switch actionId {
case ExpoLogBoxNativeActions.onReloadName:
nativeActions.onReload()
case ExpoLogBoxNativeActions.fetchTextAsyncName:
guard let url = args[0] as? String,
let options = args[1] as? [String: Any] else {
print("ExpoLogBox fetchTextAsync action is missing url or options.")
return
}
let method = options["method"] as? String
let body = options["body"] as? String
let result = await nativeActions.fetchTextAsync(url: url, method: method, body: body)
sendReturn(result: result, uid: uid, actionId: actionId)
default:
print("Unknown native action: \(actionId)")
}
} catch {
sendReturn(error: error, uid: uid, actionId: actionId)
}
} else {
print("Unknown message type: \(messageType)")
}
}
func sendReturn(result: Any, uid: String, actionId: String) {
sendReturn(data: [
"type": Constants.NATIVE_ACTION_RESULT,
"data": [
"uid": uid,
"actionId": actionId,
"result": result,
],
])
}
func sendReturn(error: Any, uid: String, actionId: String) {
sendReturn(data: [
"type": Constants.NATIVE_ACTION_RESULT,
"data": [
"uid": uid,
"actionId": actionId,
"error": [
"message": "\(error)",
],
],
])
}
func sendReturn(data: [String: Any]) {
guard let jsonData = try? JSONSerialization.data(
withJSONObject: [ "detail": data ],
options: []
), let jsonDataStringified = String(data: jsonData, encoding: .utf8) else {
print("ExpoLogBox failed to stringify native action result.")
return
}
sendReturn(value: jsonDataStringified)
}
func sendReturn(value: String) {
let injectedJavascript = """
;
(function() {
try {
console.log("received", \(value));
window.dispatchEvent(new CustomEvent("\(Constants.DOM_EVENT)", \(value)));
} catch (e) {}
})();
true;
"""
webView.evaluateJavaScript(injectedJavascript) { _, error in
if let error = error {
print("ExpoLogBox NativeActions return value injection error: \(error.localizedDescription)")
}
}
}
}
#endif

48
node_modules/@expo/log-box/ios/ExpoRedBoxSwap.mm generated vendored Normal file
View File

@@ -0,0 +1,48 @@
#if !TARGET_OS_MACCATALYST && EXPO_UNSTABLE_LOG_BOX
#import <objc/runtime.h>
#import <React/RCTRedBox.h>
#import <React/RCTUtils.h>
#import "ExpoLogBox-Swift.h"
@implementation RCTRedBox (WithExpoLogBox)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class classs = [self class];
SEL originalSelector = @selector(showErrorMessage:withParsedStack:isUpdate:errorCookie:);
SEL swizzledSelector = @selector(showErrorMessageWithExpoLogBox:withParsedStack:isUpdate:errorCookie:);
Method originalMethod = class_getInstanceMethod(classs, originalSelector);
Method swizzledMethod = class_getInstanceMethod(classs, swizzledSelector);
BOOL didAddMethod =
class_addMethod(classs,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(classs,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)showErrorMessageWithExpoLogBox:(NSString *)message
withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
isUpdate: (BOOL) isUpdate
errorCookie:(int)errorCookie {
UIViewController *expoRedBox = [ExpoLogBoxScreenProvider makeHostingControllerWithMessage:message stack:stack];
[RCTKeyWindow().rootViewController presentViewController:expoRedBox animated:YES completion:nil];
}
@end
#endif

1
node_modules/@expo/log-box/lib.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export * from './src/index';

9
node_modules/@expo/log-box/metro.config.js generated vendored Normal file
View File

@@ -0,0 +1,9 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
config.transformerPath = require.resolve('./shadowDomCssTransformer');
module.exports = config;

49
node_modules/@expo/log-box/package.json generated vendored Normal file
View File

@@ -0,0 +1,49 @@
{
"name": "@expo/log-box",
"version": "55.0.7",
"main": "app/index.ts",
"scripts": {
"start": "expo start",
"lint": "expo-module lint",
"typecheck": "expo-module typecheck",
"clean": "run-p clean:lib clean:bundle",
"clean:lib": "rimraf build",
"clean:bundle": "rimraf dist",
"watch": "run-p watch:lib watch:bundle",
"watch:lib": "tsc -p tsconfig.lib.json --watch",
"watch:bundle": "node ./scripts/watch-bundle.mjs",
"prepare": "run-s clean build",
"build": "run-s build:lib build:bundle",
"build:lib": "tsc -p tsconfig.lib.json",
"build:bundle": "node ./scripts/build-bundle.mjs",
"export:web": "expo export -p web --no-minify"
},
"dependencies": {
"@expo/dom-webview": "^55.0.3",
"anser": "^1.4.9",
"stacktrace-parser": "^0.1.10"
},
"devDependencies": {
"@expo/spawn-async": "^1.7.2",
"@types/react": "~19.2.0",
"expo-module-scripts": "^55.0.2",
"glob": "^13.0.0",
"npm-run-all2": "^8.0.4",
"react": "19.2.0",
"react-native": "0.83.2",
"rimraf": "^6.1.2",
"typescript": "~5.9.2",
"typescript-plugin-css-modules": "^5.2.0"
},
"peerDependencies": {
"@expo/dom-webview": "^55.0.3",
"expo": "*",
"react": "*",
"react-native": "*"
},
"license": "MIT",
"author": "Expo",
"homepage": "https://github.com/expo/expo/tree/main/packages/@expo/log-box",
"description": "Error overlay for universal Expo apps.",
"gitHead": "928cc951854450f3c72e00e8e420e567fabd1f8c"
}

18
node_modules/@expo/log-box/shadowDomCssTransformer.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
const defaultTransformerPath = require('expo/metro-config').unstable_transformerPath;
const defaultTransform = require(defaultTransformerPath).transform;
function transform(config, projectRoot, filename, data, options, ...rest) {
let newData = data;
if (filename.endsWith('.css')) {
// We need to rewrite :host to :root as the DOM component
// on iOS and Android does not render in shadow DOM.
const originalCss = newData.toString('utf8');
const newCss = originalCss.replace(/^:host/gm, ':root');
newData = Buffer.from(newCss, 'utf8');
}
return defaultTransform(config, projectRoot, filename, newData, options, ...rest);
}
module.exports = {
transform,
};

42
node_modules/@expo/log-box/src/ContextActions.tsx generated vendored Normal file
View File

@@ -0,0 +1,42 @@
import React, { createContext, ReactNode, use } from 'react';
interface ActionsContextType {
onMinimize: (() => void) | undefined;
onReload: (() => void) | undefined;
onCopyText: ((text: string) => void) | undefined;
}
const ActionsContextProvider = createContext<ActionsContextType>({
onMinimize: undefined,
onReload: undefined,
onCopyText: undefined,
});
export const ActionsContext: React.FC<{ children: ReactNode } & ActionsContextType> = ({
children,
onMinimize,
onReload,
onCopyText,
}) => {
return (
<ActionsContextProvider value={{ onMinimize, onReload, onCopyText }}>
{children}
</ActionsContextProvider>
);
};
export const withActions = (Component: React.FC, actions: ActionsContextType) => {
return (props: any) => (
<ActionsContext {...actions}>
<Component {...props} />
</ActionsContext>
);
};
export const useActions = (): ActionsContextType => {
const context = use(ActionsContextProvider);
if (context === undefined) {
throw new Error('useActions must be used within an ActionsProvider');
}
return context;
};

51
node_modules/@expo/log-box/src/ContextDevServer.tsx generated vendored Normal file
View File

@@ -0,0 +1,51 @@
import React, { useEffect, useState, createContext, use, ReactNode } from 'react';
import { fetchProjectMetadataAsync } from './utils/devServerEndpoints';
// Dev Server implementation https://github.com/expo/expo/blob/f29b9f3715e42dca87bf3eebf11f7e7dd1ff73c1/packages/%40expo/cli/src/start/server/metro/MetroBundlerDevServer.ts#L1145
function useProjectMetadataFromServer() {
const [meta, setMeta] = useState<DevServerContextType | null>(null);
useEffect(() => {
fetchProjectMetadataAsync()
.then(setMeta)
.catch((error) => {
console.warn(
`Failed to fetch project metadata. Some debugging features may not work as expected: ${error}`
);
});
}, []);
return meta;
}
interface DevServerContextType {
projectRoot: string | undefined;
serverRoot: string | undefined;
sdkVersion: string | undefined;
}
const DevServerContextProvider = createContext<DevServerContextType | undefined>(undefined);
export const DevServerContext: React.FC<{ children: ReactNode }> = ({ children }) => {
const meta = useProjectMetadataFromServer();
return (
<DevServerContextProvider
value={{
projectRoot: meta?.projectRoot,
serverRoot: meta?.serverRoot,
sdkVersion: meta?.sdkVersion,
}}>
{children}
</DevServerContextProvider>
);
};
export const useDevServer = (): DevServerContextType => {
const context = use(DevServerContextProvider);
if (context === undefined) {
throw new Error('useDevServer must be used within a DevServerProvider');
}
return context;
};

471
node_modules/@expo/log-box/src/Data/LogBoxData.tsx generated vendored Normal file
View File

@@ -0,0 +1,471 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use client';
import * as React from 'react';
import { NativeEventEmitter } from 'react-native';
import { LogBoxLog, LogContext } from './LogBoxLog';
import type { LogLevel, MetroStackFrame, StackType, Category, Message } from './Types';
import type { ExtendedExceptionData } from './parseLogBoxLog';
import { isError, parseLogBoxException, parseLogBoxLog } from './parseLogBoxLog';
import { parseErrorStack } from '../utils/parseErrorStack';
export type LogBoxLogs = Set<LogBoxLog>;
export type LogData = {
level: LogLevel;
message: Message;
category: Category;
componentStack: MetroStackFrame[];
};
export type Observer = (options: {
logs: LogBoxLogs;
isDisabled: boolean;
selectedLogIndex: number;
}) => void;
export type IgnorePattern = string | RegExp;
export type Subscription = {
unsubscribe: () => void;
};
export type WarningInfo = {
finalFormat: string;
forceDialogImmediately: boolean;
suppressDialog_LEGACY: boolean;
suppressCompletely: boolean;
monitorEvent: string | null;
monitorListVersion: number;
monitorSampleRate: number;
};
export type WarningFilter = (format: string) => WarningInfo;
let warningFilter: WarningFilter = function (format) {
return {
finalFormat: format,
forceDialogImmediately: false,
suppressDialog_LEGACY: false,
suppressCompletely: false,
monitorEvent: 'warning_unhandled',
monitorListVersion: 0,
monitorSampleRate: 1,
};
};
export function setWarningFilter(filter: WarningFilter): void {
warningFilter = filter;
}
export function checkWarningFilter(format: string): WarningInfo {
return warningFilter(format);
}
type Props = object;
type State = {
logs: LogBoxLogs;
isDisabled: boolean;
hasError: boolean;
selectedLogIndex: number;
};
const observers: Set<{ observer: Observer } & any> = new Set();
const ignorePatterns: Set<IgnorePattern> = new Set();
let logs: LogBoxLogs = new Set();
let updateTimeout: null | ReturnType<typeof setTimeout> = null;
let _isDisabled = false;
let _selectedIndex = -1;
const LOGBOX_ERROR_MESSAGE = 'An error was thrown while presenting an error!';
function getNextState() {
return {
logs,
isDisabled: _isDisabled,
selectedLogIndex: _selectedIndex,
};
}
export function reportUnexpectedLogBoxError(error: any): void {
if (isError(error)) {
error.message = `${LOGBOX_ERROR_MESSAGE}\n\n${error.message}`;
}
addException(error);
}
export function isLogBoxErrorMessage(message: string): boolean {
return typeof message === 'string' && message.includes(LOGBOX_ERROR_MESSAGE);
}
export function isMessageIgnored(message: string): boolean {
for (const pattern of ignorePatterns) {
if (
(pattern instanceof RegExp && pattern.test(message)) ||
(typeof pattern === 'string' && message.includes(pattern))
) {
return true;
}
}
return false;
}
function handleUpdate(): void {
if (updateTimeout == null) {
updateTimeout = setTimeout(() => {
updateTimeout = null;
const nextState = getNextState();
observers.forEach(({ observer }) => observer(nextState));
}, 0);
}
}
/** Exposed for debugging */
export function _appendNewLog(newLog: LogBoxLog): void {
// Don't want store these logs because they trigger a
// state update when we add them to the store.
if (isMessageIgnored(newLog.message.content)) {
return;
}
// If the next log has the same category as the previous one
// then roll it up into the last log in the list by incrementing
// the count (similar to how Chrome does it).
const lastLog = Array.from(logs).pop();
if (lastLog && lastLog.category === newLog.category) {
if (lastLog.level === newLog.level) {
lastLog.incrementCount();
handleUpdate();
return;
} else {
// Determine which one is more important. This is because console.error for React errors shows before the more important root componentDidCatch which should force the UI to show.
if (newLog.level === 'fatal') {
// If the new log is fatal, then we want to show it
// and hide the last one.
newLog.count = lastLog.count;
logs.delete(lastLog);
}
}
}
if (newLog.level === 'fatal') {
// If possible, to avoid jank, we don't want to open the error before
// it's symbolicated. To do that, we optimistically wait for
// symbolication for up to a second before adding the log.
const OPTIMISTIC_WAIT_TIME = 1000;
let addPendingLog: null | (() => void) = () => {
logs.add(newLog);
if (_selectedIndex < 0) {
setSelectedLog(logs.size - 1);
} else {
handleUpdate();
}
addPendingLog = null;
};
const optimisticTimeout = setTimeout(() => {
if (addPendingLog) {
addPendingLog();
}
}, OPTIMISTIC_WAIT_TIME);
// TODO: HANDLE THIS
newLog.symbolicate('component');
newLog.symbolicate('stack', (status) => {
if (addPendingLog && status !== 'PENDING') {
addPendingLog();
clearTimeout(optimisticTimeout);
} else if (status !== 'PENDING') {
// The log has already been added but we need to trigger a render.
handleUpdate();
}
});
} else if (newLog.level === 'syntax' || newLog.level === 'resolution') {
logs.add(newLog);
setSelectedLog(logs.size - 1);
} else {
logs.add(newLog);
handleUpdate();
}
}
export function addLog(log: LogData): void {
const errorForStackTrace = new Error();
// Parsing logs are expensive so we schedule this
// otherwise spammy logs would pause rendering.
setTimeout(() => {
try {
const stack = parseErrorStack(errorForStackTrace?.stack);
_appendNewLog(
new LogBoxLog({
level: log.level,
message: log.message,
isComponentError: !!log.componentStack?.length,
stack,
category: log.category,
componentStack: log.componentStack,
codeFrame: {},
})
);
} catch (unexpectedError: any) {
reportUnexpectedLogBoxError(unexpectedError);
}
}, 0);
}
export function addException(error: ExtendedExceptionData): void {
// Parsing logs are expensive so we schedule this
// otherwise spammy logs would pause rendering.
setTimeout(() => {
try {
_appendNewLog(new LogBoxLog(parseLogBoxException(error)));
} catch (unexpectedError: any) {
reportUnexpectedLogBoxError(unexpectedError);
}
}, 0);
}
export function symbolicateLogNow(type: StackType, log: LogBoxLog) {
log.symbolicate(type, () => {
handleUpdate();
});
}
export function retrySymbolicateLogNow(type: StackType, log: LogBoxLog) {
log.retrySymbolicate(type, () => {
handleUpdate();
});
}
export function symbolicateLogLazy(type: StackType, log: LogBoxLog) {
log.symbolicate(type);
}
export function clear(): void {
if (logs.size > 0) {
logs = new Set();
setSelectedLog(-1);
}
}
export function setSelectedLog(proposedNewIndex: number): void {
const oldIndex = _selectedIndex;
let newIndex = proposedNewIndex;
const logArray = Array.from(logs);
let index = logArray.length - 1;
while (index >= 0) {
// The latest syntax error is selected and displayed before all other logs.
if (logArray[index].level === 'syntax' || logArray[index].level === 'resolution') {
newIndex = index;
break;
}
index -= 1;
}
_selectedIndex = newIndex;
handleUpdate();
if (process.env.EXPO_OS === 'web') {
setTimeout(() => {
if (oldIndex < 0 && newIndex >= 0) {
require('../ErrorOverlayWebControls').presentGlobalErrorOverlay();
} else if (oldIndex >= 0 && newIndex < 0) {
require('../ErrorOverlayWebControls').dismissGlobalErrorOverlay();
}
}, 0);
}
}
export function clearErrors(): void {
const newLogs = Array.from(logs).filter((log) => log.level !== 'error' && log.level !== 'fatal');
if (newLogs.length !== logs.size) {
logs = new Set(newLogs);
setSelectedLog(-1);
}
}
export function dismiss(log: LogBoxLog): void {
if (logs.has(log)) {
logs.delete(log);
handleUpdate();
} else {
// Find log with matching message
const message = log.message.content;
const logToDismiss = Array.from(logs).find((l) => l.message.content === message);
if (logToDismiss) {
logs.delete(logToDismiss);
handleUpdate();
} else {
console.warn('LogBoxLog not found in logs:', log, logs);
}
}
}
export function getIgnorePatterns(): IgnorePattern[] {
return Array.from(ignorePatterns);
}
export function addIgnorePatterns(patterns: IgnorePattern[]): void {
const existingSize = ignorePatterns.size;
// The same pattern may be added multiple times, but adding a new pattern
// can be expensive so let's find only the ones that are new.
patterns.forEach((pattern: IgnorePattern) => {
if (pattern instanceof RegExp) {
for (const existingPattern of ignorePatterns) {
if (
existingPattern instanceof RegExp &&
existingPattern.toString() === pattern.toString()
) {
return;
}
}
ignorePatterns.add(pattern);
}
ignorePatterns.add(pattern);
});
if (ignorePatterns.size === existingSize) {
return;
}
// We need to recheck all of the existing logs.
// This allows adding an ignore pattern anywhere in the codebase.
// Without this, if you ignore a pattern after the a log is created,
// then we would keep showing the log.
logs = new Set(Array.from(logs).filter((log) => !isMessageIgnored(log.message.content)));
handleUpdate();
}
export function setDisabled(value: boolean): void {
if (value === _isDisabled) {
return;
}
_isDisabled = value;
handleUpdate();
}
export function isDisabled(): boolean {
return _isDisabled;
}
export function observe(observer: Observer): Subscription {
const subscription = { observer };
observers.add(subscription);
observer(getNextState());
return {
unsubscribe(): void {
observers.delete(subscription);
},
};
}
const emitter = new NativeEventEmitter({
addListener() {},
removeListeners() {},
});
export function withSubscription(WrappedComponent: React.FC<any>) {
class RootDevErrorBoundary extends React.Component<React.PropsWithChildren<Props>, State> {
static getDerivedStateFromError() {
return { hasError: true };
}
constructor(props: object) {
super(props);
if (process.env.NODE_ENV === 'development') {
emitter.addListener('devLoadingView:hide', () => {
if (this.state.hasError) {
this.retry();
}
});
}
}
componentDidCatch(
err: Error & { componentStack?: string },
errorInfo: { componentStack: string } & any
) {
// TODO: Won't this catch all React errors and make them appear as unexpected rendering errors?
err.componentStack ??= errorInfo.componentStack;
// TODO: Make the error appear more like the React console.error, appending the "The above error occurred" line.
const { category, message, componentStack } = parseLogBoxLog([err]);
if (!isMessageIgnored(message.content)) {
addLog({
// Always show the static rendering issues as full screen since they
// are too confusing otherwise.
level: 'fatal',
category,
message,
componentStack,
});
}
}
_subscription?: Subscription;
state = {
logs: new Set<LogBoxLog>(),
isDisabled: false,
hasError: false,
selectedLogIndex: -1,
};
retry = () => {
return new Promise<void>((resolve) => {
this.setState({ hasError: false }, () => {
resolve();
});
});
};
render() {
return (
<LogContext.Provider
value={{
selectedLogIndex: this.state.selectedLogIndex,
isDisabled: this.state.isDisabled,
logs: Array.from(this.state.logs),
}}>
{this.props.children}
<WrappedComponent />
</LogContext.Provider>
);
}
componentDidMount(): void {
this._subscription = observe((data) => {
// Ignore the initial empty log
// if (data.selectedLogIndex === -1) return;
React.startTransition(() => {
this.setState(data);
});
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}
}
return RootDevErrorBoundary;
}

223
node_modules/@expo/log-box/src/Data/LogBoxLog.ts generated vendored Normal file
View File

@@ -0,0 +1,223 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import type {
LogLevel,
Message,
Category,
CodeFrame,
SymbolicationStatus,
StackType,
LogBoxLogData,
MetroStackFrame,
} from './Types';
import { symbolicateStackAndCacheAsync, invalidateCachedStack } from '../utils/devServerEndpoints';
type SymbolicationCallback = (status: SymbolicationStatus) => void;
type SymbolicationResult =
| { error: null; stack: null; status: 'NONE' }
| { error: null; stack: null; status: 'PENDING' }
| { error: null; stack: MetroStackFrame[]; status: 'COMPLETE' }
| { error: Error; stack: null; status: 'FAILED' };
export class LogBoxLog {
message: Message;
type: string;
category: Category;
componentStack: MetroStackFrame[];
stack: MetroStackFrame[];
count: number;
level: LogLevel;
codeFrame: Partial<Record<StackType, CodeFrame>> = {};
isComponentError: boolean;
isMissingModuleError?: string;
private symbolicated: Record<StackType, SymbolicationResult> = {
stack: {
error: null,
stack: null,
status: 'NONE',
},
component: {
error: null,
stack: null,
status: 'NONE',
},
};
private callbacks: Map<StackType, Set<SymbolicationCallback>> = new Map();
constructor(
data: LogBoxLogData & {
symbolicated?: Record<StackType, SymbolicationResult>;
}
) {
this.level = data.level;
this.type = data.type ?? 'error';
this.message = data.message;
this.stack = data.stack;
this.category = data.category;
this.componentStack = data.componentStack;
this.codeFrame = data.codeFrame;
this.isComponentError = data.isComponentError;
this.count = 1;
this.symbolicated = data.symbolicated ?? this.symbolicated;
this.isMissingModuleError = data.isMissingModuleError;
}
incrementCount(): void {
this.count += 1;
}
getStackStatus(type: StackType) {
return this.symbolicated[type]?.status;
}
getAvailableStack(type: StackType): MetroStackFrame[] | null {
if (this.symbolicated[type]?.status === 'COMPLETE') {
return this.symbolicated[type].stack;
}
return this.getStack(type);
}
private flushCallbacks(type: StackType): void {
const callbacks = this.callbacks.get(type);
const status = this.symbolicated[type]?.status;
if (callbacks) {
for (const callback of callbacks) {
callback(status);
}
callbacks.clear();
}
}
private pushCallback(type: StackType, callback: SymbolicationCallback): void {
let callbacks = this.callbacks.get(type);
if (!callbacks) {
callbacks = new Set();
this.callbacks.set(type, callbacks);
}
callbacks.add(callback);
}
retrySymbolicate(type: StackType, callback?: (status: SymbolicationStatus) => void): void {
this._symbolicate(type, true, callback);
}
symbolicate(type: StackType, callback?: (status: SymbolicationStatus) => void): void {
this._symbolicate(type, false, callback);
}
private _symbolicate(
type: StackType,
retry: boolean,
callback?: (status: SymbolicationStatus) => void
): void {
if (callback) {
this.pushCallback(type, callback);
}
const status = this.symbolicated[type]?.status;
if (status === 'COMPLETE') {
return this.flushCallbacks(type);
}
if (retry) {
invalidateCachedStack(this.getStack(type));
this.handleSymbolicate(type);
} else {
if (status === 'NONE') {
this.handleSymbolicate(type);
}
}
}
private getStack(type: StackType): MetroStackFrame[] {
if (type === 'component') {
return this.componentStack;
}
return this.stack;
}
private handleSymbolicate(type: StackType): void {
if (this.symbolicated[type]?.status === 'PENDING') {
return;
}
this.updateStatus(type, null, null, null);
symbolicateStackAndCacheAsync(this.getStack(type)).then(
(data) => {
this.updateStatus(type, null, data?.stack, data?.codeFrame);
},
(error) => {
this.updateStatus(type, error, null, null);
}
);
}
private updateStatus(
type: StackType,
error?: Error | null,
stack?: MetroStackFrame[] | null,
codeFrame?: CodeFrame | null
): void {
const lastStatus = this.symbolicated[type]?.status;
if (error != null) {
this.symbolicated[type] = {
error,
stack: null,
status: 'FAILED',
};
} else if (stack != null) {
if (codeFrame) {
this.codeFrame[type] = codeFrame;
}
this.symbolicated[type] = {
error: null,
stack,
status: 'COMPLETE',
};
} else {
this.symbolicated[type] = {
error: null,
stack: null,
status: 'PENDING',
};
}
const status = this.symbolicated[type]?.status;
if (lastStatus !== status) {
if (['COMPLETE', 'FAILED'].includes(status)) {
this.flushCallbacks(type);
}
}
}
}
export const LogContext = React.createContext<{
selectedLogIndex: number;
isDisabled: boolean;
logs: LogBoxLog[];
} | null>(null);
export function useLogs(): {
selectedLogIndex: number;
isDisabled: boolean;
logs: LogBoxLog[];
} {
const logs = React.use(LogContext);
if (!logs) {
throw new Error('useLogs must be used within a LogContext.Provider');
}
return logs;
}

54
node_modules/@expo/log-box/src/Data/Types.ts generated vendored Normal file
View File

@@ -0,0 +1,54 @@
import { type StackFrame as UpstreamStackFrame } from 'stacktrace-parser';
export type Message = {
content: string;
substitutions: {
length: number;
offset: number;
}[];
};
export type Category = string;
export type CodeFrame = {
content: string;
location?: {
row: number;
column: number;
[key: string]: any;
} | null;
fileName: string;
collapse?: boolean;
};
export type SymbolicationStatus = 'NONE' | 'PENDING' | 'COMPLETE' | 'FAILED';
export type LogLevel = 'error' | 'fatal' | 'syntax' | 'resolution' | 'static';
export type StackType = 'stack' | 'component';
export type LogBoxLogData = {
level: LogLevel;
type?: string;
message: Message;
stack: MetroStackFrame[];
category: string;
componentStack: MetroStackFrame[];
codeFrame: Partial<Record<StackType, CodeFrame>>;
isComponentError: boolean;
isMissingModuleError?: string;
extraData?: Record<string, unknown>;
};
export type LogBoxLogDataLegacy = {
level: LogLevel;
type?: string;
message: Message;
stack: MetroStackFrame[];
category: string;
componentStack: CodeFrame[];
codeFrame?: CodeFrame;
isComponentError: boolean;
};
export type MetroStackFrame = Omit<UpstreamStackFrame, 'arguments'> & { collapse?: boolean };

458
node_modules/@expo/log-box/src/Data/parseLogBoxLog.ts generated vendored Normal file
View File

@@ -0,0 +1,458 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import type { Category, LogBoxLogData, Message, MetroStackFrame } from './Types';
import {
parseBabelCodeFrameError,
parseBabelTransformError,
type ParsedBuildError,
parseMetroError,
} from '../utils/metroBuildErrorsFormat';
import { parseErrorStack } from '../utils/parseErrorStack';
type ExceptionData = {
message: string;
originalMessage: string | undefined;
name: string | undefined;
componentStack: string | undefined;
stack: {
column: number | null;
file: string | null;
lineNumber: number | null;
methodName: string;
collapse?: boolean;
}[];
id: number;
isFatal: boolean;
extraData?: Record<string, unknown>;
[key: string]: unknown;
};
export type ExtendedExceptionData = ExceptionData & {
isComponentError: boolean;
[key: string]: any;
};
const SUBSTITUTION = '\ufeff%s';
// https://github.com/krystofwoldrich/react-native/blob/7db31e2fca0f828aa6bf489ae6dc4adef9b7b7c3/packages/react-native/Libraries/LogBox/Data/parseLogBoxLog.js#L130
// In RN the original is not used outside of this file.
// TODO: Get rid of this. The substitution logic is wild.
export function parseInterpolation(args: readonly any[]): {
category: Category;
message: Message;
} {
const categoryParts: string[] = [];
const contentParts: string[] = [];
const substitutionOffsets: { length: number; offset: number }[] = [];
const remaining = [...args];
if (typeof remaining[0] === 'string') {
const formatString = String(remaining.shift());
const formatStringParts = formatString.split('%s');
const substitutionCount = formatStringParts.length - 1;
const substitutions = remaining.splice(0, substitutionCount);
let categoryString = '';
let contentString = '';
let substitutionIndex = 0;
for (const formatStringPart of formatStringParts) {
categoryString += formatStringPart;
contentString += formatStringPart;
if (substitutionIndex < substitutionCount) {
if (substitutionIndex < substitutions.length) {
// Don't stringify a string type.
// It adds quotation mark wrappers around the string,
// which causes the LogBox to look odd.
const substitution =
typeof substitutions[substitutionIndex] === 'string'
? substitutions[substitutionIndex]
: stringifySafe(substitutions[substitutionIndex]);
substitutionOffsets.push({
length: substitution.length,
offset: contentString.length,
});
categoryString += SUBSTITUTION;
contentString += substitution;
} else {
substitutionOffsets.push({
length: 2,
offset: contentString.length,
});
categoryString += '%s';
contentString += '%s';
}
substitutionIndex++;
}
}
categoryParts.push(categoryString);
contentParts.push(contentString);
}
const remainingArgs = remaining.map((arg) => {
// Don't stringify a string type.
// It adds quotation mark wrappers around the string,
// which causes the LogBox to look odd.
return typeof arg === 'string' ? arg : stringifySafe(arg);
});
categoryParts.push(...remainingArgs);
contentParts.push(...remainingArgs);
return {
category: categoryParts.join(' '),
message: {
content: contentParts.join(' '),
substitutions: substitutionOffsets,
},
};
}
export function parseLogBoxException(error: ExtendedExceptionData): LogBoxLogData {
const message = error.originalMessage != null ? error.originalMessage : 'Unknown';
let parsed: ParsedBuildError | null = null;
if ((parsed = parseMetroError(message))) {
const { content, fileName, row, column, codeFrame } = parsed;
return {
level: 'fatal',
type: 'Metro Error',
stack: [],
isComponentError: false,
componentStack: [],
codeFrame: {
stack: {
fileName,
location: {
row,
column,
},
content: codeFrame,
},
},
message: {
content,
substitutions: [],
},
category: `${fileName}-${row}-${column}`,
extraData: error.extraData,
};
}
if ((parsed = parseBabelTransformError(message))) {
// Transform errors are thrown from inside the Babel transformer.
const { fileName, content, row, column, codeFrame } = parsed;
return {
level: 'syntax',
stack: [],
isComponentError: false,
componentStack: [],
codeFrame: {
stack: {
fileName,
location: {
row,
column,
},
content: codeFrame,
},
},
message: {
content,
substitutions: [],
},
category: `${fileName}-${row}-${column}`,
extraData: error.extraData,
};
}
if ((parsed = parseBabelCodeFrameError(message))) {
const { fileName, content, codeFrame } = parsed;
return {
level: 'syntax',
stack: [],
isComponentError: false,
componentStack: [],
codeFrame: {
stack: {
fileName,
location: null, // We are not given the location.
content: codeFrame,
},
},
message: {
content,
substitutions: [],
},
category: `${fileName}-${1}-${1}`,
extraData: error.extraData,
};
}
if (message.match(/^TransformError /)) {
return {
level: 'syntax',
stack: error.stack,
isComponentError: error.isComponentError,
componentStack: [],
codeFrame: {},
message: {
content: message,
substitutions: [],
},
category: message,
extraData: error.extraData,
};
}
const componentStack = error.componentStack;
if (error.isFatal || error.isComponentError) {
return {
level: 'fatal',
stack: error.stack,
codeFrame: {},
isComponentError: error.isComponentError,
componentStack: componentStack != null ? parseErrorStack(componentStack) : [],
extraData: error.extraData,
...parseInterpolation([message]),
};
}
if (componentStack != null) {
// It is possible that console errors have a componentStack.
return {
level: 'error',
stack: error.stack,
codeFrame: {},
isComponentError: error.isComponentError,
componentStack: parseErrorStack(componentStack),
extraData: error.extraData,
...parseInterpolation([message]),
};
}
// Most `console.error` calls won't have a componentStack. We parse them like
// regular logs which have the component stack buried in the message.
return {
level: 'error',
stack: error.stack,
codeFrame: {},
isComponentError: error.isComponentError,
extraData: error.extraData,
...parseLogBoxLog([message]),
};
}
function interpolateLikeConsole(...args: any[]) {
let output = '';
if (typeof args[0] === 'string') {
const format = args[0];
const rest = args.slice(1);
let argIndex = 0;
// TODO: %c for colors
output = format.replace(/%[sdifoO%]/g, (match) => {
if (match === '%%') return '%'; // escape %%
const arg = rest[argIndex++];
switch (match) {
case '%s':
return String(arg);
case '%d':
case '%i':
return parseInt(arg, 10).toString();
case '%f':
return parseFloat(arg).toString();
case '%o':
case '%O':
return stringifySafe(arg);
default:
return match;
}
});
// Append any remaining arguments
for (; argIndex < rest.length; argIndex++) {
const arg = rest[argIndex];
output +=
' ' +
(typeof arg === 'object'
? arg instanceof Error
? arg.stack || arg.toString()
: JSON.stringify(arg, null, 2)
: String(arg));
}
} else {
// No format string, just join args with spaces
output = args.map((arg) => stringifySafe(arg)).join(' ');
}
return output;
}
export function isError(err: any): err is Error {
return typeof err === 'object' && err !== null && 'name' in err && 'message' in err;
}
export function parseLogBoxLog(args: any[]): {
componentStack: MetroStackFrame[];
category: Category;
message: Message;
} {
// React will pass a full error object to the console.error function.
// https://github.com/facebook/react/blob/c44e4a250557e53b120e40db8b01fb5fd93f1e35/packages/react-reconciler/src/ReactFiberErrorLogger.js#L105
// But we can't be sure at which order, so we'll check all arguments.
let error: Error | undefined;
for (const arg of args) {
if (isError(arg)) {
error = arg;
break;
}
}
// Create a string representation of the error arguments.
const message = interpolateLikeConsole(...args);
// If no error was passed, create a new Error object with the message.
if (!isError(error)) {
error = new Error(message);
}
// Use the official stack from componentDidCatch
if ('componentStack' in error) {
// @ts-expect-error
error.stack = error.componentStack;
} else {
// Try to capture owner stack now if missing.
error.stack = React.captureOwnerStack() || undefined;
}
return {
componentStack: parseErrorStack(error.stack ?? ''),
category: error.message,
message: {
content: message,
substitutions: [],
},
};
}
/**
* Tries to stringify with JSON.stringify and toString, but catches exceptions
* (e.g. from circular objects) and always returns a string and never throws.
*/
function createStringifySafeWithLimits(limits: {
maxDepth?: number;
maxStringLimit?: number;
maxArrayLimit?: number;
maxObjectKeysLimit?: number;
}): (foo: any) => string {
const {
maxDepth = Number.POSITIVE_INFINITY,
maxStringLimit = Number.POSITIVE_INFINITY,
maxArrayLimit = Number.POSITIVE_INFINITY,
maxObjectKeysLimit = Number.POSITIVE_INFINITY,
} = limits;
const stack: any[] = [];
function replacer(this: unknown, _key: string, value: any): any {
while (stack.length && this !== stack[0]) {
stack.shift();
}
if (typeof value === 'string') {
const truncatedString = '...(truncated)...';
if (value.length > maxStringLimit + truncatedString.length) {
return value.substring(0, maxStringLimit) + truncatedString;
}
return value;
}
if (typeof value !== 'object' || value === null) {
return value;
}
let retval = value;
if (Array.isArray(value)) {
if (stack.length >= maxDepth) {
retval = `[ ... array with ${value.length} values ... ]`;
} else if (value.length > maxArrayLimit) {
retval = value
.slice(0, maxArrayLimit)
.concat([`... extra ${value.length - maxArrayLimit} values truncated ...`]);
}
} else {
// Add refinement after Array.isArray call.
if (typeof value !== 'object') {
throw new Error('This was already found earlier');
}
const keys = Object.keys(value);
if (stack.length >= maxDepth) {
retval = `{ ... object with ${keys.length} keys ... }`;
} else if (keys.length > maxObjectKeysLimit) {
// Return a sample of the keys.
retval = {};
for (const k of keys.slice(0, maxObjectKeysLimit)) {
retval[k] = value[k];
}
const truncatedKey = '...(truncated keys)...';
retval[truncatedKey] = keys.length - maxObjectKeysLimit;
}
}
stack.unshift(retval);
return retval;
}
return function stringifySafe(arg: any): string {
if (arg === undefined) {
return 'undefined';
} else if (arg === null) {
return 'null';
} else if (typeof arg === 'function') {
try {
return arg.toString();
} catch {
return '[function unknown]';
}
} else if (arg instanceof Error) {
return arg.message;
// return arg.name + ': ' + arg.message;
} else {
// Perform a try catch, just in case the object has a circular
// reference or stringify throws for some other reason.
try {
const ret = JSON.stringify(arg, replacer);
if (ret === undefined) {
return '["' + typeof arg + '" failed to stringify]';
}
return ret;
} catch {
if (typeof arg.toString === 'function') {
try {
// $FlowFixMe[incompatible-use] : toString shouldn't take any arguments in general.
return arg.toString();
} catch {}
}
}
}
return '["' + typeof arg + '" failed to stringify]';
};
}
const stringifySafe = createStringifySafeWithLimits({
maxDepth: 10,
maxStringLimit: 100,
maxArrayLimit: 50,
maxObjectKeysLimit: 50,
});

View File

@@ -0,0 +1,117 @@
// Keep module interface compatible with
// https://github.com/facebook/react-native/blob/50b1bec2d56cd1b06ceb0be284a30fd90e39c342/packages/react-native/Libraries/LogBox/Data/parseLogBoxLog.js
// Import all types from react-native's LogBoxLog to ensure type compatibility
// This file replaced the default react-native LogBoxLog parser
import type { LogBoxLogData, LogLevel } from 'react-native/Libraries/LogBox/Data/LogBoxLog';
import type {
Category,
CodeFrame,
ComponentStack,
ComponentStackType,
Message,
} from 'react-native/Libraries/LogBox/Data/parseLogBoxLog';
import type { ExtendedExceptionData } from 'react-native/Libraries/LogBox/LogBox';
// We intentionally import from our web-specific parseLogBoxLog implementation to ensure the conversion logic is correct.
import type { MetroStackFrame as ExpoMetroStackFrame } from './Types';
import type { ExtendedExceptionData as ExpoExtendedExceptionData } from './parseLogBoxLog';
// End of web-specific imports
import * as parseLogBoxLogWeb from './parseLogBoxLog';
import { parseErrorStack } from '../utils/parseErrorStack';
import { withoutANSIColorStyles as withoutANSIColorStylesHelper } from '../utils/withoutANSIStyles';
// Exported method must be compatible with upstream React Native.
export { parseInterpolation } from './parseLogBoxLog';
export function withoutANSIColorStyles<T>(text: T): T {
return withoutANSIColorStylesHelper(text);
}
export function parseLogBoxException(error: ExtendedExceptionData): LogBoxLogData {
const parsed = parseLogBoxLogWeb.parseLogBoxException(error as ExpoExtendedExceptionData);
return {
...parsed,
// @ts-ignore metro types only accepts undefined | number for column
stack: parsed.stack,
componentStack: parsed.componentStack.map(convertMetroToComponentFrame),
level: ['resolution', 'static'].includes(parsed.level) ? 'syntax' : (parsed.level as LogLevel),
componentStackType: 'stack',
extraData: {},
onNotificationPress: () => {},
// @ts-ignore metro types only accepts undefined | number for location
codeFrame: parsed.codeFrame['stack'],
componentCodeFrame: parsed.codeFrame['component'],
};
}
export function parseLogBoxLog(args: any[]): {
componentStack: ComponentStack;
componentStackType: ComponentStackType;
category: Category;
message: Message;
} {
const parsed = parseLogBoxLogWeb.parseLogBoxLog(args);
return {
...parsed,
componentStack: parsed.componentStack.map(convertMetroToComponentFrame),
componentStackType: 'stack',
};
}
function convertMetroToComponentFrame(frame: ExpoMetroStackFrame): CodeFrame {
return {
content: frame.methodName,
collapse: frame.collapse || false,
fileName: frame.file == null ? 'unknown' : frame.file,
location: {
column: frame.column == null ? -1 : frame.column,
row: frame.lineNumber == null ? -1 : frame.lineNumber,
},
};
}
// Below
// Not used in Expo code, but required for matching exports with upstream.
// https://github.com/krystofwoldrich/react-native/blob/7db31e2fca0f828aa6bf489ae6dc4adef9b7b7c3/packages/react-native/Libraries/LogBox/Data/parseLogBoxLog.js#L220
export function parseComponentStack(message: string): {
type: 'stack';
stack: readonly CodeFrame[];
} {
// We removed legacy parsing since we are in control of the React version used.
const stack = parseErrorStack(message);
return {
type: 'stack',
stack: stack.map((frame) => ({
content: frame.methodName,
collapse: frame.collapse || false,
fileName: frame.file == null ? 'unknown' : frame.file,
location: {
column: frame.column == null ? -1 : frame.column,
row: frame.lineNumber == null ? -1 : frame.lineNumber,
},
})),
};
}
export function hasComponentStack(args: any[]): boolean {
for (const arg of args) {
if (typeof arg === 'string' && isComponentStack(arg)) {
return true;
}
}
return false;
}
const RE_COMPONENT_STACK_LINE_OLD = / {4}in/;
const RE_COMPONENT_STACK_LINE_NEW = / {4}at/;
const RE_COMPONENT_STACK_LINE_STACK_FRAME = /@.*\n/;
function isComponentStack(consoleArgument: string) {
const isOldComponentStackFormat = RE_COMPONENT_STACK_LINE_OLD.test(consoleArgument);
const isNewComponentStackFormat = RE_COMPONENT_STACK_LINE_NEW.test(consoleArgument);
const isNewJSCComponentStackFormat = RE_COMPONENT_STACK_LINE_STACK_FRAME.test(consoleArgument);
return isOldComponentStackFormat || isNewComponentStackFormat || isNewJSCComponentStackFormat;
}

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { withActions } from './ContextActions';
import * as LogBoxData from './Data/LogBoxData';
import { renderInShadowRoot } from './utils/renderInShadowRoot';
let currentRoot: ReturnType<typeof renderInShadowRoot> | null = null;
export function presentGlobalErrorOverlay() {
if (currentRoot) {
return;
}
const { LogBoxInspectorContainer } =
require('./overlay/Overlay') as typeof import('./overlay/Overlay');
const ErrorOverlay = LogBoxData.withSubscription(
withActions(LogBoxInspectorContainer, {
onMinimize: () => {
LogBoxData.setSelectedLog(-1);
LogBoxData.setSelectedLog(-1);
},
onReload: () => {
window.location.reload();
},
onCopyText: (text: string) => {
navigator.clipboard.writeText(text);
},
})
);
currentRoot = renderInShadowRoot('error-overlay', React.createElement(ErrorOverlay));
}
export function dismissGlobalErrorOverlay() {
currentRoot?.unmount();
currentRoot = null;
}

View File

@@ -0,0 +1,4 @@
//@ts-ignore
import ExceptionsManager from 'react-native/Libraries/Core/ExceptionsManager';
export default ExceptionsManager;

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { parseErrorStack } from '../utils/parseErrorStack';
type ExtendedError = any;
class SyntheticError extends Error {
name: string = '';
}
/**
* Handles the developer-visible aspect of errors and exceptions
*/
let exceptionID = 0;
function parseException(e: ExtendedError, isFatal: boolean) {
const stack = parseErrorStack(e?.stack);
const currentExceptionID = ++exceptionID;
const originalMessage = e.message || '';
let message = originalMessage;
if (e.componentStack != null) {
message += `\n\nThis error is located at:${e.componentStack}`;
}
const namePrefix = e.name == null || e.name === '' ? '' : `${e.name}: `;
if (!message.startsWith(namePrefix)) {
message = namePrefix + message;
}
message = e.jsEngine == null ? message : `${message}, js engine: ${e.jsEngine}`;
const data = {
message,
originalMessage: message === originalMessage ? null : originalMessage,
name: e.name == null || e.name === '' ? null : e.name,
componentStack: typeof e.componentStack === 'string' ? e.componentStack : null,
stack,
id: currentExceptionID,
isFatal,
extraData: {
jsEngine: e.jsEngine,
rawStack: e.stack,
},
};
return {
...data,
isComponentError: !!e.isComponentError,
};
}
/**
* Logs exceptions to the (native) console and displays them
*/
function handleException(e: any) {
let error: Error;
if (e instanceof Error) {
error = e;
} else {
// Workaround for reporting errors caused by `throw 'some string'`
// Unfortunately there is no way to figure out the stacktrace in this
// case, so if you ended up here trying to trace an error, look for
// `throw '<error message>'` somewhere in your codebase.
error = new SyntheticError(e);
}
require('../../LogBox').default.addException(parseException(error, true));
}
const ErrorUtils = {
parseException,
handleException,
SyntheticError,
};
export default ErrorUtils;

114
node_modules/@expo/log-box/src/LogBox.ts generated vendored Normal file
View File

@@ -0,0 +1,114 @@
import type { IgnorePattern, LogData } from './Data/LogBoxData';
import * as LogBoxData from './Data/LogBoxData';
import { type ExtendedExceptionData } from './Data/parseLogBoxLog';
import { parseLogBoxLog } from './Data/parseLogBoxLog';
export { ExtendedExceptionData, IgnorePattern, LogData };
/**
* LogBox displays logs in the app.
*/
let originalConsoleError: typeof console.error | undefined;
let consoleErrorImpl: typeof console.error | undefined;
let isLogBoxInstalled: boolean = false;
const LogBox = {
install(): void {
if (isLogBoxInstalled) {
return;
}
isLogBoxInstalled = true;
// IMPORTANT: we only overwrite `console.error` and `console.warn` once.
// When we uninstall we keep the same reference and only change its
// internal implementation
const isFirstInstall = originalConsoleError == null;
if (isFirstInstall) {
originalConsoleError = console.error.bind(console);
console.error = (...args) => {
consoleErrorImpl?.(...args);
};
}
consoleErrorImpl = consoleErrorMiddleware;
if (process.env.NODE_ENV === 'test') {
LogBoxData.setDisabled(true);
}
},
uninstall(): void {
if (!isLogBoxInstalled) {
return;
}
isLogBoxInstalled = false;
// IMPORTANT: we don't re-assign to `console` in case the method has been
// decorated again after installing LogBox. E.g.:
// Before uninstalling: original > LogBox > OtherErrorHandler
// After uninstalling: original > LogBox (noop) > OtherErrorHandler
consoleErrorImpl = originalConsoleError;
delete (console as any).disableLogBox;
},
ignoreLogs(patterns: IgnorePattern[]): void {
LogBoxData.addIgnorePatterns(patterns);
},
ignoreAllLogs(value?: boolean): void {
LogBoxData.setDisabled(value == null ? true : value);
},
clearAllLogs(): void {
LogBoxData.clear();
},
addLog(log: LogData): void {
if (isLogBoxInstalled) {
LogBoxData.addLog(log);
}
},
addException(error: ExtendedExceptionData): void {
if (isLogBoxInstalled) {
LogBoxData.addException(error);
}
},
};
function consoleErrorMiddleware(...args: Parameters<typeof console.error>): void {
// Let errors within LogBox itself fall through.
// TODO: Drop this in favor of a more generalized tagging solution.
if (LogBoxData.isLogBoxErrorMessage(args[0])) {
originalConsoleError?.(...args);
return;
}
const { category, message, componentStack } = parseLogBoxLog(args);
if (LogBoxData.isMessageIgnored(message.content)) {
return;
}
// NOTE: Should this be used for native apps as well, we need to interpolate the message.
// See the original LogBox implementation in React Native for reference.
// NOTE: Unlike React Native, we'll just pass the logs directly to the console
originalConsoleError?.(...args);
LogBoxData.addLog({
// Always show the static rendering issues as full screen since they
// are too confusing otherwise.
// TODO: We can change this with a collection of improvements from React 19.1.
level: /did not match\. Server:/.test(message.content) ? 'fatal' : 'error',
category,
message,
componentStack,
});
}
export default LogBox;

4
node_modules/@expo/log-box/src/environmentHelper.ts generated vendored Normal file
View File

@@ -0,0 +1,4 @@
export function useEnvironmentVariablesPolyfill({ devServerUrl }: { devServerUrl?: string }) {
globalThis.process = globalThis.process || {};
globalThis.process.env.EXPO_DEV_SERVER_ORIGIN ??= devServerUrl;
}

15
node_modules/@expo/log-box/src/fetchHelper.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
export type FetchTextAsync = (input: string, init?: RequestInit) => Promise<string>;
function defaultFetchTextAsync(input: string, init?: RequestInit): Promise<string> {
return fetch(input, init).then((res) => res.text());
}
let fetchTextAsyncFn: FetchTextAsync = defaultFetchTextAsync;
export function setFetchText(fn: FetchTextAsync) {
fetchTextAsyncFn = fn;
}
export function fetchTextAsync(input: string, init?: RequestInit): Promise<string> {
return fetchTextAsyncFn(input, init);
}

3
node_modules/@expo/log-box/src/index.native.tsx generated vendored Normal file
View File

@@ -0,0 +1,3 @@
export function setupLogBox(Comp: any) {
return Comp;
}

25
node_modules/@expo/log-box/src/index.tsx generated vendored Normal file
View File

@@ -0,0 +1,25 @@
import React from 'react';
import { renderInShadowRoot } from './utils/renderInShadowRoot';
if (process.env.NODE_ENV === 'development' && process.env.EXPO_OS === 'web') {
// Stack traces are big with React Navigation
// TODO: Can this be part of the `setupLogBox` hook? Or do we need install early?
require('./LogBox').default.install();
}
let isInstalled = false;
export function setupLogBox(): void {
if (process.env.NODE_ENV === 'development' && process.env.EXPO_OS === 'web') {
if (isInstalled) {
return undefined;
}
const ErrorToast = require('./toast/ErrorToast')
.default as typeof import('./toast/ErrorToast').default;
renderInShadowRoot('error-toast', React.createElement(ErrorToast));
isInstalled = true;
}
}

116
node_modules/@expo/log-box/src/logbox-dom-polyfill.tsx generated vendored Normal file
View File

@@ -0,0 +1,116 @@
'use dom';
import React from 'react';
import { ActionsContext } from './ContextActions';
import * as LogBoxData from './Data/LogBoxData';
import { LogBoxLog, LogContext } from './Data/LogBoxLog';
import { useEnvironmentVariablesPolyfill } from './environmentHelper';
import { FetchTextAsync, setFetchText } from './fetchHelper';
import { LogBoxInspectorContainer } from './overlay/Overlay';
import { convertNativeToExpoLogBoxLog, convertToExpoLogBoxLog } from './utils/convertLogBoxLog';
export default function LogBoxPolyfillDOM({
// Default is mainly used in RedBox replacement,
// where we won't to keep the native webview wrapper interface as minimal as possible.
onCopyText = (text: string) => navigator.clipboard.writeText(text),
onMinimize,
fetchTextAsync,
onReload,
...props
}: {
// Environment props
devServerUrl: string | undefined;
// Common actions props
fetchTextAsync: FetchTextAsync | undefined;
// LogBox UI actions props
onMinimize: (() => void) | undefined;
onReload: (() => void) | undefined;
onCopyText: ((text: string) => void) | undefined;
// LogBoxData actions props
onDismiss: ((index: number) => void) | undefined;
onChangeSelectedIndex: ((index: number) => void) | undefined;
// LogBox props
/**
* LobBoxLogs from the JS Runtime
*/
logs?: any[];
/**
* Logs from the native runtime (both native and JS, both iOS and Android, e.g. redbox errors)
*/
nativeLogs?: any[];
selectedIndex?: number;
// DOM props
dom?: import('expo/dom/internal').DOMPropsInternal;
}) {
useEnvironmentVariablesPolyfill(props);
const logs = React.useMemo(() => {
return [
// Convert from React Native style to Expo style LogBoxLog
...(props.logs ?? []).map(convertToExpoLogBoxLog),
// Convert native logs to Expo Log Box format
...(props.nativeLogs ?? []).map(convertNativeToExpoLogBoxLog),
];
}, [props.logs, props.nativeLogs]);
const selectedIndex = props.selectedIndex ?? (logs && logs?.length - 1) ?? -1;
if (fetchTextAsync) setFetchText(fetchTextAsync);
useViewportMeta('width=device-width, initial-scale=1, viewport-fit=cover');
useNativeLogBoxDataPolyfill({ logs }, props);
return (
<LogContext
value={{
selectedLogIndex: selectedIndex,
isDisabled: false,
logs,
}}>
<ActionsContext onMinimize={onMinimize} onReload={onReload} onCopyText={onCopyText}>
<LogBoxInspectorContainer />
</ActionsContext>
</LogContext>
);
}
function useNativeLogBoxDataPolyfill(
{
logs,
}: {
logs: LogBoxLog[];
},
polyfill: {
onChangeSelectedIndex?: (index: number) => void;
onDismiss?: (index: number) => void;
}
) {
// @ts-ignore
// eslint-disable-next-line import/namespace
LogBoxData.setSelectedLog = polyfill.onChangeSelectedIndex;
// @ts-ignore
// eslint-disable-next-line import/namespace
LogBoxData.dismiss = (log: LogBoxLog) => {
const index = logs.indexOf(log);
polyfill.onDismiss?.(index);
};
}
function useViewportMeta(content: string) {
React.useEffect(() => {
let meta = document.querySelector('meta[name="viewport"]');
if (!meta) {
meta = document.createElement('meta');
// @ts-ignore
meta.name = 'viewport';
document.head.appendChild(meta);
}
meta.setAttribute('content', content);
}, [content]);
}

207
node_modules/@expo/log-box/src/logbox-rn-polyfill.tsx generated vendored Normal file
View File

@@ -0,0 +1,207 @@
import { isRunningInExpoGo } from 'expo';
import React, { useMemo } from 'react';
import { View, DevSettings, Platform, Clipboard, type Modal as ModalInterface } from 'react-native';
// @ts-ignore
import * as LogBoxData from 'react-native/Libraries/LogBox/Data/LogBoxData';
// @ts-ignore
import type LogBoxLog from 'react-native/Libraries/LogBox/Data/LogBoxLog';
// @ts-ignore
import RCTModalHostView from 'react-native/Libraries/Modal/RCTModalHostViewNativeComponent';
import LogBoxPolyfillDOM from './logbox-dom-polyfill';
import { getBaseUrl } from './utils/devServerEndpoints';
const Modal = RCTModalHostView as typeof ModalInterface;
const Colors = {
background: '#111113',
};
function LogBoxRNPolyfill(props: {
onDismiss: (index: number) => void;
onMinimize: () => void;
onChangeSelectedIndex: (index: number) => void;
logs: any[];
selectedIndex: number;
}) {
const logs = React.useMemo(() => {
return props.logs.map((log) => {
return {
symbolicated: log.symbolicated,
symbolicatedComponentStack: log.symbolicatedComponentStack,
componentCodeFrame: log.componentCodeFrame,
level: log.level,
type: log.type,
message: log.message,
stack: log.stack,
category: log.category,
componentStack: log.componentStack,
componentStackType: log.componentStackType,
codeFrame: log.codeFrame,
isComponentError: log.isComponentError,
extraData: log.extraData,
count: log.count,
};
});
}, [props.logs]);
const [open, setOpen] = React.useState(true);
const bundledLogBoxUrl = getBundledLogBoxURL();
const closeModal = (cb: () => void) => {
setOpen(false);
setTimeout(
cb,
Platform.select({
ios: 500, // To allow the native modal to slide away before unmounting
default: 0, // Android has no animation, Web has css animation which doesn't require the delay
})
);
};
const onMinimize = () => closeModal(props.onMinimize);
const onDismiss = props.onDismiss;
const LogBoxWrapper = useMemo(
() =>
Platform.OS === 'ios'
? ({ children, open }: { children?: React.ReactNode; open: boolean }) => {
return (
<Modal
animationType="slide"
presentationStyle="pageSheet"
visible={open}
onRequestClose={onMinimize}>
{children}
</Modal>
);
}
: ({ children }: { children?: React.ReactNode; open: boolean }) => <>{children}</>,
[]
);
return (
<LogBoxWrapper open={open}>
<View
style={{
backgroundColor: Platform.select({ default: undefined, ios: Colors.background }),
pointerEvents: 'box-none',
top: 0,
flex: 1,
}}
collapsable={false}>
<LogBoxPolyfillDOM
selectedIndex={props.selectedIndex}
logs={logs}
// LogBoxData actions props
onDismiss={onDismiss}
onChangeSelectedIndex={props.onChangeSelectedIndex}
// Environment polyfill props
devServerUrl={getBaseUrl()}
// Common actions props
fetchTextAsync={async (input, init) => {
const res = await fetch(input, init);
return res.text();
}}
// LogBox UI actions props
onMinimize={onMinimize}
onReload={() => {
// NOTE: For iOS only the reload is enough, but on Android the app gets stuck on an empty black screen
onMinimize();
setTimeout(() => {
DevSettings.reload();
}, 100);
}}
onCopyText={(text: string) => {
Clipboard.setString(text);
}}
// DOM props
dom={{
useExpoDOMWebView: true,
overrideUri: bundledLogBoxUrl,
contentInsetAdjustmentBehavior: 'never',
containerStyle: {
pointerEvents: 'box-none',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
style: {
flex: 1,
},
suppressMenuItems: ['underline', 'lookup', 'translate'],
bounces: true,
overScrollMode: 'never',
}}
/>
</View>
</LogBoxWrapper>
);
}
function LogBoxInspectorContainer({
selectedLogIndex,
logs,
}: {
logs: readonly LogBoxLog[];
selectedLogIndex: number;
isDisabled?: boolean;
}) {
const handleDismiss = (index: number) => {
LogBoxData.dismiss(logs[index]);
};
const handleMinimize = () => {
LogBoxData.setSelectedLog(-1);
};
const handleSetSelectedLog = (index: number) => {
LogBoxData.setSelectedLog(index);
};
if (selectedLogIndex < 0) {
return null;
}
return (
<LogBoxRNPolyfill
onDismiss={handleDismiss}
onMinimize={handleMinimize}
onChangeSelectedIndex={handleSetSelectedLog}
logs={logs as LogBoxLog[]}
selectedIndex={selectedLogIndex}
/>
);
}
let cachedBundledLogBoxUrl: string | null | undefined = undefined;
/**
* Get the base URL for the Expo LogBox Prebuilt DOM Component HTML
*/
function getBundledLogBoxURL(): string | null {
if (cachedBundledLogBoxUrl !== undefined) {
return cachedBundledLogBoxUrl;
}
if (isRunningInExpoGo()) {
// TODO: This will require a new version of Expo Go with the prebuilt Expo LogBox DOM Components
cachedBundledLogBoxUrl = null;
return null;
}
// Serving prebuilt from application bundle
if (process.env.EXPO_OS === 'android') {
cachedBundledLogBoxUrl = 'file:///android_asset/ExpoLogBox.bundle/index.html';
} else if (process.env.EXPO_OS === 'ios') {
cachedBundledLogBoxUrl = 'ExpoLogBox.bundle/index.html';
} else {
// Other platforms do not support the bundled LogBox DOM Component
cachedBundledLogBoxUrl = null;
}
return cachedBundledLogBoxUrl;
}
export default LogBoxData.withSubscription(LogBoxInspectorContainer);

45
node_modules/@expo/log-box/src/logbox-web-polyfill.tsx generated vendored Normal file
View File

@@ -0,0 +1,45 @@
import React from 'react';
import { LogBoxLog, useLogs } from './Data/LogBoxLog';
import LogBoxInspectorContainer from './logbox-dom-polyfill';
export default () => {
const { logs, selectedLogIndex } = useLogsFromExpoStaticError();
return (
<LogBoxInspectorContainer
logs={logs}
selectedIndex={selectedLogIndex}
// LogBoxData actions props
onDismiss={undefined}
onChangeSelectedIndex={undefined}
// Environment polyfill props
platform="web"
devServerUrl={undefined} // not needed for static error
// Common actions props
fetchTextAsync={undefined} // fallback to global fetch
// LogBox UI actions props
onMinimize={undefined}
onReload={() => window.location.reload()}
onCopyText={(text: string) => navigator.clipboard.writeText(text)}
/>
);
};
function useLogsFromExpoStaticError(): ReturnType<typeof useLogs> {
if (process.env.EXPO_OS === 'web' && typeof window !== 'undefined') {
// Logbox data that is pre-fetched on the dev server and rendered here.
const expoCliStaticErrorElement = document.getElementById('_expo-static-error');
if (expoCliStaticErrorElement?.textContent) {
const raw = JSON.parse(expoCliStaticErrorElement.textContent);
return {
...raw,
logs: raw.logs.map((raw: any) => new LogBoxLog(raw)),
};
}
}
throw new Error(
'`useLogsFromExpoStaticError` must be used within a document with `_expo-static-error` element.'
);
}

View File

@@ -0,0 +1,8 @@
.line {
display: flex;
flex-direction: row;
}
.text {
white-space: pre;
}

View File

@@ -0,0 +1,133 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import Anser from 'anser';
import React from 'react';
import styles from './AnsiHighlight.module.css';
// Afterglow theme from https://iterm2colorschemes.com/
const COLORS: Record<string, string> = {
'ansi-black': 'rgb(27, 27, 27)',
'ansi-red': 'var(--expo-log-syntax-red)',
'ansi-green': '#ffca16',
'ansi-yellow': 'var(--expo-log-syntax-orange)',
'ansi-cyan': '#de51a8',
'ansi-magenta': '#6abaff',
'ansi-blue': 'rgb(125, 169, 199)',
// Instead of white, use the default color provided to the component
// 'ansi-white': 'rgb(216, 216, 216)',
'ansi-bright-black': 'rgb(98, 98, 98)',
'ansi-bright-red': 'rgb(187, 86, 83)',
'ansi-bright-green': 'rgb(144, 157, 98)',
'ansi-bright-yellow': 'rgb(234, 193, 121)',
'ansi-bright-blue': 'rgb(125, 169, 199)',
'ansi-bright-magenta': 'rgb(176, 101, 151)',
'ansi-bright-cyan': 'rgb(140, 220, 216)',
'ansi-bright-white': 'rgb(247, 247, 247)',
};
export class Ansi extends React.Component<
{
// TODO: Does undefined make sense here?
text: string | undefined;
style: React.CSSProperties;
},
{ hasError: boolean }
> {
constructor(props: { text: string; style: React.CSSProperties }) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(_error: Error) {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('AnsiSafe caught an error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<span className={styles.text} style={this.props.style}>
Error rendering ANSI text.
</span>
);
}
return <AnsiUnsafe text={this.props.text || ''} style={this.props.style} />;
}
}
export function AnsiUnsafe({ text, style }: { text: string; style: React.CSSProperties }) {
// TMP
if (!text) {
return (
<span className={styles.text} style={style}>
Text not provided to Ansi component.
</span>
);
}
let commonWhitespaceLength = Infinity;
const parsedLines = text.split(/\n/).map((line) =>
Anser.ansiToJson(line, {
json: true,
remove_empty: true,
use_classes: true,
})
);
parsedLines.map((lines) => {
// The third item on each line includes the whitespace of the source code.
// We are looking for the least amount of common whitespace to trim all lines.
// Example: Array [" ", " 96 |", " text", ...]
const match = lines[2] && lines[2]?.content?.match(/^ +/);
const whitespaceLength = (match && match[0]?.length) || 0;
if (whitespaceLength < commonWhitespaceLength) {
commonWhitespaceLength = whitespaceLength;
}
});
const getText = (content: string, key: number) => {
if (key === 1) {
// Remove the vertical bar after line numbers
return content.replace(/\| $/, ' ');
} else if (key === 2 && commonWhitespaceLength < Infinity) {
// Remove common whitespace at the beginning of the line
return content.substr(commonWhitespaceLength);
} else {
return content;
}
};
return (
<>
{parsedLines.map((items, i) => (
<div className={styles.line} key={i}>
{items.map((bundle, key) => {
const textStyle: React.CSSProperties =
bundle.fg && COLORS[bundle.fg]
? {
backgroundColor: bundle.bg && COLORS[bundle.bg],
color: bundle.fg && COLORS[bundle.fg],
}
: {
backgroundColor: bundle.bg && COLORS[bundle.bg],
};
return (
<span className={styles.text} style={{ ...style, ...textStyle }} key={key}>
{getText(bundle.content, key)}
</span>
);
})}
</div>
))}
</>
);
}

View File

@@ -0,0 +1,145 @@
.fileIcon {
display: none;
}
.copyButton {
cursor: pointer;
display: inline-flex;
font-weight: 500;
align-items: center;
white-space: nowrap;
transition: all 0.2s ease-in-out;
font-size: 10px;
background-color: transparent;
box-shadow: none;
gap: 6px;
height: 40px;
border-radius: 0;
border-width: 0;
padding-left: 16px;
padding-right: 16px;
justify-content: flex-end;
border-left: 1px solid var(--expo-log-color-border);
background-color: var(--expo-log-color-background);
}
.copyButton:hover {
background-color: var(--expo-log-secondary-system-background-hover);
}
.copyButtonIcon {
transform: translateZ(0);
width: 1.1rem;
height: 1.1rem;
margin-bottom: -1px;
color: var(--expo-log-secondary-label);
transition: all 0.2s ease-in-out;
}
/* Child svg of copyButton should transition to diagonally on hover of the button */
.copyButton:hover .copyButtonIcon {
/* origin in bottom left */
transform-origin: bottom left;
transform: scale(1.1);
}
.copyButtonText {
display: none;
align-items: center;
color: inherit;
line-height: 1;
font-size: 14px;
font-weight: 400;
letter-spacing: -0.003rem;
font-family: var(--expo-log-font-family);
color: var(--expo-log-color-label);
}
.headerScrollText {
color: inherit;
font-size: 15px;
line-height: 1.25;
letter-spacing: -0.009rem;
display: flex;
overflow-x: auto;
align-items: center;
gap: 8px;
position: relative;
font-weight: 600;
&::-webkit-scrollbar {
display: none;
}
}
.header {
display: flex;
min-height: 40px;
justify-content: space-between;
overflow: hidden;
background-color: var(--expo-log-color-background);
border-bottom: 1px solid var(--expo-log-color-border);
border-top-left-radius: 6px;
border-top-right-radius: 6px;
position: relative;
}
.headerIconWrapper {
display: none;
}
.headerText {
padding-left: 12px;
overflow-wrap: break-word;
font-family: var(--expo-log-font-mono);
white-space: nowrap;
color: var(--expo-log-color-label);
padding-right: 16px;
}
.blurGradientRL {
background: linear-gradient(to left, var(--expo-log-color-background) 0%, rgba(0, 0, 0, 0) 100%);
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 20;
pointer-events: none;
}
.blurGradientLR {
background: linear-gradient(to right, var(--expo-log-color-background) 0%, rgba(0, 0, 0, 0) 100%);
border-top-left-radius: 6px;
opacity: 0;
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 20;
pointer-events: none;
}
@media screen and (min-width: 768px) {
.fileIcon {
display: block;
}
.headerText {
padding-left: 0px;
}
.headerIconWrapper {
display: block;
min-width: 1rem;
height: 1rem;
padding-left: 16px;
}
.header {
padding-left: 0px;
}
.copyButtonText {
display: flex;
}
}

272
node_modules/@expo/log-box/src/overlay/CodeFrame.tsx generated vendored Normal file
View File

@@ -0,0 +1,272 @@
/**
* Copyright (c) 650 Industries.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { useEffect } from 'react';
import { Ansi } from './AnsiHighlight';
import styles from './CodeFrame.module.css';
import type { CodeFrame as CodeFrameData } from '../Data/Types';
import { formatProjectFilePath, openFileInEditor } from '../utils/devServerEndpoints';
export function ErrorCodeFrame({
showPathsRelativeTo,
codeFrame,
}: {
showPathsRelativeTo?: string;
codeFrame?: CodeFrameData;
}) {
if (codeFrame == null) {
return null;
}
function getFileName() {
return formatProjectFilePath(showPathsRelativeTo, codeFrame?.fileName);
}
function getLocation() {
const location = codeFrame?.location;
if (location != null) {
return ` (${location.row}:${location.column + 1 /* Code frame columns are zero indexed */})`;
}
return null;
}
return (
<CodeFrame
title={
<>
{getFileName()}
<span style={{ opacity: 0.8 }}>{getLocation()}</span>
</>
}
headerIcon={<FileIcon />}
headerAction={
<button
className={styles.copyButton}
type="button"
title="Open in editor"
onClick={() => {
openFileInEditor(codeFrame.fileName, codeFrame.location?.row ?? 0);
}}
aria-label="Copy content">
<p className={styles.copyButtonText} data-text="true">
Open
</p>
<OpenIcon className={styles.copyButtonIcon} width={26} height={26} />
</button>
}
content={codeFrame.content}
/>
);
}
export function Terminal({ content, moduleName }: { content?: string; moduleName: string }) {
return (
<CodeFrame
title="Terminal"
// TODO: change to copy button
// headerAction={
// <button
// className={styles.copyButton}
// type="button"
// title="Run command in project"
// onClick={() => {
// }}
// aria-label="Copy content">
// <p className={styles.copyButtonText} data-text="true">
// Run
// </p>
// <PlayIcon className={styles.copyButtonIcon} width={26} height={26} />
// </button>
// }
headerIcon={<TerminalIcon />}
content={content}
/>
);
}
function CodeFrame({
content,
headerIcon,
headerAction,
title,
}: {
content?: string;
headerIcon?: React.ReactNode;
headerAction?: React.ReactNode;
title: React.ReactNode;
}) {
const leftBlurRef = React.useRef<HTMLDivElement>(null);
const scrollTextRef = React.useRef<HTMLDivElement>(null);
// Transition the opacity of the header blur when the scroll position changes.
useEffect(() => {
const scrollElement = scrollTextRef.current;
const leftBlurElement = leftBlurRef.current;
if (scrollElement == null || leftBlurElement == null) {
return;
}
const handleScroll = () => {
leftBlurElement.style.opacity = String(scrollElement.scrollLeft / 20);
};
scrollElement.addEventListener('scroll', handleScroll);
return () => {
scrollElement.removeEventListener('scroll', handleScroll);
};
}, [scrollTextRef, leftBlurRef]);
// Scroll to end of the text when it changes
useEffect(() => {
const scrollElement = scrollTextRef.current;
if (scrollElement == null) {
return;
}
scrollElement.scrollTo({
left: scrollElement.scrollWidth,
behavior: 'smooth',
});
}, [scrollTextRef, content]);
// Try to match the Expo docs
return (
<div
style={{
backgroundColor: 'var(--expo-log-secondary-system-grouped-background)',
border: '1px solid var(--expo-log-color-border)',
marginTop: 5,
borderRadius: 6,
}}>
<header className={styles.header}>
<span
style={{
display: 'flex',
width: '100%',
position: 'relative',
overflowX: 'hidden',
}}>
<span ref={scrollTextRef} className={styles.headerScrollText}>
<span className={styles.headerIconWrapper} style={{}}>
{headerIcon}
</span>
<span className={styles.headerText}>{title}</span>
</span>
<span ref={leftBlurRef} className={styles.blurGradientLR} />
{/* R-L gradient to fade contents */}
<span className={styles.blurGradientRL} />
</span>
{headerAction}
</header>
<div
style={{
padding: 10,
display: 'flex',
flexDirection: 'column',
}}>
<div
style={{
overflowX: 'auto',
display: 'flex',
flexDirection: 'column',
}}>
{content && (
<Ansi
style={{
flexDirection: 'column',
color: 'var(--expo-log-color-label)',
fontSize: 12,
lineHeight: '20px',
fontFamily: 'var(--expo-log-font-mono)',
}}
text={content}
/>
)}
</div>
</div>
</div>
);
}
function PlayIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
fill="none"
viewBox="0 0 24 24"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
stroke="currentColor"
{...props}
role="img">
<polygon points="6 3 20 12 6 21 6 3" />
</svg>
);
}
function OpenIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" {...props} role="img">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M7 17L17 7M17 7H7M17 7V17"
/>
</svg>
);
}
export function FileIcon() {
return (
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
style={{
width: '1rem',
height: '1rem',
color: 'var(--expo-log-secondary-label)',
}}
className={styles.fileIcon}
role="img">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M14 2.26953V6.40007C14 6.96012 14 7.24015 14.109 7.45406C14.2049 7.64222 14.3578 7.7952 14.546 7.89108C14.7599 8.00007 15.0399 8.00007 15.6 8.00007H19.7305M14 17.5L16.5 15L14 12.5M10 12.5L7.5 15L10 17.5M20 9.98822V17.2C20 18.8802 20 19.7202 19.673 20.362C19.3854 20.9265 18.9265 21.3854 18.362 21.673C17.7202 22 16.8802 22 15.2 22H8.8C7.11984 22 6.27976 22 5.63803 21.673C5.07354 21.3854 4.6146 20.9265 4.32698 20.362C4 19.7202 4 18.8802 4 17.2V6.8C4 5.11984 4 4.27976 4.32698 3.63803C4.6146 3.07354 5.07354 2.6146 5.63803 2.32698C6.27976 2 7.11984 2 8.8 2H12.0118C12.7455 2 13.1124 2 13.4577 2.08289C13.7638 2.15638 14.0564 2.27759 14.3249 2.44208C14.6276 2.6276 14.887 2.88703 15.4059 3.40589L18.5941 6.59411C19.113 7.11297 19.3724 7.3724 19.5579 7.67515C19.7224 7.94356 19.8436 8.2362 19.9171 8.5423C20 8.88757 20 9.25445 20 9.98822Z"
/>
</svg>
);
}
export function TerminalIcon() {
return (
<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
style={{
width: '1rem',
height: '1rem',
color: 'var(--expo-log-secondary-label)',
}}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={styles.fileIcon}
role="img">
<polyline points="4 17 10 11 4 5" />
<line x1="12" x2="20" y1="19" y2="19" />
</svg>
);
}

1
node_modules/@expo/log-box/src/overlay/Constants.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export const SHOW_MORE_MESSAGE_LENGTH = 500;

View File

@@ -0,0 +1,88 @@
/* ErrorOverlayHeader.module.css */
.container {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 1rem;
height: 48px;
min-height: 48px;
justify-content: space-between;
position: relative;
/* no text select */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.leftGroup {
display: flex;
flex-direction: row;
align-items: stretch;
gap: 16px;
position: relative;
}
.headerControls {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.divider {
width: 0;
border-left: 1px solid var(--expo-log-color-border);
}
.navGroup {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.sdkBadge {
display: flex;
flex-direction: row;
gap: 6px;
border-radius: 999px;
padding: 4px 12px;
border: 1px solid var(--expo-log-color-border);
color: var(--expo-log-secondary-label);
}
.sdkIcon {
width: 14px;
}
.sdkText {
color: var(--expo-log-secondary-label);
font-family: var(--expo-log-font-family);
font-size: 0.75rem;
}
.pageButton {
border: none;
outline: none;
aspect-ratio: 1;
border-radius: 50%;
display: flex;
align-items: center;
height: 24px;
width: 24px;
background-color: var(--expo-log-secondary-system-background);
padding: 2px;
transition: background-color 0.2s;
justify-content: center;
cursor: pointer;
}
.pageButton[aria-disabled='true'] {
cursor: not-allowed;
}
.pageButton:not([aria-disabled]):hover {
background-color: var(--expo-log-secondary-system-background-hover);
}

178
node_modules/@expo/log-box/src/overlay/Header.tsx generated vendored Normal file
View File

@@ -0,0 +1,178 @@
import React from 'react';
import styles from './Header.module.css';
import type { LogLevel } from '../Data/Types';
export function ErrorOverlayHeader({
selectedIndex,
total,
sdkVersion,
...props
}: {
onSelectIndex: (selectedIndex: number) => void;
level: LogLevel;
onDismiss: () => void;
onMinimize: () => void;
onCopy: () => void;
onReload?: () => void;
isDismissable: boolean;
selectedIndex: number;
sdkVersion?: string;
total: number;
}) {
const titleText = `${selectedIndex + 1}/${total}`;
const isUNVERSIONED = sdkVersion?.toLowerCase() === 'unversioned' || !sdkVersion;
return (
<div className={styles.container}>
<div className={styles.leftGroup}>
<div className={styles.headerControls}>
<HeaderButton
title="Dismiss error"
onPress={props.isDismissable ? props.onDismiss : undefined}>
{/* Dismiss Icon */}
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="var(--expo-log-secondary-label)"
xmlns="http://www.w3.org/2000/svg">
<path d="M6.18945 17.8222C6.10346 17.747 6.04703 17.6556 6.02015 17.5481C5.99328 17.4405 5.99328 17.333 6.02015 17.2255C6.0524 17.118 6.10883 17.0266 6.18945 16.9513L11.1312 11.9998L6.18945 7.04836C6.10883 6.97309 6.05509 6.8817 6.02822 6.77417C6.00134 6.66665 6.00134 6.56181 6.02822 6.45966C6.05509 6.35214 6.10883 6.25806 6.18945 6.17741C6.26469 6.09677 6.35605 6.0457 6.46354 6.02419C6.57103 5.99731 6.67583 5.99731 6.77794 6.02419C6.88006 6.05107 6.97411 6.10215 7.0601 6.17741L12.0018 11.1289L16.9517 6.17741C17.0699 6.05914 17.215 6 17.387 6C17.559 6 17.7041 6.05914 17.8223 6.17741C17.9459 6.29569 18.005 6.44354 17.9997 6.62095C17.9997 6.79299 17.9405 6.93546 17.8223 7.04836L12.8725 11.9998L17.8223 16.9513C17.9405 17.0642 17.9997 17.2067 17.9997 17.3787C17.9997 17.5508 17.9405 17.6986 17.8223 17.8222C17.7041 17.9459 17.559 18.005 17.387 17.9997C17.215 17.9997 17.0699 17.9405 16.9517 17.8222L12.0018 12.8708L7.0601 17.8222C6.97411 17.9029 6.88006 17.954 6.77794 17.9755C6.67583 18.0024 6.57103 18.0024 6.46354 17.9755C6.35605 17.954 6.26469 17.9029 6.18945 17.8222Z" />
</svg>
</HeaderButton>
<HeaderButton
title="Minimize errors"
onPress={props.isDismissable ? props.onMinimize : undefined}>
{/* Minimize Icon */}
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="var(--expo-log-secondary-label)"
xmlns="http://www.w3.org/2000/svg">
<path d="M5 11.5C5 11.2239 5.22386 11 5.5 11H18.5C18.7761 11 19 11.2239 19 11.5V11.5C19 11.7761 18.7761 12 18.5 12H5.5C5.22386 12 5 11.7761 5 11.5V11.5Z" />
</svg>
</HeaderButton>
</div>
<div className={styles.divider} />
<div className={styles.navGroup}>
<HeaderButton
title="Previous error"
disabled={total <= 1}
onPress={() =>
props.onSelectIndex(selectedIndex - 1 < 0 ? total - 1 : selectedIndex - 1)
}>
<ChevronIcon left />
</HeaderButton>
<span
style={{
fontSize: 16,
fontFamily: 'var(--expo-log-font-mono)',
color: 'var(--expo-log-secondary-label)',
}}>
{titleText}
</span>
<HeaderButton
title="Next error"
disabled={total <= 1}
onPress={() => props.onSelectIndex((selectedIndex + 1) % total)}>
{/* Right Chevron */}
<ChevronIcon />
</HeaderButton>
</div>
</div>
<div className={styles.headerControls}>
{props.onReload && (
<HeaderButton title="Reload application" onPress={() => props.onReload?.()}>
<ReloadIcon />
</HeaderButton>
)}
<HeaderButton title="Copy error" onPress={() => props.onCopy()}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#98989F"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
<path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" />
<path d="M16 4h2a2 2 0 0 1 2 2v4" />
<path d="M21 14H11" />
<path d="m15 10-4 4 4 4" />
</svg>
</HeaderButton>
<div
className={styles.sdkBadge}
style={{
borderColor: isUNVERSIONED ? 'var(--expo-log-secondary-label)' : undefined,
}}>
<svg
className={styles.sdkIcon}
fill="white"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path d="M0 20.084c.043.53.23 1.063.718 1.778.58.849 1.576 1.315 2.303.567.49-.505 5.794-9.776 8.35-13.29a.761.761 0 011.248 0c2.556 3.514 7.86 12.785 8.35 13.29.727.748 1.723.282 2.303-.567.57-.835.728-1.42.728-2.046 0-.426-8.26-15.798-9.092-17.078-.8-1.23-1.044-1.498-2.397-1.542h-1.032c-1.353.044-1.597.311-2.398 1.542C8.267 3.991.33 18.758 0 19.77Z" />
</svg>
<span className={styles.sdkText}>{isUNVERSIONED ? `Debug` : `Expo ${sdkVersion}`}</span>
</div>
</div>
</div>
);
}
function ChevronIcon({ left }: { left?: boolean }) {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="var(--expo-log-secondary-label)"
xmlns="http://www.w3.org/2000/svg">
{left ? (
<path d="M7 11.5C7 11.6053 7.01978 11.7014 7.05934 11.7881C7.0989 11.8748 7.16154 11.9554 7.24725 12.0297L14.6846 18.7955C14.8297 18.9318 15.011 19 15.2286 19C15.3736 19 15.5055 18.969 15.6242 18.9071C15.7363 18.8513 15.8286 18.7677 15.9011 18.6561C15.967 18.5446 16 18.4207 16 18.2844C16 18.0861 15.9275 17.9157 15.7824 17.7732L8.87912 11.5L15.7824 5.23606C15.9275 5.08736 16 4.91698 16 4.72491C16 4.5824 15.967 4.45849 15.9011 4.35316C15.8286 4.24164 15.7363 4.15489 15.6242 4.09294C15.5055 4.03098 15.3736 4 15.2286 4C15.011 4 14.8297 4.07125 14.6846 4.21375L7.24725 10.9796C7.16154 11.0539 7.0989 11.1344 7.05934 11.2212C7.01978 11.3079 7 11.4009 7 11.5Z" />
) : (
<path d="M17 11.5C17 11.6053 16.9802 11.7014 16.9407 11.7881C16.9011 11.8748 16.8385 11.9554 16.7527 12.0297L9.31538 18.7955C9.17033 18.9318 8.98901 19 8.77143 19C8.62637 19 8.49451 18.969 8.37582 18.9071C8.26374 18.8513 8.17143 18.7677 8.0989 18.6561C8.03297 18.5446 8 18.4207 8 18.2844C8 18.0861 8.07253 17.9157 8.21758 17.7732L15.1209 11.5L8.21758 5.23606C8.07253 5.08736 8 4.91698 8 4.72491C8 4.5824 8.03297 4.45849 8.0989 4.35316C8.17143 4.24164 8.26374 4.15489 8.37582 4.09294C8.49451 4.03098 8.62637 4 8.77143 4C8.98901 4 9.17033 4.07125 9.31538 4.21375L16.7527 10.9796C16.8385 11.0539 16.9011 11.1344 16.9407 11.2212C16.9802 11.3079 17 11.4009 17 11.5Z" />
)}
</svg>
);
}
function ReloadIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="var(--expo-log-secondary-label)"
xmlns="http://www.w3.org/2000/svg">
<path d="M7.248 1.307A.75.75 0 118.252.193l2.5 2.25a.75.75 0 010 1.114l-2.5 2.25a.75.75 0 01-1.004-1.114l1.29-1.161a4.5 4.5 0 103.655 2.832.75.75 0 111.398-.546A6 6 0 118.018 2l-.77-.693z" />
</svg>
);
}
function HeaderButton(props: {
disabled?: boolean;
onPress?: () => void;
children: React.ReactNode;
title: string;
}) {
return (
<button
title={props.title}
className={styles.pageButton}
aria-disabled={!props.onPress || props.disabled ? true : undefined}
onClick={props.disabled ? undefined : props.onPress}>
{props.children}
</button>
);
}

View File

@@ -0,0 +1,45 @@
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.spinner {
animation: spin 2s linear infinite;
}
.root {
display: flex;
align-items: center;
border-radius: 12px;
flex-direction: row;
height: 24px;
padding: 0 8px;
cursor: pointer;
background-color: transparent;
border: none;
}
@media (hover: hover) {
.root:hover {
background-color: rgba(51, 51, 51, 1);
}
}
.root:active {
background-color: rgba(51, 51, 51, 0.6);
}
.image {
height: 14px;
width: 16px;
margin-right: 4px;
}
.text {
font-size: 12px;
line-height: 16px;
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use client';
import React from 'react';
import styles from './LogBoxInspectorSourceMapStatus.module.css';
function AlertTriangleIcon({ color, className }: { color: string; className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
);
}
function LoaderIcon({ color, className }: { color: string; className?: string }) {
return (
<svg
className={className}
viewBox="0 0 24 24"
fill="none"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<line x1="12" y1="2" x2="12" y2="6" />
<line x1="12" y1="18" x2="12" y2="22" />
<line x1="4.93" y1="4.93" x2="7.76" y2="7.76" />
<line x1="16.24" y1="16.24" x2="19.07" y2="19.07" />
<line x1="2" y1="12" x2="6" y2="12" />
<line x1="18" y1="12" x2="22" y2="12" />
<line x1="4.93" y1="19.07" x2="7.76" y2="16.24" />
<line x1="16.24" y1="7.76" x2="19.07" y2="4.93" />
</svg>
);
}
export function LogBoxInspectorSourceMapStatus(props: {
onPress?: (() => void) | null;
status: 'COMPLETE' | 'FAILED' | 'NONE' | 'PENDING';
}) {
let icon;
let color;
switch (props.status) {
case 'FAILED':
color = `rgba(243, 83, 105, 1)`;
icon = <AlertTriangleIcon color={color} className={styles.image} />;
break;
case 'PENDING':
color = `rgba(250, 186, 48, 1)`;
icon = <LoaderIcon color={color} className={`${styles.image} ${styles.spinner}`} />;
break;
}
if (props.status === 'COMPLETE' || icon == null) {
return null;
}
const content = (
<>
{icon}
<span className={styles.text} style={{ color }}>
Source Map
</span>
</>
);
if (props.onPress == null) {
return <div className={styles.root}>{content}</div>;
}
return (
<button className={styles.root} onClick={props.onPress}>
{content}
</button>
);
}

58
node_modules/@expo/log-box/src/overlay/Message.tsx generated vendored Normal file
View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from 'react';
import type { Message } from '../Data/Types';
export function LogBoxMessage(props: { message: Message; maxLength?: number }): React.ReactElement {
const { content, substitutions }: Message = props.message;
const maxLength = props.maxLength != null ? props.maxLength : Infinity;
const substitutionStyle: React.CSSProperties = { opacity: 0.6 };
const elements: React.ReactElement[] = [];
let length = 0;
const createUnderLength = (key: string | '-1', message: string, style?: React.CSSProperties) => {
let cleanMessage = message;
if (props.maxLength != null) {
cleanMessage = cleanMessage.slice(0, props.maxLength - length);
}
if (length < maxLength) {
elements.push(
<span key={key} style={style}>
{cleanMessage}
</span>
);
}
length += cleanMessage.length;
};
const lastOffset = substitutions.reduce((prevOffset, substitution, index) => {
const key = String(index);
if (substitution.offset > prevOffset) {
const prevPart = content.substr(prevOffset, substitution.offset - prevOffset);
createUnderLength(key, prevPart);
}
const substititionPart = content.substr(substitution.offset, substitution.length);
createUnderLength(key + '.5', substititionPart, substitutionStyle);
return substitution.offset + substitution.length;
}, 0);
if (lastOffset < content.length) {
const lastPart = content.substr(lastOffset);
createUnderLength('-1', lastPart);
}
return <>{elements}</>;
}

View File

@@ -0,0 +1,207 @@
/* Using :host to ensure styles only apply in Shadow DOM */
/* NOTE: :host is transformed to :root on native via shadowDomCssTransformer.js */
:host {
/* Theme variables */
/* Based on UIKit colors in dark mode: https://github.com/EvanBacon/expo-apple-colors/blob/80f541c38c7fadcbd8a1fe85800eea0b3a97079b/colors.css#L1 */
--expo-log-color-border: #313538;
--expo-log-color-label: #edeef0;
--expo-log-color-background: #111113;
--expo-log-secondary-label: rgba(234.6, 234.6, 244.8, 0.6);
--expo-log-secondary-system-background: rgba(28.05, 28.05, 30.6, 1);
--expo-log-secondary-system-grouped-background: color(display-p3 0.095 0.098 0.105);
--expo-log-secondary-system-background-hover: color(display-p3 0.156 0.163 0.176);
--expo-log-color-danger: color(display-p3 0.861 0.403 0.387);
--expo-log-syntax-red: color(display-p3 0.861 0.403 0.387);
--expo-log-syntax-orange: color(display-p3 1 0.63 0.38);
--expo-log-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',
Arial, sans-serif;
--expo-log-font-mono: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
}
.overlayIos {
top: 0;
}
.overlayAndroid {
top: var(--android-safe-area-inset-top, env(safe-area-inset-top));
}
.overlayWeb {
/* Add top safe area for web */
top: calc(10vh + env(safe-area-inset-top, 2vh));
}
.overlay {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 9900;
display: flex;
align-content: center;
align-items: center;
flex-direction: column;
-webkit-font-smoothing: antialiased;
}
.containerTopRadius {
border-top-left-radius: 20px;
border-top-right-radius: 20px;
}
.container {
--expo-log-max-width: 960px;
margin: 0 auto;
max-width: var(--expo-log-max-width);
width: 100%;
overflow-x: hidden;
background-color: var(--expo-log-color-background);
display: flex;
flex: 1;
flex-direction: column;
position: relative;
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 200ms ease-out forwards;
}
.content {
display: flex;
flex-direction: column;
flex: 1;
width: 100%;
height: 100%;
opacity: 0;
animation: fadeInUp 200ms ease-out forwards;
background-color: var(--expo-log-color-background);
}
.scroll {
/* Padding added to give the progressive blur some room to animate in. */
padding-top: 16px;
gap: 0.5rem;
overflow-y: auto;
background-color: var(--expo-log-color-background);
/* Wrap horizontal contents */
overflow-x: hidden;
display: flex;
flex: 1;
flex-direction: column;
position: relative;
padding-bottom: calc(var(--expo-log-footer-height) + 1rem);
}
.bg {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
pointer-events: all;
z-index: -1;
opacity: 0;
animation: data-expo-log-backdrop-fade-in 150ms ease-out forwards;
}
.bgExit {
opacity: 1;
animation: fadeOut 150ms ease-in forwards;
}
.containerExit {
opacity: 1;
transform: translateY(0);
animation: fadeOutUp 150ms ease-in forwards;
}
@keyframes data-expo-log-backdrop-fade-in {
to {
opacity: 1;
}
}
@keyframes fadeOut {
to {
opacity: 0;
}
}
@keyframes fadeOutUp {
to {
opacity: 0;
transform: translateY(20px);
}
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
.footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
min-height: var(--expo-log-footer-height);
overflow: hidden;
padding-bottom: var(--android-safe-area-inset-bottom, env(safe-area-inset-bottom));
background-color: var(--expo-log-secondary-system-background);
}
.headerBlur {
background: linear-gradient(to top, transparent, var(--expo-log-color-background));
mask-image: linear-gradient(to bottom, var(--expo-log-color-background) 50%, transparent);
backdrop-filter: blur(4px);
height: 48px;
min-height: 48px;
position: absolute;
top: 47px;
left: 0;
right: 0;
z-index: 1;
pointer-events: none;
/* Fade manually */
opacity: 0;
}
/* md-visible */
@media screen and (min-width: 576px) {
.overlay {
top: 10vh;
bottom: 10vh;
}
.container {
border: 1px solid var(--expo-log-color-border);
border-radius: 10px;
}
}
@media (min-width: 576px) {
.container {
--expo-log-max-width: 540px;
}
}
@media (min-width: 768px) {
.container {
--expo-log-max-width: 720px;
}
}
@media (min-width: 992px) {
.container {
--expo-log-max-width: 960px;
}
}

424
node_modules/@expo/log-box/src/overlay/Overlay.tsx generated vendored Normal file
View File

@@ -0,0 +1,424 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { useCallback, useEffect, useState } from 'react';
import { ErrorCodeFrame, Terminal } from './CodeFrame';
import { LogBoxMessage } from './Message';
import styles from './Overlay.module.css';
import { useActions } from '../ContextActions';
import { SHOW_MORE_MESSAGE_LENGTH } from './Constants';
import { ErrorOverlayHeader } from './Header';
import ShowMoreButton from './ShowMoreButton';
import { StackTraceList } from './StackTraceList';
import { DevServerContext, useDevServer } from '../ContextDevServer';
import * as LogBoxData from '../Data/LogBoxData';
import { LogBoxLog, useLogs } from '../Data/LogBoxLog';
import type { Message, LogLevel, StackType } from '../Data/Types';
import { classNames } from '../utils/classNames';
import { getFormattedStackTrace } from '../utils/devServerEndpoints';
const HEADER_TITLE_MAP: Record<LogLevel, string> = {
error: 'Console Error',
fatal: 'Uncaught Error',
resolution: 'Resolution Error',
syntax: 'Build Error',
static: 'Server Error',
};
export function LogBoxInspectorContainer() {
const { selectedLogIndex, logs } = useLogs();
const log = logs[selectedLogIndex];
if (log == null) {
return null;
}
return (
<DevServerContext>
<LogBoxInspector log={log} selectedLogIndex={selectedLogIndex} logs={logs} />
</DevServerContext>
);
}
function LogBoxInspector({
log,
selectedLogIndex,
logs,
}: {
log: LogBoxLog;
selectedLogIndex: number;
logs: LogBoxLog[];
}) {
const { onMinimize: onMinimizeAction } = useActions();
const isDismissable = !['static', 'syntax', 'resolution'].includes(log.level);
const [closing, setClosing] = useState(false);
const animateClose = (callback: () => void) => {
setClosing(true);
setTimeout(() => {
callback();
}, 200);
};
const onMinimize = useCallback(
(cb?: () => void): void => {
if (['ios', 'android'].includes(process.env.EXPO_DOM_HOST_OS ?? '')) {
onMinimizeAction?.();
cb?.();
} else {
animateClose(() => {
onMinimizeAction?.();
cb?.();
});
}
},
[onMinimizeAction]
);
return (
<div
className={[
styles.overlay,
process.env.EXPO_DOM_HOST_OS === 'ios' ? styles.overlayIos : null,
process.env.EXPO_DOM_HOST_OS === 'android' ? styles.overlayAndroid : null,
process.env.EXPO_DOM_HOST_OS === undefined ? styles.overlayWeb : null,
]
.filter(Boolean)
.join(' ')}>
<div
data-expo-log-backdrop="true"
className={
process.env.EXPO_DOM_HOST_OS === undefined
? `${styles.bg} ${closing ? styles.bgExit : ''}`
: undefined
}
onClick={() => {
if (isDismissable) {
onMinimize();
}
}}
/>
<div
className={classNames(
styles.container,
process.env.EXPO_DOM_HOST_OS !== 'android' && styles.containerTopRadius,
closing && styles.containerExit
)}>
<LogBoxContent
log={log}
selectedLogIndex={selectedLogIndex}
logs={logs}
isDismissable={isDismissable}
onMinimize={onMinimize}
/>
</div>
</div>
);
}
function LogBoxContent({
log,
selectedLogIndex,
logs,
isDismissable,
onMinimize,
}: {
log: LogBoxLog;
selectedLogIndex: number;
logs: LogBoxLog[];
isDismissable: boolean;
onMinimize: (cb?: () => void) => void;
}) {
const { serverRoot, sdkVersion } = useDevServer();
const onDismiss = (): void => {
// Here we handle the cases when the log is dismissed and it
// was either the last log, or when the current index
// is now outside the bounds of the log array.
if (selectedLogIndex === null) return;
if (logs.length - 1 <= 0) {
// Only one log, minimize the overlay and dismiss (remove) the log.
onMinimize(() => {
LogBoxData.dismiss(logs[0]);
});
} else if (selectedLogIndex <= logs.length - 1) {
// Multiple logs, calculate the new selected, select it and dismiss (remove) the previously selected.
const toDismissIndex = selectedLogIndex;
// If we dismiss (remove) the first (toDismissIndex = 0) log, select the closes next log
// (second one, which will become also the first log in the array)
// so we stay at index 0, no setSelectedLog call needed.
if (toDismissIndex !== 0) {
LogBoxData.setSelectedLog(toDismissIndex - 1);
}
LogBoxData.dismiss(logs[toDismissIndex]);
}
};
const onChangeSelectedIndex = useCallback((index: number): void => {
LogBoxData.setSelectedLog(index);
}, []);
useEffect(() => {
if (log) {
LogBoxData.symbolicateLogNow('stack', log);
LogBoxData.symbolicateLogNow('component', log);
}
}, [log]);
useEffect(() => {
// Optimistically symbolicate the last and next logs.
if (logs.length > 1) {
const selected = selectedLogIndex;
const lastIndex = logs.length - 1;
const prevIndex = selected - 1 < 0 ? lastIndex : selected - 1;
const nextIndex = selected + 1 > lastIndex ? 0 : selected + 1;
for (const type of ['component', 'stack'] as const) {
LogBoxData.symbolicateLogLazy(type, logs[prevIndex]);
LogBoxData.symbolicateLogLazy(type, logs[nextIndex]);
}
}
}, [logs, selectedLogIndex]);
const _handleRetry = useCallback(
(type: StackType) => {
LogBoxData.retrySymbolicateLogNow(type, log);
},
[log]
);
const { onReload, onCopyText } = useActions();
const onCopy = () => {
// Copy log to clipboard
const errContents = [log.message.content.trim()];
const componentStack = log.getAvailableStack('component');
if (componentStack?.length) {
errContents.push('', 'Component Stack', getFormattedStackTrace(componentStack, serverRoot));
}
const stackTrace = log.getAvailableStack('stack');
if (stackTrace?.length) {
errContents.push('', 'Call Stack', getFormattedStackTrace(stackTrace, serverRoot));
}
onCopyText?.(errContents.join('\n'));
};
const [collapsed, setCollapsed] = useState(true);
const headerTitle = HEADER_TITLE_MAP[log.level] ?? log.type;
const headerBlurRef = React.useRef<HTMLDivElement>(null);
const scrollRef = React.useRef<HTMLDivElement>(null);
// Transition the opacity of the header blur when the scroll position changes.
useEffect(() => {
const scrollElement = scrollRef.current;
const headerBlurElement = headerBlurRef.current;
if (scrollElement && headerBlurElement) {
const handleScroll = () => {
const scrollTop = scrollElement.scrollTop;
const opacity = Math.min(scrollTop / 16, 1);
headerBlurElement.style.opacity = `${opacity}`;
};
scrollElement.addEventListener('scroll', handleScroll);
return () => {
scrollElement.removeEventListener('scroll', handleScroll);
};
}
return () => {};
}, [scrollRef, headerBlurRef]);
let codeFrames = log?.codeFrame
? (Object.entries(log.codeFrame).filter(([, value]) => value?.content) as [StackType, any][])
: [];
codeFrames = uniqueBy(
uniqueBy(codeFrames, ([, value]) => {
return [value.fileName, value.location?.column, value.location?.row].join(':');
}),
([, value]) => {
return value?.content;
}
);
return (
<div className={styles.content}>
<div className={styles.headerBlur} ref={headerBlurRef} />
<div
style={{
position: 'sticky',
top: 0,
left: 0,
right: 0,
zIndex: 1,
backgroundColor: 'var(--expo-log-color-background)',
}}>
<ErrorOverlayHeader
sdkVersion={sdkVersion}
selectedIndex={selectedLogIndex}
total={logs.length}
isDismissable={isDismissable}
onDismiss={onDismiss}
onMinimize={() => onMinimize()}
onSelectIndex={onChangeSelectedIndex}
level={log.level}
onCopy={onCopy}
onReload={onReload}
/>
</div>
<div className={styles.scroll} ref={scrollRef}>
<ErrorMessageHeader
collapsed={collapsed}
onPress={() => setCollapsed(!collapsed)}
message={log.message}
level={log.level}
title={headerTitle}
/>
{
<div style={{ padding: '0 1rem', gap: 10, display: 'flex', flexDirection: 'column' }}>
{codeFrames.map(([key, codeFrame]) => {
// If no frame from a stack is expanded, likely no frame is from user code, let's not show the code snippet.
// This avoid cluttering the overlay with irrelevant code frames of node_modules and internals.
if (
log.getStackStatus(key) === 'COMPLETE' &&
log.getAvailableStack(key) &&
// If there are no frames (for example in build errors) we want to show the code frame.
log.getAvailableStack(key)!.length > 0 &&
!log.getAvailableStack(key)!.some(({ collapse }) => !collapse)
) {
return null;
}
return (
<ErrorCodeFrame key={key} showPathsRelativeTo={serverRoot} codeFrame={codeFrame} />
);
})}
{log.isMissingModuleError && (
<InstallMissingModuleTerminal moduleName={log.isMissingModuleError} />
)}
{!!log?.componentStack?.length && (
<StackTraceList
key={selectedLogIndex + '-component-stack'}
type="component"
stack={log.getAvailableStack('component')}
symbolicationStatus={log.getStackStatus('component')}
// eslint-disable-next-line react/jsx-no-bind
onRetry={_handleRetry.bind(_handleRetry, 'component')}
/>
)}
<StackTraceList
key={selectedLogIndex + '-stack'}
type="stack"
stack={log.getAvailableStack('stack')}
symbolicationStatus={log.getStackStatus('stack')}
// eslint-disable-next-line react/jsx-no-bind
onRetry={_handleRetry.bind(_handleRetry, 'stack')}
/>
</div>
}
{!isDismissable && (
<ErrorOverlayFooter message="Build-time errors can only be dismissed by fixing the issue." />
)}
</div>
</div>
);
}
function InstallMissingModuleTerminal({ moduleName }: { moduleName: string }) {
return <Terminal moduleName={moduleName} content={`$ npx expo install ${moduleName}`} />;
}
function uniqueBy<T>(array: T[], key: (item: T) => string): T[] {
const seen = new Set<string>();
return array.filter((item) => {
const k = key(item);
if (seen.has(k)) {
return false;
}
seen.add(k);
return true;
});
}
function ErrorOverlayFooter({ message }: { message?: string }) {
return (
<div className={styles.footer}>
<footer
style={{
padding: '1rem',
flex: 1,
borderTop: `1px solid var(--expo-log-color-border)`,
}}>
<span
style={{
color: 'var(--expo-log-secondary-label)',
fontSize: '0.875rem',
fontFamily: 'var(--expo-log-font-family)',
}}>
{message}
</span>
</footer>
</div>
);
}
function ErrorMessageHeader(props: {
collapsed: boolean;
message: Message;
level: LogLevel;
title: string;
onPress: () => void;
}) {
return (
<div
style={{
padding: '0 1rem',
display: 'flex',
gap: 8,
flexDirection: 'column',
}}>
<div style={{ display: 'flex' }}>
<span
data-testid="logbox_title"
style={{
fontFamily: 'var(--expo-log-font-family)',
padding: 8,
marginLeft: -4,
backgroundColor: 'rgba(205, 97, 94, 0.2)',
borderRadius: 8,
fontWeight: '600',
fontSize: 14,
color: `var(--expo-log-color-danger)`,
}}>
{props.title}
</span>
</div>
<span
style={{
color: 'var(--expo-log-color-label)',
fontFamily: 'var(--expo-log-font-family)',
fontSize: 16,
whiteSpace: 'pre-wrap',
fontWeight: '500',
wordBreak: 'normal',
}}>
<LogBoxMessage
maxLength={props.collapsed ? SHOW_MORE_MESSAGE_LENGTH : Infinity}
message={props.message}
/>
<ShowMoreButton {...props} />
</span>
</div>
);
}

View File

@@ -0,0 +1,33 @@
import React from 'react';
import { SHOW_MORE_MESSAGE_LENGTH } from './Constants';
import { Message } from '../Data/Types';
export default function ShowMoreButton({
message,
collapsed,
onPress,
}: {
collapsed: boolean;
message: Message;
onPress: () => void;
}) {
if (message.content.length < SHOW_MORE_MESSAGE_LENGTH || !collapsed) {
return null;
}
return (
<button
style={{
color: 'var(--expo-log-color-label)',
fontFamily: 'var(--expo-log-font-family)',
backgroundColor: 'transparent',
cursor: 'pointer',
border: 'none',
opacity: 0.7,
fontSize: 14,
}}
onClick={onPress}>
... See more
</button>
);
}

View File

@@ -0,0 +1,160 @@
.root {
margin-top: 5px;
display: flex;
flex-direction: column;
gap: 6px;
}
.header {
display: flex;
align-items: center;
margin-bottom: 4px;
justify-content: space-between;
}
.headerLeft {
display: flex;
gap: 8px;
align-items: center;
}
.headerIcon {
width: 1rem;
height: 1rem;
color: var(--expo-log-color-label);
}
.headerTitle {
font-family: var(--expo-log-font-family);
color: var(--expo-log-color-label);
font-size: 18px;
font-weight: 600;
margin: 0;
}
.badge {
background-color: rgba(234.6, 234.6, 244.8, 0.1);
font-family: var(--expo-log-font-family);
color: var(--expo-log-color-label);
border-radius: 50px;
font-size: 12px;
aspect-ratio: 1/1;
display: flex;
width: 22px;
height: 22px;
justify-content: center;
align-items: center;
}
.collapseButton {
background: transparent;
border: none;
padding: 0;
cursor: pointer;
}
.collapseContent {
padding: 6px;
border-radius: 8px;
transition: background-color 0.3s;
outline-color: transparent;
color: rgba(234.6, 234.6, 244.8, 0.6);
display: flex;
justify-content: center;
align-items: center;
gap: 2px;
user-select: none;
}
@media (hover: hover) {
.collapseButton:hover .collapseContent {
background-color: rgba(234.6, 234.6, 244.8, 0.1);
}
}
.collapseTitle {
display: none;
font-family: var(--expo-log-font-family);
font-size: 14px;
user-select: none;
color: rgba(234.6, 234.6, 244.8, 0.6);
}
/* xs */
@media screen and (min-width: 320px) {
.collapseTitle {
display: block;
}
}
.transition {
overflow: hidden;
transition: height 0.3s ease, opacity 0.3s ease;
}
.warningBox {
background-color: var(--expo-log-secondary-system-background);
border: 1px solid var(--expo-log-color-border);
padding: 10px 15px;
border-radius: 5px;
}
.warningText {
font-family: var(--expo-log-font-family);
color: var(--expo-log-color-label);
opacity: 0.7;
font-size: 13px;
font-weight: 400;
}
.list {
display: flex;
flex-direction: column;
gap: 2px;
}
.stackFrame {
display: flex;
flex-direction: column;
flex: 1;
padding: 6px 12px;
border-radius: 8px;
transition: background-color 0.3s;
outline-color: transparent;
background-color: transparent;
color: var(--expo-log-color-label);
cursor: pointer;
/* collapsed frames */
&[data-collapsed] {
opacity: 0.4;
}
/* aria-disabled */
&[aria-disabled='true'] {
background-color: transparent;
color: var(--expo-log-color-label);
cursor: unset;
}
}
@media (hover: hover) {
.stackFrame:hover {
background-color: rgba(234.6, 234.6, 244.8, 0.1);
outline-color: var(--expo-log-color-label);
}
}
.stackFrameTitle {
font-size: 14px;
font-weight: 400;
line-height: 1.5;
overflow-wrap: break-word;
}
.stackFrameFile {
font-size: 12px;
font-weight: 300;
opacity: 0.7;
overflow-wrap: break-word;
}

View File

@@ -0,0 +1,391 @@
/**
* Copyright (c) 650 Industries.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { useState, type SVGProps } from 'react';
import { LogBoxInspectorSourceMapStatus } from './LogBoxInspectorSourceMapStatus';
import styles from './StackTraceList.module.css';
import { useDevServer } from '../ContextDevServer';
import type { StackType, MetroStackFrame } from '../Data/Types';
import {
getStackFormattedLocation,
isStackFileAnonymous,
openFileInEditor,
} from '../utils/devServerEndpoints';
function Transition({
children,
status,
onExitComplete,
isInitial,
index,
initialDelay = 50,
}: {
children: React.ReactNode;
status: 'stable' | 'entering' | 'exiting';
onExitComplete: () => void;
isInitial: boolean;
index: number;
initialDelay?: number;
}) {
const ref = React.useRef<HTMLDivElement>(null);
React.useLayoutEffect(() => {
const element = ref.current;
if (!element) return;
if (isInitial && status === 'stable') {
element.style.height = '0px';
element.style.opacity = '0';
setTimeout(() => {
element.style.height = `${element.scrollHeight}px`;
element.style.opacity = '1';
}, index * initialDelay);
} else if (status === 'entering') {
element.style.height = '0px';
element.style.opacity = '0';
requestAnimationFrame(() => {
element.style.height = `${element.scrollHeight}px`;
element.style.opacity = '1';
});
} else if (status === 'exiting') {
element.style.height = `${element.scrollHeight}px`;
element.style.opacity = '1';
requestAnimationFrame(() => {
element.style.height = '0px';
element.style.opacity = '0';
});
} else if (status === 'stable') {
element.style.height = `${element.scrollHeight}px`;
element.style.opacity = '1';
}
}, [status, isInitial, index]);
React.useEffect(() => {
if (status === 'exiting') {
const handleTransitionEnd = (e: TransitionEvent) => {
if (e.propertyName === 'height') {
onExitComplete();
}
};
ref.current?.addEventListener('transitionend', handleTransitionEnd);
return () => {
ref.current?.removeEventListener('transitionend', handleTransitionEnd);
};
}
return undefined;
}, [status, onExitComplete]);
return (
<div ref={ref} className={styles.transition}>
{children}
</div>
);
}
type DisplayItem = {
item: { id: number; content: React.ReactNode; isCollapsed: boolean };
status: 'stable' | 'entering' | 'exiting';
};
function List({
items,
showCollapsed,
isInitial,
initialDelay,
}: {
items: { id: number; content: React.ReactNode; isCollapsed: boolean }[];
showCollapsed: boolean;
isInitial: boolean;
initialDelay: number;
}) {
const [displayItems, setDisplayItems] = React.useState<DisplayItem[]>(
items.filter((item) => !item.isCollapsed).map((item) => ({ item, status: 'stable' }))
);
React.useEffect(() => {
const visibleItems = showCollapsed ? items : items.filter((item) => !item.isCollapsed);
setDisplayItems((prev: DisplayItem[]): DisplayItem[] => {
const prevIds = new Set(prev.map((d) => d.item.id));
const newItems: DisplayItem[] = visibleItems
.filter((item) => !prevIds.has(item.id))
.map((item) => ({ item, status: 'entering' }));
const updatedPrev: DisplayItem[] = prev.map((d) => {
if (!visibleItems.some((item) => item.id === d.item.id)) {
return { ...d, status: 'exiting' };
}
return d;
});
return [...updatedPrev, ...newItems].sort((a, b) => a.item.id - b.item.id);
});
}, [showCollapsed, items]);
const onExitComplete = (id: number) => {
setDisplayItems((prev) => prev.filter((d) => d.item.id !== id));
};
return (
<div>
{displayItems.map((d, index) => (
<Transition
key={d.item.id}
status={d.status}
onExitComplete={() => onExitComplete(d.item.id)}
isInitial={isInitial}
initialDelay={initialDelay}
index={index}>
{d.item.content}
</Transition>
))}
</div>
);
}
function useContainerWidth() {
const [width, setWidth] = useState(0);
const ref = React.useRef<HTMLDivElement>(null);
React.useLayoutEffect(() => {
if (ref.current) {
setWidth(ref.current.clientWidth);
const handleResize = () => {
if (ref.current) {
setWidth(ref.current.clientWidth);
}
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}
return undefined;
}, [ref]);
return { width, ref };
}
export function StackTraceList({
onRetry,
type,
stack,
symbolicationStatus,
}: {
type: StackType;
onRetry: () => void;
stack: MetroStackFrame[] | null;
symbolicationStatus: 'COMPLETE' | 'FAILED' | 'NONE' | 'PENDING';
}) {
const [collapsed, setCollapsed] = useState(true);
const stackCount = stack?.length;
const [isInitial, setIsInitial] = React.useState(true);
const initialDelay = 50;
const initialTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null);
React.useEffect(() => {
if (isInitial) {
const visibleCount = stack?.filter((frame) => !frame.collapse).length ?? 0;
initialTimer.current = setTimeout(
() => setIsInitial(false),
visibleCount * initialDelay + 500
);
}
return () => {
if (initialTimer.current) {
clearTimeout(initialTimer.current);
initialTimer.current = null;
}
};
}, [isInitial]);
const { width: containerWidth, ref } = useContainerWidth();
if (!stackCount) {
return null;
}
const collapseTitle = getCollapseMessage(stack, !!collapsed);
// Unfortunately RN implementation ignores symbolication collapse flag and expands all frames of components stack.
// https://github.com/facebook/react-native/blob/93ac7db54ead26da002abebb8f987f58b6be3a1d/packages/react-native/Libraries/LogBox/Data/LogBoxLog.js#L51
const expandFirst = 4;
const stackTraceItems = stack.map((frame, index) => {
const { file, lineNumber } = frame;
const isLaunchable =
!isStackFileAnonymous(frame) &&
symbolicationStatus === 'COMPLETE' &&
file != null &&
lineNumber != null;
const isCollapsed =
type === 'component' && process.env.EXPO_DOM_HOST_OS !== 'web'
? index >= expandFirst
: !!frame.collapse;
return {
id: index,
content: (
<StackTraceItem
key={index}
isLaunchable={isLaunchable}
frame={frame}
onPress={isLaunchable ? () => openFileInEditor(file, lineNumber) : undefined}
/>
),
isCollapsed,
};
});
return (
<div className={styles.root}>
{/* Header */}
<div ref={ref} className={styles.header}>
<div className={styles.headerLeft}>
{type === 'component' ? (
<ReactIcon stroke="unset" className={styles.headerIcon} />
) : (
<JavaScriptIcon className={styles.headerIcon} />
)}
<h3 className={styles.headerTitle}>
{type === 'component' ? 'Component Stack' : 'Call Stack'}
</h3>
<span data-text className={styles.badge}>
{stackCount}
</span>
<LogBoxInspectorSourceMapStatus
onPress={symbolicationStatus === 'FAILED' ? onRetry : null}
status={symbolicationStatus}
/>
</div>
<button className={styles.collapseButton} onClick={() => setCollapsed(!collapsed)}>
<div title={collapseTitle.full} className={styles.collapseContent}>
<span className={styles.collapseTitle}>
{containerWidth > 440 ? collapseTitle.full : collapseTitle.short}
</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
{collapsed ? (
<>
<path d="m7 15 5 5 5-5" />
<path d="m7 9 5-5 5 5" />
</>
) : (
<>
<path d="m7 20 5-5 5 5" />
<path d="m7 4 5 5 5-5" />
</>
)}
</svg>
</div>
</button>
</div>
{/* Body */}
{symbolicationStatus !== 'COMPLETE' && (
<div className={styles.warningBox}>
<span className={styles.warningText}>
This call stack is not symbolicated. Some features are unavailable such as viewing the
function name or tapping to open files.
</span>
</div>
)}
{/* List */}
<div className={styles.list}>
<List
initialDelay={initialDelay}
items={stackTraceItems}
showCollapsed={!collapsed}
isInitial={isInitial}
/>
</div>
</div>
);
}
function StackTraceItem({
frame,
onPress,
isLaunchable,
}: {
isLaunchable: boolean;
frame: MetroStackFrame;
onPress?: () => void;
}) {
const { serverRoot } = useDevServer();
const fileName = getStackFormattedLocation(serverRoot, frame);
return (
<div
aria-disabled={!isLaunchable ? true : undefined}
onClick={onPress}
className={styles.stackFrame}
data-collapsed={frame.collapse === true ? '' : undefined}>
<code className={styles.stackFrameTitle}>{frame.methodName}</code>
<code className={styles.stackFrameFile}>{fileName}</code>
</div>
);
}
const ReactIcon = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38a2.167 2.167 0 0 0-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44a23.476 23.476 0 0 0-3.107-.534A23.892 23.892 0 0 0 12.769 4.7c1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442a22.73 22.73 0 0 0-3.113.538 15.02 15.02 0 0 1-.254-1.42c-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87a25.64 25.64 0 0 1-4.412.005 26.64 26.64 0 0 1-1.183-1.86c-.372-.64-.71-1.29-1.018-1.946a25.17 25.17 0 0 1 1.013-1.954c.38-.66.773-1.286 1.18-1.868A25.245 25.245 0 0 1 12 8.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933a25.952 25.952 0 0 0-1.345-2.32zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493a23.966 23.966 0 0 0-1.1-2.98c.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98a23.142 23.142 0 0 0-1.086 2.964c-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39a25.819 25.819 0 0 0 1.341-2.338zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143a22.005 22.005 0 0 1-2.006-.386c.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295a1.185 1.185 0 0 1-.553-.132c-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z" />
</svg>
);
const JavaScriptIcon = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" {...props}>
<path d="M0 0h24v24H0V0zm22.034 18.276c-.175-1.095-.888-2.015-3.003-2.873-.736-.345-1.554-.585-1.797-1.14-.091-.33-.105-.51-.046-.705.15-.646.915-.84 1.515-.66.39.12.75.42.976.9 1.034-.676 1.034-.676 1.755-1.125-.27-.42-.404-.601-.586-.78-.63-.705-1.469-1.065-2.834-1.034l-.705.089c-.676.165-1.32.525-1.71 1.005-1.14 1.291-.811 3.541.569 4.471 1.365 1.02 3.361 1.244 3.616 2.205.24 1.17-.87 1.545-1.966 1.41-.811-.18-1.26-.586-1.755-1.336l-1.83 1.051c.21.48.45.689.81 1.109 1.74 1.756 6.09 1.666 6.871-1.004.029-.09.24-.705.074-1.65l.046.067zm-8.983-7.245h-2.248c0 1.938-.009 3.864-.009 5.805 0 1.232.063 2.363-.138 2.711-.33.689-1.18.601-1.566.48-.396-.196-.597-.466-.83-.855-.063-.105-.11-.196-.127-.196l-1.825 1.125c.305.63.75 1.172 1.324 1.517.855.51 2.004.675 3.207.405.783-.226 1.458-.691 1.811-1.411.51-.93.402-2.07.397-3.346.012-2.054 0-4.109 0-6.179l.004-.056z" />
</svg>
);
function getCollapseMessage(
stackFrames: MetroStackFrame[],
collapsed: boolean
): { short: string; full: string } {
if (stackFrames.length === 0) {
return { full: 'No frames to show', short: 'No frames' };
}
const collapsedCount = stackFrames.reduce((count, { collapse }) => {
if (collapse === true) {
return count + 1;
}
return count;
}, 0);
if (collapsedCount === 0) {
if (collapsed) {
return { full: 'Show all', short: 'Show all' };
} else {
return { full: 'Hide extended frames', short: 'Hide' };
}
}
const short = collapsed ? 'Show' : 'Hide';
const framePlural = `frame${collapsedCount > 1 ? 's' : ''}`;
if (collapsedCount === stackFrames.length) {
return {
full: `${short}${collapsedCount > 1 ? ' all ' : ' '}${collapsedCount} ignore-listed ${framePlural}`,
short,
};
} else {
// Match the chrome inspector wording
return { full: `${short} ${collapsedCount} ignored-listed ${framePlural}`, short };
}
}

View File

@@ -0,0 +1,103 @@
.container {
bottom: calc(6px + env(safe-area-inset-bottom, 0px));
left: 10px;
right: 10px;
max-width: 320px;
position: fixed;
display: flex;
}
.toast {
background-color: #632e2c;
height: 48px;
justify-content: center;
margin-bottom: 4px;
display: flex;
border-radius: 6px;
transition: background-color 0.2s;
border: 1px solid var(--expo-log-color-danger);
cursor: pointer;
overflow: hidden;
flex: 1;
padding: 0 10px;
flex-direction: row;
align-items: center;
gap: 8px;
font-family: var(--expo-log-font-family);
}
@media (hover: hover) {
.toast:hover {
background-color: #924340;
}
}
.message {
user-select: none;
padding-left: 8px;
color: var(--expo-log-color-label);
font-family: var(--expo-log-font-family);
flex: 1;
font-size: 14px;
line-height: 22px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
.count {
min-width: 30px;
height: 30px;
border-radius: 6px;
justify-content: center;
align-items: center;
display: flex;
background: var(--expo-log-color-danger);
}
.countText {
color: var(--expo-log-color-label);
font-family: var(--expo-log-font-family);
font-size: 14px;
line-height: 18px;
text-align: center;
font-weight: 600;
text-shadow: 0px 0px 3px rgba(51, 51, 51, 0.8);
}
.dismissButton {
background: transparent;
border: none;
padding: 12px 10px;
margin: -12px -10px;
margin-left: calc(5px - 10px);
cursor: pointer;
}
.dismissContent {
background-color: rgba(255, 255, 255, 0.1);
height: 20px;
width: 20px;
border-radius: 25px;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s;
}
@media (hover: hover) {
.dismissButton:hover .dismissContent {
opacity: 0.8;
}
}
.dismissButton:active .dismissContent {
opacity: 0.5;
}
.dismissIcon {
width: 12px;
height: 12px;
color: white;
}

187
node_modules/@expo/log-box/src/toast/ErrorToast.tsx generated vendored Normal file
View File

@@ -0,0 +1,187 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React, { useEffect, useCallback, useMemo } from 'react';
import styles from './ErrorToast.module.css';
import * as LogBoxData from '../Data/LogBoxData';
import { LogBoxLog, useLogs } from '../Data/LogBoxLog';
import { LogBoxMessage } from '../overlay/Message';
import { parseUnexpectedThrownValue } from '../utils/parseUnexpectedThrownValue';
import '../overlay/Overlay.module.css';
export function ErrorToastContainer() {
useRejectionHandler();
const { logs, isDisabled } = useLogs();
if (!logs.length || isDisabled) {
return null;
}
return <ErrorToastStack logs={logs} />;
}
function ErrorToastStack({ logs }: { logs: LogBoxLog[] }) {
const onDismissErrors = useCallback(() => {
LogBoxData.clearErrors();
}, []);
const setSelectedLog = useCallback((index: number): void => {
LogBoxData.setSelectedLog(index);
}, []);
function openLog(log: LogBoxLog) {
let index = logs.length - 1;
while (index > 0 && logs[index] !== log) {
index -= 1;
}
setSelectedLog(index);
}
const errors = useMemo(
() => logs.filter((log) => log.level === 'error' || log.level === 'fatal'),
[logs]
);
return (
<div className={styles.container}>
{errors.length > 0 && (
<ErrorToast
log={errors[errors.length - 1]}
level="error"
totalLogCount={errors.length}
onPressOpen={() => openLog(errors[errors.length - 1])}
onPressDismiss={onDismissErrors}
/>
)}
</div>
);
}
function useSymbolicatedLog(log: LogBoxLog) {
// Eagerly symbolicate so the stack is available when pressing to inspect.
useEffect(() => {
LogBoxData.symbolicateLogLazy('stack', log);
LogBoxData.symbolicateLogLazy('component', log);
}, [log]);
}
function ErrorToast(props: {
log: LogBoxLog;
totalLogCount: number;
level: 'warn' | 'error';
onPressOpen: () => void;
onPressDismiss: () => void;
}) {
const { totalLogCount, log } = props;
useSymbolicatedLog(log);
return (
<div className={styles.toast} onClick={props.onPressOpen} role="button" tabIndex={0}>
<Count count={totalLogCount} />
<span className={styles.message}>
{log.message && <LogBoxMessage maxLength={40} message={log.message} />}
</span>
<Dismiss onPress={props.onPressDismiss} />
</div>
);
}
function Count({ count }: { count: number }) {
return (
<div className={styles.count}>
<span className={styles.countText}>{count <= 1 ? '!' : count}</span>
</div>
);
}
function Dismiss({ onPress }: { onPress: () => void }) {
return (
<button
className={styles.dismissButton}
onClick={(e) => {
e.stopPropagation();
onPress();
}}>
<div className={styles.dismissContent}>
<svg className={styles.dismissIcon} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M17 7L7 17M7 7L17 17"
/>
</svg>
</div>
</button>
);
}
function useStackTraceLimit(limit: number) {
const current = React.useRef(0);
React.useEffect(() => {
try {
// @ts-ignore: StackTraceLimit is not defined in the Error type
const currentLimit = Error.stackTraceLimit;
// @ts-ignore: StackTraceLimit is not defined in the Error type
Error.stackTraceLimit = limit;
current.current = currentLimit;
} catch {}
return () => {
try {
// @ts-ignore: StackTraceLimit is not defined in the Error type
Error.stackTraceLimit = current.current;
} catch {}
};
}, [limit]);
}
function useRejectionHandler() {
const hasError = React.useRef(false);
useStackTraceLimit(35);
React.useEffect(() => {
function onUnhandledError(ev: ErrorEvent) {
hasError.current = true;
const error: (Error & { componentStack?: string | null }) | undefined | object = ev?.error;
if (!error || !(error instanceof Error) || typeof error.stack !== 'string') {
// TODO: Handle non-Error objects?
return;
}
error.componentStack = React.captureOwnerStack();
LogBoxData.addException(parseUnexpectedThrownValue(error));
}
function onUnhandledRejection(ev: PromiseRejectionEvent) {
hasError.current = true;
const reason = ev?.reason;
if (!reason || !(reason instanceof Error) || typeof reason.stack !== 'string') {
// TODO: Handle non-Error objects?
return;
}
LogBoxData.addException(parseUnexpectedThrownValue(reason));
}
window.addEventListener('unhandledrejection', onUnhandledRejection);
window.addEventListener('error', onUnhandledError);
return () => {
window.removeEventListener('error', onUnhandledError);
window.removeEventListener('unhandledrejection', onUnhandledRejection);
};
}, []);
return hasError;
}
export default LogBoxData.withSubscription(ErrorToastContainer);

4
node_modules/@expo/log-box/src/typings.d.ts generated vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}

5
node_modules/@expo/log-box/src/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
// This is the main export for `@expo/log-box/utils` used in the `@expo/cli` and `expo/async-require/hmr`
// This needs to be transpiled to CJS for use in the Expo CLI
export { parseWebBuildErrors } from './utils/parseWebBuildErrors';
export { withoutANSIColorStyles } from './utils/withoutANSIStyles';

3
node_modules/@expo/log-box/src/utils/classNames.ts generated vendored Normal file
View File

@@ -0,0 +1,3 @@
export function classNames(...classes: (string | undefined | false | null)[]) {
return classes.filter(Boolean).join(' ');
}

View File

@@ -0,0 +1,108 @@
import { LogBoxLog } from '../Data/LogBoxLog';
import { CodeFrame, StackType } from '../Data/Types';
import { parseLogBoxException } from '../Data/parseLogBoxLog';
export function convertToExpoLogBoxLog({
symbolicated,
symbolicatedComponentStack,
codeFrame,
componentCodeFrame,
...log
}: any): LogBoxLog {
const outputCodeFrame: Partial<Record<StackType, CodeFrame>> = {};
if (codeFrame) {
outputCodeFrame.stack = codeFrame;
}
if (componentCodeFrame) {
outputCodeFrame.component = componentCodeFrame;
}
const outputSymbolicated = {
stack: {
error: null,
stack: null,
status: 'NONE',
},
component: {
error: null,
stack: null,
status: 'NONE',
},
};
if (symbolicated) {
outputSymbolicated.stack = symbolicated;
}
if (symbolicatedComponentStack) {
outputSymbolicated.component = {
error: symbolicatedComponentStack.error,
// @ts-ignore
stack: symbolicatedComponentStack.componentStack?.map((frame) => ({
// From the upstream style (incorrect)
// {
// "fileName": "/Users/evanbacon/Documents/GitHub/expo/node_modules/react-native/Libraries/Components/View/View.js",
// "location": { "row": 32, "column": 33 },
// "content": "React.forwardRef$argument_0",
// "collapse": false
// },
// To the stack frame style (correct)
column: frame.location?.column,
file: frame.fileName,
lineNumber: frame.location?.row,
methodName: frame.content,
collapse: frame.collapse,
})),
status: symbolicatedComponentStack.status,
};
}
return new LogBoxLog({
...log,
codeFrame: outputCodeFrame,
symbolicated: outputSymbolicated,
});
}
export function convertNativeToExpoLogBoxLog({ message, stack }: any): LogBoxLog {
let processedMessage = message;
let processedStack = stack || [];
if (processedMessage.startsWith('Unable to load script.')) {
// Unable to load script native JVM stack is not useful.
processedStack = [];
}
if (process.env.EXPO_DOM_HOST_OS === 'android') {
try {
const bodyIndex = processedMessage.indexOf('Body:');
if (bodyIndex !== -1) {
const originalJson = processedMessage.slice(bodyIndex + 5);
if (originalJson) {
const originalErrorResponseBody = JSON.parse(originalJson);
processedMessage = originalErrorResponseBody.message;
}
}
} catch (e) {
// Ignore JSON parse errors
}
}
const log = new LogBoxLog(
parseLogBoxException({
message: processedMessage,
originalMessage: processedMessage,
stack: processedStack,
name: undefined,
componentStack: undefined,
id: -1,
isComponentError: false,
isFatal: true,
})
);
// Never show stack for native errors, these are typically bundling errors, component stack would lead to LogBox.
log.componentStack = [];
return log;
}

View File

@@ -0,0 +1,214 @@
import type { CodeFrame, MetroStackFrame } from '../Data/Types';
import { fetchTextAsync } from '../fetchHelper';
export type SymbolicatedStackTrace = {
stack: MetroStackFrame[];
codeFrame?: CodeFrame;
};
const cache: Map<MetroStackFrame[], Promise<SymbolicatedStackTrace>> = new Map();
export function getBaseUrl() {
const devServerOverride = process.env.EXPO_DEV_SERVER_ORIGIN;
if (devServerOverride) {
return devServerOverride;
}
if (process.env.EXPO_OS !== 'web') {
const getDevServer = require('react-native/Libraries/Core/Devtools/getDevServer').default;
const devServer = getDevServer();
if (!devServer.bundleLoadedFromServer) {
throw new Error('Cannot create devtools websocket connections in embedded environments.');
}
return devServer.url;
}
return window.location.protocol + '//' + window.location.host;
}
function fetchTextAsyncWithBase(url: string, init?: RequestInit) {
const fullUrl = new URL(url, getBaseUrl()).href;
return fetchTextAsync(fullUrl, init);
}
export function openFileInEditor(file: string, lineNumber: number): void {
fetchTextAsyncWithBase('/open-stack-frame', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ file, lineNumber }),
});
}
export async function fetchProjectMetadataAsync(): Promise<{
projectRoot: string;
serverRoot: string;
sdkVersion: string;
}> {
// Dev Server implementation https://github.com/expo/expo/blob/f29b9f3715e42dca87bf3eebf11f7e7dd1ff73c1/packages/%40expo/cli/src/start/server/metro/MetroBundlerDevServer.ts#L1145
const response = await fetchTextAsyncWithBase('/_expo/error-overlay-meta', {
method: 'GET',
});
return JSON.parse(response);
}
async function symbolicateStackTrace(stack: MetroStackFrame[]): Promise<SymbolicatedStackTrace> {
const response = await fetchTextAsyncWithBase('/symbolicate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ stack }),
});
return JSON.parse(response);
}
export function formatProjectFilePath(
projectRoot: string = '',
file: string | null = null
): string {
if (file == null) {
return '<unknown>';
}
return pathRelativeToPath(file.replace(/\\/g, '/'), projectRoot.replace(/\\/g, '/')).replace(
/\?.*$/,
''
);
}
export function getFormattedStackTrace(stack: MetroStackFrame[], projectRoot: string = '') {
return stack
.map((frame) => {
let stack = ` at `;
const location = getStackFormattedLocation(projectRoot, frame);
stack += `${frame.methodName ?? '<unknown>'} (${location})`;
return stack;
})
.join('\n');
}
function pathRelativeToPath(path: string, relativeTo: string, sep = '/') {
const relativeToParts = relativeTo.split(sep);
const pathParts = path.split(sep);
let i = 0;
while (i < relativeToParts.length && i < pathParts.length) {
if (relativeToParts[i] !== pathParts[i]) {
break;
}
i++;
}
return pathParts.slice(i).join(sep);
}
export function isStackFileAnonymous(
frame: Pick<MetroStackFrame, 'column' | 'file' | 'lineNumber'>
): boolean {
return !frame.file || frame.file === '<unknown>' || frame.file === '<anonymous>';
}
export function getStackFormattedLocation(
projectRoot: string | undefined,
frame: Pick<MetroStackFrame, 'column' | 'file' | 'lineNumber'>
): string {
const column = frame.column != null && parseInt(String(frame.column), 10);
let location = formatProjectFilePath(projectRoot, frame.file);
if (frame.lineNumber != null && frame.lineNumber >= 0) {
location += ':' + frame.lineNumber;
if (column && !isNaN(column) && column >= 0) {
location += ':' + (column + 1);
}
}
return location;
}
/**
* Sanitize because sometimes, `symbolicateStackTrace` gives us invalid values.
*/
function normalizeMetroSymbolicatedStackResults({
stack: maybeStack,
codeFrame,
}: SymbolicatedStackTrace): SymbolicatedStackTrace {
if (!Array.isArray(maybeStack)) {
throw new Error('Expected stack to be an array.');
}
const stack = maybeStack.map<MetroStackFrame>((maybeFrame) => {
let collapse = false;
if ('collapse' in maybeFrame) {
if (typeof maybeFrame.collapse !== 'boolean') {
throw new Error('Expected stack frame `collapse` to be a boolean.');
}
collapse = maybeFrame.collapse;
}
return {
arguments: [],
column: maybeFrame.column,
file: maybeFrame.file,
lineNumber: maybeFrame.lineNumber,
methodName: maybeFrame.methodName,
collapse,
};
});
return { stack, codeFrame };
}
export function invalidateCachedStack(stack: MetroStackFrame[]): void {
cache.delete(stack);
}
export function symbolicateStackAndCacheAsync(
stack: MetroStackFrame[]
): Promise<SymbolicatedStackTrace> {
let promise = cache.get(stack);
if (promise == null) {
promise = symbolicateStackTrace(ensureStackFilesHaveParams(stack)).then(
normalizeMetroSymbolicatedStackResults
);
cache.set(stack, promise);
}
return promise;
}
// Sometime the web stacks don't have correct query params, this can lead to Metro errors when it attempts to resolve without a platform.
// This will attempt to reconcile the issue by adding the current query params to the stack frames if they exist, or fallback to some common defaults.
function ensureStackFilesHaveParams(stack: MetroStackFrame[]): MetroStackFrame[] {
const currentSrc =
typeof document !== 'undefined' && document.currentScript
? ('src' in document.currentScript && document.currentScript.src) || null
: null;
const platform = process.env.EXPO_DOM_HOST_OS ?? process.env.EXPO_OS;
const currentParams = currentSrc
? new URLSearchParams(currentSrc)
: new URLSearchParams({
...(platform ? { platform } : undefined),
dev: String(__DEV__),
});
return stack.map((frame) => {
if (
!frame.file?.startsWith('http') ||
// Account for Metro malformed URLs
frame.file.includes('&platform=')
)
return frame;
const url = new URL(frame.file);
if (url.searchParams.has('platform')) {
return frame;
}
currentParams.forEach((value, key) => {
if (url.searchParams.has(key)) return;
url.searchParams.set(key, value);
});
return { ...frame, file: url.toString() };
});
}

View File

@@ -0,0 +1,80 @@
// Related Metro error's formatting (for the portion of the function parsing Metro errors)
// https://github.com/facebook/metro/blob/34bb8913ec4b5b02690b39d2246599faf094f721/packages/metro/src/lib/formatBundlingError.js#L36
const BABEL_TRANSFORM_ERROR_FORMAT =
/^(?:TransformError )?(?:SyntaxError: |ReferenceError: )(.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/;
const BABEL_CODE_FRAME_ERROR_FORMAT =
// Adjusted from original to not capture import stack a part of the code frame
/^(?:TransformError )?(?:.*):? (?:.*?)([/|\\].*): ([\s\S]+?)\n((?:[ >]*\d+[\s|]+[^\n]*\n?)+|\u{001b}\[[0-9;]*m(?:.*\n?)+?(?=\n\n|\n[^\u{001b}\s]|$))/mu;
const METRO_ERROR_FORMAT =
/^(?:(?:InternalError )?Metro has encountered an error:) (.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/u;
const UNABLE_TO_RESOLVE_MODULE_ERROR_FORMAT = /(?:\w )?Unable to resolve module (.*) from/;
export type ParsedBuildError = {
content: string;
fileName: string;
row: number;
column: number;
codeFrame: string;
// Mainly in BabelCodeFrame errors
missingModule?: string;
};
export function parseMetroError(message: string): ParsedBuildError | null {
const e = message.match(METRO_ERROR_FORMAT);
if (!e) {
return null;
}
const [, content, fileName, row, column, codeFrame] = e;
return {
content,
fileName,
row: parseInt(row, 10),
column: parseInt(column, 10),
codeFrame,
};
}
export function parseBabelTransformError(message: string): ParsedBuildError | null {
const e = message.match(BABEL_TRANSFORM_ERROR_FORMAT);
if (!e) {
return null;
}
// Transform errors are thrown from inside the Babel transformer.
const [, fileName, content, row, column, codeFrame] = e;
return {
content,
fileName,
row: parseInt(row, 10),
column: parseInt(column, 10),
codeFrame,
};
}
export function parseBabelCodeFrameError(message: string): ParsedBuildError | null {
const e = message.match(BABEL_CODE_FRAME_ERROR_FORMAT);
if (!e) {
return null;
}
// Codeframe errors are thrown from any use of buildCodeFrameError.
const [, fileName, content, codeFrame] = e;
//TODO: In the future we should send metadata from @expo/cli, but at the moment
// parsing the message is the only way that work across all LogBox scenarios
// (build web, build ios, build android, hmr web, hmr native).
const [, missingModule] = message.match(UNABLE_TO_RESOLVE_MODULE_ERROR_FORMAT) || [];
const messageContent = missingModule ? `Unable to resolve module ${missingModule}` : content;
return {
content: messageContent,
fileName,
row: -1,
column: -1,
codeFrame,
missingModule,
};
}

View File

@@ -0,0 +1,20 @@
import { parse } from 'stacktrace-parser';
import type { MetroStackFrame } from '../Data/Types';
export function parseErrorStack(stack?: string): MetroStackFrame[] {
if (stack == null) {
return [];
}
if (Array.isArray(stack)) {
return stack;
}
return parse(stack).map((frame) => {
// frame.file will mostly look like `http://localhost:8081/index.bundle?platform=web&dev=true&hot=false`
return {
...frame,
column: frame.column != null ? frame.column - 1 : null,
};
});
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 650 Industries.
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { parseErrorStack } from './parseErrorStack';
import { ExtendedExceptionData } from '../LogBox';
import { withoutANSIColorStyles } from './withoutANSIStyles';
/**
* Handles the developer-visible aspect of errors and exceptions
*/
let exceptionID = 0;
/**
* Logs exceptions to the (native) console and displays them
*/
export function parseUnexpectedThrownValue(error: Error | string): ExtendedExceptionData {
let e: Error & {
componentStack?: string;
jsEngine?: string;
isComponentError?: boolean;
originalMessage?: string;
};
if (error instanceof Error) {
e = error;
} else {
// Workaround for reporting errors caused by `throw 'some string'`
// Unfortunately there is no way to figure out the stacktrace in this
// case, so if you ended up here trying to trace an error, look for
// `throw '<error message>'` somewhere in your codebase.
e = new Error(error);
}
const stack = parseErrorStack(e?.stack);
const currentExceptionID = ++exceptionID;
// Keep the ansi error formatting to the message for CLI/Bundler errors such as missing imports.
const originalMessage = e.originalMessage || e.message || '';
// This ensures the console.error has ansi stripped.
let message = withoutANSIColorStyles(originalMessage);
if (e.componentStack != null) {
message += `\n\nThis error is located at:${e.componentStack}`;
}
const namePrefix = e.name == null || e.name === '' ? '' : `${e.name}: `;
if (!message.startsWith(namePrefix)) {
message = namePrefix + message;
}
const data = {
message,
originalMessage: message === originalMessage ? undefined : originalMessage,
name: e.name == null || e.name === '' ? undefined : e.name,
componentStack: typeof e.componentStack === 'string' ? e.componentStack : undefined,
stack,
id: currentExceptionID,
isFatal: true,
extraData: {
jsEngine: e.jsEngine,
rawStack: e.stack,
},
};
return {
...data,
isComponentError: !!e.isComponentError,
};
}

View File

@@ -0,0 +1,88 @@
import { parseBabelCodeFrameError } from './metroBuildErrorsFormat';
import { LogBoxLogDataLegacy, MetroStackFrame } from '../Data/Types';
/**
* Called in expo/cli, the return value is injected into the static error page which is bundled
* instead of the app when the web build fails.
*/
export function parseWebBuildErrors({
error,
projectRoot,
parseErrorStack,
}: {
error: Error & { type?: unknown };
projectRoot: string;
parseErrorStack: (
projectRoot: string,
stack?: string
) => (MetroStackFrame & { collapse?: boolean })[];
}): LogBoxLogDataLegacy {
// NOTE: Ideally this will be merged with the parseWebHmrBuildErrors function
// Remap direct Metro Node.js errors to a format that will appear more client-friendly in the logbox UI.
let stack: MetroStackFrame[] | undefined;
if (isTransformError(error) && error.filename) {
// Syntax errors in static rendering.
stack = [
{
// Avoid using node:path to be compatible with web and RN runtime.
file: `${projectRoot}/${error.filename}`,
methodName: '<unknown>',
// TODO: Import stack
lineNumber: error.lineNumber,
column: error.column,
},
];
} else if (
'originModulePath' in error &&
typeof error.originModulePath === 'string' &&
'targetModuleName' in error &&
typeof error.targetModuleName === 'string' &&
'cause' in error
) {
const { codeFrame } = parseBabelCodeFrameError(error.message) || {};
// We are purposely not using the parsed fileName or content here
// because we have the original data in the error object.
const content = `Unable to resolve module ${error.targetModuleName}`;
return {
level: 'resolution',
// TODO: Add import stacks
stack: [],
isComponentError: false,
componentStack: [],
codeFrame: codeFrame
? {
fileName: error.originModulePath,
location: null, // We are not given the location.
content: codeFrame,
}
: undefined,
message: {
content,
substitutions: [],
},
category: `${error.originModulePath}-${1}-${1}`,
};
} else {
stack = parseErrorStack(projectRoot, error.stack);
}
return {
level: 'static',
message: {
content: error.message,
substitutions: [],
},
isComponentError: false,
stack,
category: 'static',
componentStack: [],
};
}
function isTransformError(
error: any
): error is { type: 'TransformError'; filename: string; lineNumber: number; column: number } {
return error.type === 'TransformError';
}

View File

@@ -0,0 +1,54 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
export function renderInShadowRoot(
id: string,
element: React.ReactNode
): {
unmount: () => void;
} {
const div = document.createElement('div');
div.id = id;
// Position absolute removes element from document flow, preventing layout impact
div.style.position = 'absolute';
document.body.appendChild(div);
const shadowRoot = div.attachShadow({ mode: 'open' });
// Inject reset styles to make the portal truly inert (invisible to layout and browser inspector)
// This is applied via JS to avoid affecting native platforms where :host becomes :root
const resetStyle = document.createElement('style');
resetStyle.textContent = `
:host {
all: initial;
direction: ltr;
position: absolute;
}
`;
shadowRoot.appendChild(resetStyle);
document.querySelectorAll('style').forEach((styleEl) => {
const moduleName = styleEl.getAttribute('data-expo-css-hmr');
const isLogBoxStyle = moduleName && moduleName.includes('expo_log_box');
const isReactNativeStyle = styleEl.id === 'react-native-stylesheet';
if (isLogBoxStyle || isReactNativeStyle) {
shadowRoot.appendChild(styleEl.cloneNode(true));
}
});
const shadowContainer = document.createElement('div');
shadowRoot.appendChild(shadowContainer);
let currentRoot: ReactDOM.Root | null = ReactDOM.createRoot(shadowContainer);
currentRoot.render(element);
return {
unmount: () => {
currentRoot?.unmount();
currentRoot = null;
document.getElementById(id)?.remove();
},
};
}

View File

@@ -0,0 +1,11 @@
export function withoutANSIColorStyles(message: any): any {
if (typeof message !== 'string') {
return message;
}
return message.replace(
// eslint-disable-next-line no-control-regex
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
''
);
}

4
node_modules/@expo/log-box/swap-rn-logbox-parser.js generated vendored Normal file
View File

@@ -0,0 +1,4 @@
// Swaps with `react-native/Libraries/LogBox/Data/parseLogBoxLog.js`
export * from './src/Data/parseLogBoxLogNative';
// Swapped in https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/start/server/metro/withMetroMultiPlatform.ts

5
node_modules/@expo/log-box/swap-rn-logbox.js generated vendored Normal file
View File

@@ -0,0 +1,5 @@
// Swaps with `react-native/Libraries/LogBox/LogBoxInspectorContainer.js`
export { default } from './src/logbox-rn-polyfill';
// Swapped in https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/start/server/metro/withMetroMultiPlatform.ts
// NOTE: For notifications swap `react-native/Libraries/LogBox/LogBoxNotificationContainer.js`

13
node_modules/@expo/log-box/tsconfig.json generated vendored Normal file
View File

@@ -0,0 +1,13 @@
{
"extends": "expo-module-scripts/tsconfig.node",
"compilerOptions": {
"noEmit": true,
"jsx": "react",
"plugins": [
{ "name": "typescript-plugin-css-modules" }
],
"customConditions": ["react-native-strict-api-UNSAFE-ALLOW-SUBPATHS"]
},
"include": ["src/**/*", "app/**/*"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}

8
node_modules/@expo/log-box/tsconfig.lib.json generated vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig",
"compilerOptions": {
"noEmit": false,
"outDir": "build"
},
"include": ["src/utils.ts"],
}

1
node_modules/@expo/log-box/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export * from './build/utils';

1
node_modules/@expo/log-box/utils.js generated vendored Normal file
View File

@@ -0,0 +1 @@
module.exports = require('./build/utils');