feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,5 @@
import registerRootComponent from 'expo/build/launch/registerRootComponent';
import App from '../../App';
registerRootComponent(App);

46
smart-app-city/frontend/node_modules/expo/Expo.podspec generated vendored Normal file
View File

@@ -0,0 +1,46 @@
require 'json'
require 'colored2' # dependency of CocoaPods
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
# Use a global flag to check whether the warning about missing autolinking
# scripts has already been printed. The podspec may be read multiple times
# during `pod install` and we don't want to make the warning more obtrusive.
$expo_warned_about_missing_autolinking |= false
Pod::Spec.new do |s|
s.name = 'Expo'
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 => '13.4',
:osx => '10.15',
:tvos => '13.4'
}
s.source = { git: 'https://github.com/expo/expo.git' }
s.static_framework = true
s.header_dir = 'Expo'
# Don't require the `ExpoModulesCore` dependency if the autolinking hasn't been imported.
# Otherwise, `pod install` would fail because it's not linkable by the community CLI.
if defined?(use_expo_modules!)
s.dependency 'ExpoModulesCore'
elsif !$expo_warned_about_missing_autolinking
puts <<~EOS
Your project includes the Expo package, but React Native Community CLI is unable to install the related Pods.
Make sure to require autolinking scripts from Expo and call `use_expo_modules!` in your target.
Learn more: https://docs.expo.dev/bare/installing-expo-modules
EOS
.yellow
# Suppress the warning next time.
$expo_warned_about_missing_autolinking = true
end
s.source_files = 'ios/**/*.{h,m,swift}'
end

10
smart-app-city/frontend/node_modules/expo/README.md generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# expo
The `expo` package is a single package you can install in any React Native app to begin using Expo modules.
- includes core infrastructure for Expo modules: `expo-modules-core` and `expo-modules-autolinking`.
- bundles a minimal set of Expo modules that are required by nearly every app, such as `expo-asset`.
- provides [`@expo/cli`](https://github.com/expo/expo/blob/main/packages/%40expo/cli/README.md), a small CLI that provides a clean interface around both bundlers (such as Metro and Webpack) and native build tools (Xcode, Simulator.app, Android Studio, ADB, etc.), can generate native projects with `npx expo prebuild`, and aligns compatible package versions with `npx expo install`.
- exposes a JavaScript module that configures an app at runtime as needed to use `expo-font` and to function in Expo Go (optional, only if applicable).
See [CONTRIBUTING](./CONTRIBUTING.md) for instructions on working on this package.

View File

@@ -0,0 +1,99 @@
apply plugin: 'com.android.library'
// Import autolinking script
apply from: "../scripts/autolinking.gradle"
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useDefaultAndroidSdkVersions()
useExpoPublishing()
static def versionToNumber(major, minor, patch) {
return patch * 100 + minor * 10000 + major * 1000000
}
def getRNVersion() {
def nodeModulesVersion = providers.exec {
workingDir(projectDir)
commandLine("node", "-e", "console.log(require('react-native/package.json').version);")
}.standardOutput.asText.get().trim()
def version = safeExtGet("reactNativeVersion", nodeModulesVersion)
def coreVersion = version.split("-")[0]
def (major, minor, patch) = coreVersion.tokenize('.').collect { it.toInteger() }
return versionToNumber(
major,
minor,
patch
)
}
ensureDependeciesWereEvaluated(project)
group = 'host.exp.exponent'
version = '51.0.39'
buildscript {
// Simple helper that allows the root project to override versions declared by this library.
ext.safeExtGet = { prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
}
android {
namespace "expo.core"
defaultConfig {
versionCode 1
versionName "51.0.39"
consumerProguardFiles("proguard-rules.pro")
}
testOptions {
unitTests.includeAndroidResources = true
}
sourceSets {
main {
java {
srcDirs += new File(project.buildDir, generatedFilesSrcDir)
def rnVersion = getRNVersion()
if (rnVersion >= versionToNumber(0, 74, 0)) {
srcDirs += "src/reactWrappers"
}
}
}
}
}
dependencies { dependencyHandler ->
implementation 'com.facebook.react:react-android'
testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test:core:1.5.0'
testImplementation "com.google.truth:truth:1.1.2"
testImplementation 'io.mockk:mockk:1.13.5'
// Link expo modules as dependencies of the adapter. It uses `api` configuration so they all will be visible for the app as well.
// A collection of the dependencies depends on the options passed to `useExpoModules` in your project's `settings.gradle`.
addExpoModulesDependencies(dependencyHandler, project)
}
// A task generating a package list of expo modules.
task generateExpoModulesPackageListTask {
def modulesConfig = getModulesConfig()
def outputPath = getGenerateExpoModulesPackagesListPath()
if (modulesConfig) {
outputs.file(file(outputPath))
inputs.property("modulesConfig", modulesConfig)
}
// TOOD(@lukmccall): fix not working with configuration cache enabled
doLast {
generateExpoModulesPackageList()
}
}
// Run that task during prebuilding phase.
preBuild.dependsOn "generateExpoModulesPackageListTask"

View File

@@ -0,0 +1,26 @@
# For ReactActivityDelegateWrapper
-keepclassmembers public class com.facebook.react.ReactActivityDelegate {
public *;
protected *;
private ReactDelegate mReactDelegate;
}
# Remove this after react-native 0.74.1
-keepclassmembers public class expo.modules.ReactActivityDelegateWrapper {
protected ReactDelegate getReactDelegate();
}
-keepclassmembers public class com.facebook.react.ReactActivity {
private final ReactActivityDelegate mDelegate;
}
# For ReactNativeHostWrapper
-keepclassmembers public class com.facebook.react.ReactNativeHost {
protected *;
}
# For ExpoModulesPackage autolinking
-keepclassmembers public class expo.modules.ExpoModulesPackageList {
public *;
}
-keepnames class * extends expo.modules.core.BasePackage
-keepnames class * implements expo.modules.core.interfaces.Package

View File

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

View File

@@ -0,0 +1,27 @@
package expo.modules
import android.app.Application
import android.content.res.Configuration
import androidx.annotation.UiThread
import expo.modules.core.interfaces.ApplicationLifecycleListener
object ApplicationLifecycleDispatcher {
private var listeners: List<ApplicationLifecycleListener>? = null
@UiThread
private fun getCachedListeners(application: Application): List<ApplicationLifecycleListener> {
return listeners ?: ExpoModulesPackage.packageList
.flatMap { it.createApplicationLifecycleListeners(application) }
.also { listeners = it }
}
@JvmStatic
fun onApplicationCreate(application: Application) {
getCachedListeners(application).forEach { it.onCreate(application) }
}
@JvmStatic
fun onConfigurationChanged(application: Application, newConfig: Configuration) {
getCachedListeners(application).forEach { it.onConfigurationChanged(newConfig) }
}
}

View File

@@ -0,0 +1,41 @@
package expo.modules
import android.util.Log
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
import expo.modules.adapters.react.ModuleRegistryAdapter
import expo.modules.core.ModulePriorities
import expo.modules.core.interfaces.Package
import java.lang.Exception
class ExpoModulesPackage : ReactPackage {
val moduleRegistryAdapter = ModuleRegistryAdapter(packageList)
companion object {
@Suppress("unchecked_cast")
val packageList: List<Package> by lazy {
try {
val expoModules = Class.forName("expo.modules.ExpoModulesPackageList")
val getPackageList = expoModules.getMethod("getPackageList")
(getPackageList.invoke(null) as List<Package>)
.sortedByDescending { ModulePriorities.get(it::class.qualifiedName) }
} catch (e: Exception) {
Log.e("ExpoModulesPackage", "Couldn't get expo package list.", e)
emptyList()
}
}
}
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return moduleRegistryAdapter.createNativeModules(reactContext)
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return moduleRegistryAdapter.createViewManagers(reactContext)
}
}

View File

@@ -0,0 +1,161 @@
// Copyright 2015-present 650 Industries. All rights reserved.
package expo.modules
import android.content.Context
import com.facebook.react.JSEngineResolutionAlgorithm
import com.facebook.react.ReactHost
import com.facebook.react.ReactInstanceEventListener
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.ReactPackageTurboModuleManagerDelegate
import com.facebook.react.bridge.JSBundleLoader
import com.facebook.react.bridge.ReactContext
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.defaults.DefaultComponentsRegistry
import com.facebook.react.defaults.DefaultTurboModuleManagerDelegate
import com.facebook.react.fabric.ComponentFactory
import com.facebook.react.fabric.ReactNativeConfig
import com.facebook.react.interfaces.exceptionmanager.ReactJsExceptionHandler
import com.facebook.react.runtime.BindingsInstaller
import com.facebook.react.runtime.JSCInstance
import com.facebook.react.runtime.JSRuntimeFactory
import com.facebook.react.runtime.ReactHostDelegate
import com.facebook.react.runtime.ReactHostImpl
import com.facebook.react.runtime.hermes.HermesInstance
import java.lang.ref.WeakReference
object ExpoReactHostFactory {
private var reactHost: ReactHost? = null
@UnstableReactNativeAPI
private class ExpoReactHostDelegate(
private val weakContext: WeakReference<Context>,
private val reactNativeHostWrapper: ReactNativeHostWrapper,
override val bindingsInstaller: BindingsInstaller? = null,
private val reactNativeConfig: ReactNativeConfig = ReactNativeConfig.DEFAULT_CONFIG,
override val turboModuleManagerDelegateBuilder: ReactPackageTurboModuleManagerDelegate.Builder =
DefaultTurboModuleManagerDelegate.Builder()
) : ReactHostDelegate {
// Keeps this `_jsBundleLoader` backing property for DevLauncher to replace its internal value
private var _jsBundleLoader: JSBundleLoader? = null
override val jsBundleLoader: JSBundleLoader
get() {
val backingJSBundleLoader = _jsBundleLoader
if (backingJSBundleLoader != null) {
return backingJSBundleLoader
}
val context = weakContext.get() ?: throw IllegalStateException("Unable to get concrete Context")
reactNativeHostWrapper.jsBundleFile?.let { jsBundleFile ->
if (jsBundleFile.startsWith("assets://")) {
return JSBundleLoader.createAssetLoader(context, jsBundleFile, true)
}
return JSBundleLoader.createFileLoader(jsBundleFile)
}
val jsBundleAssetPath = reactNativeHostWrapper.bundleAssetName
return JSBundleLoader.createAssetLoader(context, "assets://$jsBundleAssetPath", true)
}
override val jsMainModulePath: String
get() = reactNativeHostWrapper.jsMainModuleName
override val jsRuntimeFactory: JSRuntimeFactory
get() = if (reactNativeHostWrapper.jsEngineResolutionAlgorithm == JSEngineResolutionAlgorithm.HERMES) {
HermesInstance()
} else {
JSCInstance()
}
override val reactPackages: List<ReactPackage>
get() = reactNativeHostWrapper.packages
override fun getReactNativeConfig(): ReactNativeConfig = reactNativeConfig
override fun handleInstanceException(error: Exception) {
val useDeveloperSupport = reactNativeHostWrapper.useDeveloperSupport
reactNativeHostWrapper.reactNativeHostHandlers.forEach { handler ->
handler.onReactInstanceException(useDeveloperSupport, error)
}
}
}
@OptIn(UnstableReactNativeAPI::class)
@JvmStatic
fun createFromReactNativeHost(
context: Context,
reactNativeHost: ReactNativeHost
): ReactHost {
require(reactNativeHost is ReactNativeHostWrapper) {
"You can call createFromReactNativeHost only with instances of ReactNativeHostWrapper"
}
if (reactHost == null) {
val useDeveloperSupport = reactNativeHost.useDeveloperSupport
val reactHostDelegate = ExpoReactHostDelegate(WeakReference(context), reactNativeHost)
val reactJsExceptionHandler = ReactJsExceptionHandler { _ -> }
val componentFactory = ComponentFactory()
DefaultComponentsRegistry.register(componentFactory)
reactNativeHost.reactNativeHostHandlers.forEach { handler ->
handler.onWillCreateReactInstance(useDeveloperSupport)
}
var reactHostImpl: ReactHostImpl
try {
// react-native 0.75.0 removed the ReactJsExceptionHandler parameter
val constructorWithoutHandler = ReactHostImpl::class.java.getConstructor(
Context::class.java,
ReactHostDelegate::class.java,
ComponentFactory::class.java,
Boolean::class.javaPrimitiveType,
Boolean::class.javaPrimitiveType
)
reactHostImpl = constructorWithoutHandler.newInstance(
context,
reactHostDelegate,
componentFactory,
true,
useDeveloperSupport
)
} catch (e: NoSuchMethodException) {
val constructorWithHandler = ReactHostImpl::class.java.getConstructor(
Context::class.java,
ReactHostDelegate::class.java,
ComponentFactory::class.java,
Boolean::class.javaPrimitiveType,
ReactJsExceptionHandler::class.java,
Boolean::class.javaPrimitiveType
)
reactHostImpl = constructorWithHandler.newInstance(
context,
reactHostDelegate,
componentFactory,
true,
reactJsExceptionHandler,
useDeveloperSupport
)
}
reactHostImpl.apply {
jsEngineResolutionAlgorithm = reactNativeHost.jsEngineResolutionAlgorithm
}
reactNativeHost.reactNativeHostHandlers.forEach { handler ->
handler.onDidCreateDevSupportManager(reactHostImpl.devSupportManager)
}
reactHostImpl.addReactInstanceEventListener(object : ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
reactNativeHost.reactNativeHostHandlers.forEach { handler ->
handler.onDidCreateReactInstance(useDeveloperSupport, context)
}
}
})
reactHost = reactHostImpl
}
return reactHost as ReactHost
}
}

View File

@@ -0,0 +1,347 @@
package expo.modules
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.view.KeyEvent
import android.view.ViewGroup
import androidx.collection.ArrayMap
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.ReactDelegate
import com.facebook.react.ReactHost
import com.facebook.react.ReactInstanceEventListener
import com.facebook.react.ReactInstanceManager
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactRootView
import com.facebook.react.bridge.ReactContext
import com.facebook.react.config.ReactFeatureFlags
import com.facebook.react.modules.core.PermissionListener
import expo.modules.core.interfaces.ReactActivityLifecycleListener
import expo.modules.kotlin.Utils
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Modifier
class ReactActivityDelegateWrapper(
private val activity: ReactActivity,
private val isNewArchitectureEnabled: Boolean,
private var delegate: ReactActivityDelegate
) : ReactActivityDelegate(activity, null) {
constructor(activity: ReactActivity, delegate: ReactActivityDelegate) :
this(activity, false, delegate)
private val reactActivityLifecycleListeners = ExpoModulesPackage.packageList
.flatMap { it.createReactActivityLifecycleListeners(activity) }
private val reactActivityHandlers = ExpoModulesPackage.packageList
.flatMap { it.createReactActivityHandlers(activity) }
private val methodMap: ArrayMap<String, Method> = ArrayMap()
private val _reactNativeHost: ReactNativeHost by lazy {
invokeDelegateMethod("getReactNativeHost")
}
private val _reactHost: ReactHost? by lazy {
delegate.reactHost
}
/**
* When the app delay for `loadApp`, the ReactInstanceManager's lifecycle will be disrupted.
* This flag indicates we should emit `onResume` after `loadApp`.
*/
private var shouldEmitPendingResume = false
//region ReactActivityDelegate
override fun getLaunchOptions(): Bundle? {
return invokeDelegateMethod("getLaunchOptions")
}
override fun createRootView(): ReactRootView? {
return invokeDelegateMethod("createRootView")
}
override fun getReactDelegate(): ReactDelegate? {
return invokeDelegateMethod("getReactDelegate")
}
override fun getReactNativeHost(): ReactNativeHost {
return _reactNativeHost
}
override fun getReactHost(): ReactHost? {
return _reactHost
}
override fun getReactInstanceManager(): ReactInstanceManager {
return delegate.reactInstanceManager
}
override fun getMainComponentName(): String? {
return delegate.mainComponentName
}
override fun loadApp(appKey: String?) {
// Give modules a chance to wrap the ReactRootView in a container ViewGroup. If some module
// wants to do this, we override the functionality of `loadApp` and call `setContentView` with
// the new container view instead.
val rootViewContainer = reactActivityHandlers.asSequence()
.mapNotNull { it.createReactRootViewContainer(activity) }
.firstOrNull()
if (rootViewContainer != null) {
val mReactDelegate = ReactActivityDelegate::class.java.getDeclaredField("mReactDelegate")
mReactDelegate.isAccessible = true
val reactDelegate = mReactDelegate[delegate] as ReactDelegate
reactDelegate.loadApp(appKey)
val reactRootView = reactDelegate.reactRootView
(reactRootView?.parent as? ViewGroup)?.removeView(reactRootView)
rootViewContainer.addView(reactRootView, ViewGroup.LayoutParams.MATCH_PARENT)
activity.setContentView(rootViewContainer)
reactActivityLifecycleListeners.forEach { listener ->
listener.onContentChanged(activity)
}
return
}
val delayLoadAppHandler = reactActivityHandlers.asSequence()
.mapNotNull { it.getDelayLoadAppHandler(activity, reactNativeHost) }
.firstOrNull()
if (delayLoadAppHandler != null) {
shouldEmitPendingResume = true
delayLoadAppHandler.whenReady {
Utils.assertMainThread()
invokeDelegateMethod<Unit, String?>("loadApp", arrayOf(String::class.java), arrayOf(appKey))
reactActivityLifecycleListeners.forEach { listener ->
listener.onContentChanged(activity)
}
shouldEmitPendingResume = false
onResume()
}
return
}
invokeDelegateMethod<Unit, String?>("loadApp", arrayOf(String::class.java), arrayOf(appKey))
reactActivityLifecycleListeners.forEach { listener ->
listener.onContentChanged(activity)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
// Give handlers a chance as early as possible to replace the wrapped delegate object.
// If they do, we call the new wrapped delegate's `onCreate` instead of overriding it here.
val newDelegate = reactActivityHandlers.asSequence()
.mapNotNull { it.onDidCreateReactActivityDelegate(activity, this) }
.firstOrNull()
if (newDelegate != null && newDelegate != this) {
val mDelegateField = ReactActivity::class.java.getDeclaredField("mDelegate")
mDelegateField.isAccessible = true
val modifiers = Field::class.java.getDeclaredField("accessFlags")
modifiers.isAccessible = true
modifiers.setInt(mDelegateField, mDelegateField.modifiers and Modifier.FINAL.inv())
mDelegateField.set(activity, newDelegate)
delegate = newDelegate
invokeDelegateMethod<Unit, Bundle?>("onCreate", arrayOf(Bundle::class.java), arrayOf(savedInstanceState))
} else {
// Since we just wrap `ReactActivityDelegate` but not inherit it, in its `onCreate`,
// the calls to `createRootView()` or `getMainComponentName()` have no chances to be our wrapped methods.
// Instead we intercept `ReactActivityDelegate.onCreate` and replace the `mReactDelegate` with our version.
// That's not ideal but works.
val launchOptions = composeLaunchOptions()
val reactDelegate: ReactDelegate
if (ReactFeatureFlags.enableBridgelessArchitecture) {
reactDelegate = ReactDelegate(
plainActivity,
reactHost,
mainComponentName,
launchOptions
)
} else {
reactDelegate = object : ReactDelegate(
plainActivity,
reactNativeHost,
mainComponentName,
launchOptions
) {
override fun createRootView(): ReactRootView {
return this@ReactActivityDelegateWrapper.createRootView() ?: super.createRootView()
}
}
}
val mReactDelegate = ReactActivityDelegate::class.java.getDeclaredField("mReactDelegate")
mReactDelegate.isAccessible = true
mReactDelegate.set(delegate, reactDelegate)
if (mainComponentName != null) {
loadApp(mainComponentName)
}
}
reactActivityLifecycleListeners.forEach { listener ->
listener.onCreate(activity, savedInstanceState)
}
}
override fun onResume() {
if (shouldEmitPendingResume) {
return
}
invokeDelegateMethod<Unit>("onResume")
reactActivityLifecycleListeners.forEach { listener ->
listener.onResume(activity)
}
}
override fun onPause() {
// If app is stopped before delayed `loadApp`, we should cancel the pending resume
if (shouldEmitPendingResume) {
shouldEmitPendingResume = false
}
reactActivityLifecycleListeners.forEach { listener ->
listener.onPause(activity)
}
return invokeDelegateMethod("onPause")
}
override fun onDestroy() {
// If app is stopped before delayed `loadApp`, we should cancel the pending resume
if (shouldEmitPendingResume) {
shouldEmitPendingResume = false
}
reactActivityLifecycleListeners.forEach { listener ->
listener.onDestroy(activity)
}
return invokeDelegateMethod("onDestroy")
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
/**
* Workaround for a problem when results from [onActivityResult] are not properly delivered to modules.
* It happens when Android kills the [Activity] upon low memory scenario and recreates it later on.
*
* In [com.facebook.react.ReactInstanceManager.onActivityResult] you can see that if
* [com.facebook.react.bridge.ReactContext] is null then React would not broadcast the result to the modules
* and thus [expo.modules.kotlin.AppContext] would not be triggered properly.
*
* If [com.facebook.react.bridge.ReactContext] is not available when [onActivityResult] is called then
* let us wait for it and invoke [onActivityResult] once it's available.
*
* TODO (@bbarthec): fix it upstream?
*/
if (!ReactFeatureFlags.enableBridgelessArchitecture && delegate.reactInstanceManager.currentReactContext == null) {
val reactContextListener = object : ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
delegate.reactInstanceManager.removeReactInstanceEventListener(this)
delegate.onActivityResult(requestCode, resultCode, data)
}
}
return delegate.reactInstanceManager.addReactInstanceEventListener(reactContextListener)
}
delegate.onActivityResult(requestCode, resultCode, data)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
// if any of the handlers return true, intentionally consume the event instead of passing it
// through to the delegate
return reactActivityHandlers
.map { it.onKeyDown(keyCode, event) }
.fold(false) { accu, current -> accu || current } || delegate.onKeyDown(keyCode, event)
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
// if any of the handlers return true, intentionally consume the event instead of passing it
// through to the delegate
return reactActivityHandlers
.map { it.onKeyUp(keyCode, event) }
.fold(false) { accu, current -> accu || current } || delegate.onKeyUp(keyCode, event)
}
override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean {
// if any of the handlers return true, intentionally consume the event instead of passing it
// through to the delegate
return reactActivityHandlers
.map { it.onKeyLongPress(keyCode, event) }
.fold(false) { accu, current -> accu || current } || delegate.onKeyLongPress(keyCode, event)
}
override fun onBackPressed(): Boolean {
val listenerResult = reactActivityLifecycleListeners
.map(ReactActivityLifecycleListener::onBackPressed)
.fold(false) { accu, current -> accu || current }
val delegateResult = delegate.onBackPressed()
return listenerResult || delegateResult
}
override fun onNewIntent(intent: Intent?): Boolean {
val listenerResult = reactActivityLifecycleListeners
.map { it.onNewIntent(intent) }
.fold(false) { accu, current -> accu || current }
val delegateResult = delegate.onNewIntent(intent)
return listenerResult || delegateResult
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
delegate.onWindowFocusChanged(hasFocus)
}
override fun requestPermissions(permissions: Array<out String>?, requestCode: Int, listener: PermissionListener?) {
delegate.requestPermissions(permissions, requestCode, listener)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>?, grantResults: IntArray?) {
delegate.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun getContext(): Context {
return invokeDelegateMethod("getContext")
}
override fun getPlainActivity(): Activity {
return invokeDelegateMethod("getPlainActivity")
}
override fun isFabricEnabled(): Boolean {
return invokeDelegateMethod("isFabricEnabled")
}
override fun composeLaunchOptions(): Bundle? {
return invokeDelegateMethod("composeLaunchOptions")
}
override fun onConfigurationChanged(newConfig: Configuration?) {
delegate.onConfigurationChanged(newConfig)
}
//endregion
//region Internals
@Suppress("UNCHECKED_CAST")
private fun <T> invokeDelegateMethod(name: String): T {
var method = methodMap[name]
if (method == null) {
method = ReactActivityDelegate::class.java.getDeclaredMethod(name)
method.isAccessible = true
methodMap[name] = method
}
return method!!.invoke(delegate) as T
}
@Suppress("UNCHECKED_CAST")
private fun <T, A> invokeDelegateMethod(
name: String,
argTypes: Array<Class<*>>,
args: Array<A>
): T {
var method = methodMap[name]
if (method == null) {
method = ReactActivityDelegate::class.java.getDeclaredMethod(name, *argTypes)
method.isAccessible = true
methodMap[name] = method
}
return method!!.invoke(delegate, *args) as T
}
//endregion
}

View File

@@ -0,0 +1,106 @@
package expo.modules
import android.app.Application
import androidx.collection.ArrayMap
import com.facebook.react.ReactInstanceEventListener
import com.facebook.react.ReactInstanceManager
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.JavaScriptExecutorFactory
import com.facebook.react.bridge.ReactContext
import java.lang.reflect.Method
open class ReactNativeHostWrapperBase(
application: Application,
protected val host: ReactNativeHost
) : ReactNativeHost(application) {
// TODO: Inherit from DefaultReactNativeHost when we drop SDK 49 support
internal val reactNativeHostHandlers = ExpoModulesPackage.packageList
.flatMap { it.createReactNativeHostHandlers(application) }
private val methodMap: ArrayMap<String, Method> = ArrayMap()
override fun createReactInstanceManager(): ReactInstanceManager {
val developerSupport = useDeveloperSupport
reactNativeHostHandlers.forEach { handler ->
handler.onWillCreateReactInstance(developerSupport)
}
val result = super.createReactInstanceManager()
reactNativeHostHandlers.forEach { handler ->
handler.onDidCreateDevSupportManager(result.devSupportManager)
}
result.addReactInstanceEventListener(object : ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
reactNativeHostHandlers.forEach { handler ->
handler.onDidCreateReactInstance(developerSupport, context)
}
}
})
injectHostReactInstanceManager(result)
return result
}
override fun getJavaScriptExecutorFactory(): JavaScriptExecutorFactory? {
return reactNativeHostHandlers.asSequence()
.mapNotNull { it.javaScriptExecutorFactory }
.firstOrNull() ?: invokeDelegateMethod("getJavaScriptExecutorFactory")
}
public override fun getJSMainModuleName(): String {
return invokeDelegateMethod("getJSMainModuleName")
}
public override fun getJSBundleFile(): String? {
return reactNativeHostHandlers.asSequence()
.mapNotNull { it.getJSBundleFile(useDeveloperSupport) }
.firstOrNull() ?: invokeDelegateMethod<String?>("getJSBundleFile")
}
public override fun getBundleAssetName(): String? {
return reactNativeHostHandlers.asSequence()
.mapNotNull { it.getBundleAssetName(useDeveloperSupport) }
.firstOrNull() ?: invokeDelegateMethod<String?>("getBundleAssetName")
}
override fun getUseDeveloperSupport(): Boolean {
return reactNativeHostHandlers.asSequence()
.mapNotNull { it.useDeveloperSupport }
.firstOrNull() ?: host.useDeveloperSupport
}
public override fun getPackages(): MutableList<ReactPackage> {
return invokeDelegateMethod("getPackages")
}
//endregion
//region Internals
@Suppress("UNCHECKED_CAST")
internal fun <T> invokeDelegateMethod(name: String): T {
var method = methodMap[name]
if (method == null) {
method = ReactNativeHost::class.java.getDeclaredMethod(name)
method.isAccessible = true
methodMap[name] = method
}
return method!!.invoke(host) as T
}
/**
* Inject the @{ReactInstanceManager} from the wrapper to the wrapped host.
* In case the wrapped host to call `getReactInstanceManager` inside its methods.
*/
private fun injectHostReactInstanceManager(reactInstanceManager: ReactInstanceManager) {
val mReactInstanceManagerField = ReactNativeHost::class.java.getDeclaredField("mReactInstanceManager")
mReactInstanceManagerField.isAccessible = true
mReactInstanceManagerField.set(host, reactInstanceManager)
}
//endregion
}

View File

@@ -0,0 +1,56 @@
package expo.modules
import android.app.Application
import android.content.Context
import com.facebook.react.JSEngineResolutionAlgorithm
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackageTurboModuleManagerDelegate
import com.facebook.react.bridge.UIManagerProvider
import com.facebook.react.common.SurfaceDelegateFactory
import com.facebook.react.devsupport.DevSupportManagerFactory
import com.facebook.react.devsupport.interfaces.RedBoxHandler
class ReactNativeHostWrapper(
application: Application,
host: ReactNativeHost
) : ReactNativeHostWrapperBase(application, host) {
override fun getDevSupportManagerFactory(): DevSupportManagerFactory? {
return reactNativeHostHandlers
.asSequence()
.mapNotNull { it.devSupportManagerFactory }
.firstOrNull() as DevSupportManagerFactory?
?: invokeDelegateMethod("getDevSupportManagerFactory")
}
override fun getReactPackageTurboModuleManagerDelegateBuilder(): ReactPackageTurboModuleManagerDelegate.Builder? {
return invokeDelegateMethod("getReactPackageTurboModuleManagerDelegateBuilder")
}
override fun getUIManagerProvider(): UIManagerProvider? {
return invokeDelegateMethod("getUIManagerProvider")
}
public override fun getJSEngineResolutionAlgorithm(): JSEngineResolutionAlgorithm? {
return invokeDelegateMethod("getJSEngineResolutionAlgorithm")
}
override fun getShouldRequireActivity(): Boolean {
return host.shouldRequireActivity
}
override fun getSurfaceDelegateFactory(): SurfaceDelegateFactory {
return host.surfaceDelegateFactory
}
override fun getRedBoxHandler(): RedBoxHandler? {
return invokeDelegateMethod("getRedBoxHandler")
}
companion object {
@JvmStatic
fun createReactHost(context: Context, reactNativeHost: ReactNativeHost): ReactHost {
return ExpoReactHostFactory.createFromReactNativeHost(context, reactNativeHost)
}
}
}

View File

@@ -0,0 +1,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<!-- Fix unit test manifestMerger build error from expo -> autolinked expo-app-auth -> net.openid.appauth -->
<activity
android:name="net.openid.appauth.RedirectUriReceiverActivity"
android:exported="true" tools:node="replace">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="test" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,109 @@
package expo.modules
import android.content.Context
import android.content.Intent
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.google.common.truth.Truth.assertThat
import expo.modules.core.interfaces.Package
import expo.modules.core.interfaces.ReactActivityHandler
import expo.modules.core.interfaces.ReactActivityLifecycleListener
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkAll
import io.mockk.verify
import org.junit.After
import org.junit.Before
import org.junit.Test
internal class ReactActivityDelegateWrapperTest {
lateinit var mockPackage0: MockPackage
lateinit var mockPackage1: MockPackage
@RelaxedMockK
lateinit var activity: ReactActivity
@RelaxedMockK
lateinit var delegate: ReactActivityDelegate
@Before
fun setUp() {
mockPackage0 = MockPackage()
mockPackage1 = MockPackage()
MockKAnnotations.init(this)
mockkObject(ExpoModulesPackage.Companion)
every { ExpoModulesPackage.Companion.packageList } returns listOf(mockPackage0, mockPackage1)
}
@After
fun tearDown() {
unmockkAll()
}
@Test
fun `onBackPressed should call each handler's callback just once`() {
val delegateWrapper = ReactActivityDelegateWrapper(activity, delegate)
every { mockPackage0.reactActivityLifecycleListener.onBackPressed() } returns true
delegateWrapper.onBackPressed()
verify(exactly = 1) { mockPackage0.reactActivityLifecycleListener.onBackPressed() }
verify(exactly = 1) { mockPackage1.reactActivityLifecycleListener.onBackPressed() }
verify(exactly = 1) { delegate.onBackPressed() }
}
@Test
fun `onBackPressed should return true if someone returns true`() {
val delegateWrapper = ReactActivityDelegateWrapper(activity, delegate)
every { mockPackage0.reactActivityLifecycleListener.onBackPressed() } returns false
every { mockPackage1.reactActivityLifecycleListener.onBackPressed() } returns true
every { delegate.onBackPressed() } returns false
val result = delegateWrapper.onBackPressed()
assertThat(result).isTrue()
}
@Test
fun `onNewIntent should call each handler's callback just once`() {
val intent = mockk<Intent>()
val delegateWrapper = ReactActivityDelegateWrapper(activity, delegate)
every { mockPackage0.reactActivityLifecycleListener.onNewIntent(intent) } returns false
every { mockPackage1.reactActivityLifecycleListener.onNewIntent(intent) } returns true
every { delegate.onNewIntent(intent) } returns false
delegateWrapper.onNewIntent(intent)
verify(exactly = 1) { mockPackage0.reactActivityLifecycleListener.onNewIntent(any()) }
verify(exactly = 1) { mockPackage1.reactActivityLifecycleListener.onNewIntent(any()) }
verify(exactly = 1) { delegate.onNewIntent(any()) }
}
@Test
fun `onNewIntent should return true if someone returns true`() {
val intent = mockk<Intent>()
val delegateWrapper = ReactActivityDelegateWrapper(activity, delegate)
every { mockPackage0.reactActivityLifecycleListener.onNewIntent(intent) } returns false
every { mockPackage1.reactActivityLifecycleListener.onNewIntent(intent) } returns true
every { delegate.onNewIntent(intent) } returns false
val result = delegateWrapper.onNewIntent(intent)
assertThat(result).isTrue()
}
}
internal class MockPackage : Package {
val reactActivityLifecycleListener = mockk<ReactActivityLifecycleListener>(relaxed = true)
val reactActivityHandler = mockk<ReactActivityHandler>(relaxed = true)
override fun createReactActivityLifecycleListeners(activityContext: Context?): List<ReactActivityLifecycleListener> {
return listOf(reactActivityLifecycleListener)
}
override fun createReactActivityHandlers(activityContext: Context?): List<ReactActivityHandler> {
return listOf(reactActivityHandler)
}
}

5
smart-app-city/frontend/node_modules/expo/bin/cli generated vendored Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env node
// Execute the CLI using node module resolution, instead of creating a new process.
// This avoids resolving issues with pnpm and yarn 2+ package managers.
require('@expo/cli');

View File

@@ -0,0 +1,6 @@
import './Expo.fx';
export { disableErrorHandling } from './errors/ExpoErrorManager';
export { default as registerRootComponent } from './launch/registerRootComponent';
export { isRunningInExpoGo, getExpoGoProjectConfig } from './environment/ExpoGo';
export { requireNativeModule, requireOptionalNativeModule, reloadAppAsync, } from 'expo-modules-core';
//# sourceMappingURL=Expo.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Expo.d.ts","sourceRoot":"","sources":["../src/Expo.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAC;AAEnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAC3B,cAAc,GACf,MAAM,mBAAmB,CAAC"}

View File

@@ -0,0 +1,3 @@
import './winter';
import 'expo-asset';
//# sourceMappingURL=Expo.fx.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Expo.fx.d.ts","sourceRoot":"","sources":["../src/Expo.fx.tsx"],"names":[],"mappings":"AACA,OAAO,UAAU,CAAC;AAClB,OAAO,YAAY,CAAC"}

View File

@@ -0,0 +1,22 @@
// load expo-asset immediately to set a custom `source` transformer in React Native
import './winter';
import 'expo-asset';
import * as Font from 'expo-font';
import { AppRegistry, StyleSheet } from 'react-native';
import { isRunningInExpoGo } from './environment/ExpoGo';
import { AppEntryNotFound } from './errors/AppEntryNotFound';
import { createErrorHandler } from './errors/ExpoErrorManager';
// If expo-font is installed and the style preprocessor is available, use it to parse fonts.
if (StyleSheet.setStyleAttributePreprocessor) {
StyleSheet.setStyleAttributePreprocessor('fontFamily', Font.processFontFamily);
}
if (isRunningInExpoGo()) {
// set up some improvements to commonly logged error messages stemming from react-native
const globalHandler = ErrorUtils.getGlobalHandler();
ErrorUtils.setGlobalHandler(createErrorHandler(globalHandler));
}
if (process.env.NODE_ENV !== 'production') {
// Register a default component and expect `registerRootComponent` to be called later and update it.
AppRegistry.registerComponent('main', () => AppEntryNotFound);
}
//# sourceMappingURL=Expo.fx.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Expo.fx.js","sourceRoot":"","sources":["../src/Expo.fx.tsx"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,OAAO,UAAU,CAAC;AAClB,OAAO,YAAY,CAAC;AAEpB,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,4FAA4F;AAC5F,IAAI,UAAU,CAAC,6BAA6B,EAAE;IAC5C,UAAU,CAAC,6BAA6B,CAAC,YAAY,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;CAChF;AAED,IAAI,iBAAiB,EAAE,EAAE;IACvB,wFAAwF;IACxF,MAAM,aAAa,GAAG,UAAU,CAAC,gBAAgB,EAAE,CAAC;IACpD,UAAU,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC;CAChE;AAED,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE;IACzC,oGAAoG;IACpG,WAAW,CAAC,iBAAiB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,CAAC;CAC/D","sourcesContent":["// load expo-asset immediately to set a custom `source` transformer in React Native\nimport './winter';\nimport 'expo-asset';\n\nimport * as Font from 'expo-font';\nimport { AppRegistry, StyleSheet } from 'react-native';\n\nimport { isRunningInExpoGo } from './environment/ExpoGo';\nimport { AppEntryNotFound } from './errors/AppEntryNotFound';\nimport { createErrorHandler } from './errors/ExpoErrorManager';\n\n// If expo-font is installed and the style preprocessor is available, use it to parse fonts.\nif (StyleSheet.setStyleAttributePreprocessor) {\n StyleSheet.setStyleAttributePreprocessor('fontFamily', Font.processFontFamily);\n}\n\nif (isRunningInExpoGo()) {\n // set up some improvements to commonly logged error messages stemming from react-native\n const globalHandler = ErrorUtils.getGlobalHandler();\n ErrorUtils.setGlobalHandler(createErrorHandler(globalHandler));\n}\n\nif (process.env.NODE_ENV !== 'production') {\n // Register a default component and expect `registerRootComponent` to be called later and update it.\n AppRegistry.registerComponent('main', () => AppEntryNotFound);\n}\n"]}

View File

@@ -0,0 +1 @@
//# sourceMappingURL=Expo.fx.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Expo.fx.web.d.ts","sourceRoot":"","sources":["../src/Expo.fx.web.tsx"],"names":[],"mappings":""}

View File

@@ -0,0 +1,16 @@
// When users dangerously import a file inside of react-native, it breaks the web alias.
// This is one of the most common, and cryptic web errors that users encounter.
// This conditional side-effect provides a more helpful error message for debugging.
// Use a wrapper `__DEV__` to remove this entire block in production.
if (__DEV__) {
if (
// Skip mocking if someone is shimming this value out.
!('__fbBatchedBridgeConfig' in global)) {
Object.defineProperty(global, '__fbBatchedBridgeConfig', {
get() {
throw new Error("Your web project is importing a module from 'react-native' instead of 'react-native-web'. Learn more: https://expo.fyi/fb-batched-bridge-config-web");
},
});
}
}
//# sourceMappingURL=Expo.fx.web.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Expo.fx.web.js","sourceRoot":"","sources":["../src/Expo.fx.web.tsx"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,+EAA+E;AAC/E,oFAAoF;AACpF,qEAAqE;AACrE,IAAI,OAAO,EAAE;IACX;IACE,sDAAsD;IACtD,CAAC,CAAC,yBAAyB,IAAI,MAAM,CAAC,EACtC;QACA,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,yBAAyB,EAAE;YACvD,GAAG;gBACD,MAAM,IAAI,KAAK,CACb,qJAAqJ,CACtJ,CAAC;YACJ,CAAC;SACF,CAAC,CAAC;KACJ;CACF","sourcesContent":["// When users dangerously import a file inside of react-native, it breaks the web alias.\n// This is one of the most common, and cryptic web errors that users encounter.\n// This conditional side-effect provides a more helpful error message for debugging.\n// Use a wrapper `__DEV__` to remove this entire block in production.\nif (__DEV__) {\n if (\n // Skip mocking if someone is shimming this value out.\n !('__fbBatchedBridgeConfig' in global)\n ) {\n Object.defineProperty(global, '__fbBatchedBridgeConfig', {\n get() {\n throw new Error(\n \"Your web project is importing a module from 'react-native' instead of 'react-native-web'. Learn more: https://expo.fyi/fb-batched-bridge-config-web\"\n );\n },\n });\n }\n}\n"]}

View File

@@ -0,0 +1,6 @@
import './Expo.fx';
export { disableErrorHandling } from './errors/ExpoErrorManager';
export { default as registerRootComponent } from './launch/registerRootComponent';
export { isRunningInExpoGo, getExpoGoProjectConfig } from './environment/ExpoGo';
export { requireNativeModule, requireOptionalNativeModule, reloadAppAsync, } from 'expo-modules-core';
//# sourceMappingURL=Expo.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Expo.js","sourceRoot":"","sources":["../src/Expo.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAC;AAEnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAC3B,cAAc,GACf,MAAM,mBAAmB,CAAC","sourcesContent":["import './Expo.fx';\n\nexport { disableErrorHandling } from './errors/ExpoErrorManager';\nexport { default as registerRootComponent } from './launch/registerRootComponent';\nexport { isRunningInExpoGo, getExpoGoProjectConfig } from './environment/ExpoGo';\nexport {\n requireNativeModule,\n requireOptionalNativeModule,\n reloadAppAsync,\n} from 'expo-modules-core';\n"]}

View File

@@ -0,0 +1,67 @@
import { EventEmitter, EventSubscription } from 'fbemitter';
import { WebSocketBackingStore } from './WebSocketBackingStore';
import type { ConnectionInfo, DevToolsPluginClientOptions } from './devtools.types';
export declare const MESSAGE_PROTOCOL_VERSION = 2;
export declare const DevToolsPluginMethod = "Expo:DevToolsPlugin";
/**
* This client is for the Expo DevTools Plugins to communicate between the app and the DevTools webpage hosted in a browser.
* All the code should be both compatible with browsers and React Native.
*/
export declare abstract class DevToolsPluginClient {
readonly connectionInfo: ConnectionInfo;
private readonly options?;
protected eventEmitter: EventEmitter;
private static defaultWSStore;
private readonly wsStore;
protected isClosed: boolean;
protected retries: number;
private readonly useTransportationNext;
private readonly messageFramePacker;
constructor(connectionInfo: ConnectionInfo, options?: DevToolsPluginClientOptions | undefined);
/**
* Initialize the connection.
* @hidden
*/
initAsync(): Promise<void>;
/**
* Close the connection.
*/
closeAsync(): Promise<void>;
/**
* Send a message to the other end of DevTools.
* @param method A method name.
* @param params any extra payload.
*/
sendMessage(method: string, params: any): void;
private sendMessageImplLegacy;
private sendMessageImplTransportationNext;
/**
* Subscribe to a message from the other end of DevTools.
* @param method Subscribe to a message with a method name.
* @param listener Listener to be called when a message is received.
*/
addMessageListener(method: string, listener: (params: any) => void): EventSubscription;
/**
* Subscribe to a message from the other end of DevTools just once.
* @param method Subscribe to a message with a method name.
* @param listener Listener to be called when a message is received.
*/
addMessageListenerOnce(method: string, listener: (params: any) => void): void;
/**
* Returns whether the client is connected to the server.
*/
isConnected(): boolean;
/**
* The method to create the WebSocket connection.
*/
protected connectAsync(): Promise<WebSocket>;
protected handleMessage: (event: WebSocketMessageEvent) => void;
private handleMessageImplLegacy;
private handleMessageImplTransportationNext;
/**
* Get the WebSocket backing store. Exposed for testing.
* @hidden
*/
getWebSocketBackingStore(): WebSocketBackingStore;
}
//# sourceMappingURL=DevToolsPluginClient.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevToolsPluginClient.d.ts","sourceRoot":"","sources":["../../src/devtools/DevToolsPluginClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAGhE,OAAO,KAAK,EAAE,cAAc,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAIpF,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,eAAO,MAAM,oBAAoB,wBAAwB,CAAC;AAO1D;;;GAGG;AACH,8BAAsB,oBAAoB;aAYtB,cAAc,EAAE,cAAc;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAZ3B,SAAS,CAAC,YAAY,EAAE,YAAY,CAAsB;IAE1D,OAAO,CAAC,MAAM,CAAC,cAAc,CAAsD;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8D;IAEtF,SAAS,CAAC,QAAQ,UAAS;IAC3B,SAAS,CAAC,OAAO,SAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;IAChD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA0D;gBAG3E,cAAc,EAAE,cAAc,EAC7B,OAAO,CAAC,yCAA6B;IAOxD;;;OAGG;IACU,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAQvC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAWxC;;;;OAIG;IACI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG;IAY9C,OAAO,CAAC,qBAAqB;YAaf,iCAAiC;IAa/C;;;;OAIG;IACI,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,iBAAiB;IAI7F;;;;OAIG;IACI,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIpF;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAyB5C,SAAS,CAAC,aAAa,UAAW,qBAAqB,UAMrD;IAEF,OAAO,CAAC,uBAAuB,CAiB7B;IAEF,OAAO,CAAC,mCAAmC,CAmBzC;IAEF;;;OAGG;IACI,wBAAwB,IAAI,qBAAqB;CAGzD"}

View File

@@ -0,0 +1,201 @@
import { EventEmitter } from 'fbemitter';
import { MessageFramePacker } from './MessageFramePacker';
import { WebSocketBackingStore } from './WebSocketBackingStore';
import { WebSocketWithReconnect } from './WebSocketWithReconnect';
import { blobToArrayBufferAsync } from './blobUtils';
import * as logger from './logger';
// This version should be synced with the one in the **createMessageSocketEndpoint.ts** in @react-native-community/cli-server-api
export const MESSAGE_PROTOCOL_VERSION = 2;
export const DevToolsPluginMethod = 'Expo:DevToolsPlugin';
/**
* This client is for the Expo DevTools Plugins to communicate between the app and the DevTools webpage hosted in a browser.
* All the code should be both compatible with browsers and React Native.
*/
export class DevToolsPluginClient {
connectionInfo;
options;
eventEmitter = new EventEmitter();
static defaultWSStore = new WebSocketBackingStore();
wsStore = DevToolsPluginClient.defaultWSStore;
isClosed = false;
retries = 0;
useTransportationNext;
messageFramePacker;
constructor(connectionInfo, options) {
this.connectionInfo = connectionInfo;
this.options = options;
this.wsStore = connectionInfo.wsStore || DevToolsPluginClient.defaultWSStore;
this.useTransportationNext = options?.useTransportationNext ?? false;
this.messageFramePacker = this.useTransportationNext ? new MessageFramePacker() : null;
}
/**
* Initialize the connection.
* @hidden
*/
async initAsync() {
if (this.wsStore.ws == null) {
this.wsStore.ws = await this.connectAsync();
}
this.wsStore.refCount += 1;
this.wsStore.ws.addEventListener('message', this.handleMessage);
}
/**
* Close the connection.
*/
async closeAsync() {
this.isClosed = true;
this.wsStore.ws?.removeEventListener('message', this.handleMessage);
this.wsStore.refCount -= 1;
if (this.wsStore.refCount < 1) {
this.wsStore.ws?.close();
this.wsStore.ws = null;
}
this.eventEmitter.removeAllListeners();
}
/**
* Send a message to the other end of DevTools.
* @param method A method name.
* @param params any extra payload.
*/
sendMessage(method, params) {
if (this.wsStore.ws?.readyState === WebSocket.CLOSED) {
logger.warn('Unable to send message in a disconnected state.');
return;
}
if (this.useTransportationNext) {
this.sendMessageImplTransportationNext(method, params);
}
else {
this.sendMessageImplLegacy(method, params);
}
}
sendMessageImplLegacy(method, params) {
const payload = {
version: MESSAGE_PROTOCOL_VERSION,
pluginName: this.connectionInfo.pluginName,
method: DevToolsPluginMethod,
params: {
method,
params,
},
};
this.wsStore.ws?.send(JSON.stringify(payload));
}
async sendMessageImplTransportationNext(method, params) {
if (this.messageFramePacker == null) {
logger.warn('MessageFramePacker is not initialized');
return;
}
const messageKey = {
pluginName: this.connectionInfo.pluginName,
method,
};
const packedData = await this.messageFramePacker.pack({ messageKey, payload: params });
this.wsStore.ws?.send(packedData);
}
/**
* Subscribe to a message from the other end of DevTools.
* @param method Subscribe to a message with a method name.
* @param listener Listener to be called when a message is received.
*/
addMessageListener(method, listener) {
return this.eventEmitter.addListener(method, listener);
}
/**
* Subscribe to a message from the other end of DevTools just once.
* @param method Subscribe to a message with a method name.
* @param listener Listener to be called when a message is received.
*/
addMessageListenerOnce(method, listener) {
this.eventEmitter.once(method, listener);
}
/**
* Returns whether the client is connected to the server.
*/
isConnected() {
return this.wsStore.ws?.readyState === WebSocket.OPEN;
}
/**
* The method to create the WebSocket connection.
*/
connectAsync() {
return new Promise((resolve, reject) => {
const endpoint = this.useTransportationNext ? 'expo-dev-plugins/broadcast' : 'message';
const ws = new WebSocketWithReconnect(`ws://${this.connectionInfo.devServer}/${endpoint}`, {
binaryType: this.options?.websocketBinaryType,
onError: (e) => {
if (e instanceof Error) {
console.warn(`Error happened from the WebSocket connection: ${e.message}\n${e.stack}`);
}
else {
console.warn(`Error happened from the WebSocket connection: ${JSON.stringify(e)}`);
}
},
});
ws.addEventListener('open', () => {
resolve(ws);
});
ws.addEventListener('error', (e) => {
reject(e);
});
ws.addEventListener('close', (e) => {
logger.info('WebSocket closed', e.code, e.reason);
});
});
}
handleMessage = (event) => {
if (this.useTransportationNext) {
this.handleMessageImplTransportationNext(event);
}
else {
this.handleMessageImplLegacy(event);
}
};
handleMessageImplLegacy = (event) => {
let payload;
try {
payload = JSON.parse(event.data);
}
catch (e) {
logger.info('Failed to parse JSON', e);
return;
}
if (payload.version !== MESSAGE_PROTOCOL_VERSION || payload.method !== DevToolsPluginMethod) {
return;
}
if (payload.pluginName && payload.pluginName !== this.connectionInfo.pluginName) {
return;
}
this.eventEmitter.emit(payload.params.method, payload.params.params);
};
handleMessageImplTransportationNext = async (event) => {
if (this.messageFramePacker == null) {
logger.warn('MessageFramePacker is not initialized');
return;
}
let buffer;
if (event.data instanceof ArrayBuffer) {
buffer = event.data;
}
else if (event.data instanceof Blob) {
buffer = await blobToArrayBufferAsync(event.data);
}
else {
logger.warn('Unsupported received data type in handleMessageImplTransportationNext');
return;
}
const { messageKey, payload } = await this.messageFramePacker.unpack(buffer);
if (messageKey.pluginName && messageKey.pluginName !== this.connectionInfo.pluginName) {
return;
}
this.eventEmitter.emit(messageKey.method, payload);
};
/**
* Get the WebSocket backing store. Exposed for testing.
* @hidden
*/
getWebSocketBackingStore() {
return this.wsStore;
}
}
//# sourceMappingURL=DevToolsPluginClient.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
import type { DevToolsPluginClient } from './DevToolsPluginClient';
import type { ConnectionInfo, DevToolsPluginClientOptions } from './devtools.types';
/**
* Factory of DevToolsPluginClient based on sender types.
* @hidden
*/
export declare function createDevToolsPluginClient(connectionInfo: ConnectionInfo, options?: DevToolsPluginClientOptions): Promise<DevToolsPluginClient>;
/**
* Public API to get the DevToolsPluginClient instance.
*/
export declare function getDevToolsPluginClientAsync(pluginName: string, options?: DevToolsPluginClientOptions): Promise<DevToolsPluginClient>;
/**
* Internal testing API to cleanup all DevToolsPluginClient instances.
*/
export declare function cleanupDevToolsPluginInstances(): void;
//# sourceMappingURL=DevToolsPluginClientFactory.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevToolsPluginClientFactory.d.ts","sourceRoot":"","sources":["../../src/devtools/DevToolsPluginClientFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAKpF;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,cAAc,EAAE,cAAc,EAC9B,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,oBAAoB,CAAC,CAS/B;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,oBAAoB,CAAC,CAyB/B;AAED;;GAEG;AACH,wBAAgB,8BAA8B,SAU7C"}

View File

@@ -0,0 +1,60 @@
import { DevToolsPluginClientImplApp } from './DevToolsPluginClientImplApp';
import { DevToolsPluginClientImplBrowser } from './DevToolsPluginClientImplBrowser';
import { getConnectionInfo } from './getConnectionInfo';
const instanceMap = {};
/**
* Factory of DevToolsPluginClient based on sender types.
* @hidden
*/
export async function createDevToolsPluginClient(connectionInfo, options) {
let client;
if (connectionInfo.sender === 'app') {
client = new DevToolsPluginClientImplApp(connectionInfo, options);
}
else {
client = new DevToolsPluginClientImplBrowser(connectionInfo, options);
}
await client.initAsync();
return client;
}
/**
* Public API to get the DevToolsPluginClient instance.
*/
export async function getDevToolsPluginClientAsync(pluginName, options) {
const connectionInfo = getConnectionInfo();
let instance = instanceMap[pluginName];
if (instance != null) {
if (instance instanceof Promise) {
return instance;
}
if (instance.isConnected() === false ||
instance.connectionInfo.devServer !== connectionInfo.devServer) {
await instance.closeAsync();
delete instanceMap[pluginName];
instance = null;
}
}
if (instance == null) {
const instancePromise = createDevToolsPluginClient({ ...connectionInfo, pluginName }, options);
instanceMap[pluginName] = instancePromise;
instance = await instancePromise;
instanceMap[pluginName] = instance;
}
return instance;
}
/**
* Internal testing API to cleanup all DevToolsPluginClient instances.
*/
export function cleanupDevToolsPluginInstances() {
for (const pluginName of Object.keys(instanceMap)) {
const instance = instanceMap[pluginName];
delete instanceMap[pluginName];
if (instance instanceof Promise) {
instance.then((instance) => instance.closeAsync());
}
else {
instance.closeAsync();
}
}
}
//# sourceMappingURL=DevToolsPluginClientFactory.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevToolsPluginClientFactory.js","sourceRoot":"","sources":["../../src/devtools/DevToolsPluginClientFactory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AAEpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,GAAyE,EAAE,CAAC;AAE7F;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,cAA8B,EAC9B,OAAqC;IAErC,IAAI,MAA4B,CAAC;IACjC,IAAI,cAAc,CAAC,MAAM,KAAK,KAAK,EAAE;QACnC,MAAM,GAAG,IAAI,2BAA2B,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;KACnE;SAAM;QACL,MAAM,GAAG,IAAI,+BAA+B,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;KACvE;IACD,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;IACzB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,UAAkB,EAClB,OAAqC;IAErC,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;IAE3C,IAAI,QAAQ,GACV,WAAW,CAAC,UAAU,CAAC,CAAC;IAC1B,IAAI,QAAQ,IAAI,IAAI,EAAE;QACpB,IAAI,QAAQ,YAAY,OAAO,EAAE;YAC/B,OAAO,QAAQ,CAAC;SACjB;QACD,IACE,QAAQ,CAAC,WAAW,EAAE,KAAK,KAAK;YAChC,QAAQ,CAAC,cAAc,CAAC,SAAS,KAAK,cAAc,CAAC,SAAS,EAC9D;YACA,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC;YAC5B,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC;YAC/B,QAAQ,GAAG,IAAI,CAAC;SACjB;KACF;IACD,IAAI,QAAQ,IAAI,IAAI,EAAE;QACpB,MAAM,eAAe,GAAG,0BAA0B,CAAC,EAAE,GAAG,cAAc,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/F,WAAW,CAAC,UAAU,CAAC,GAAG,eAAe,CAAC;QAC1C,QAAQ,GAAG,MAAM,eAAe,CAAC;QACjC,WAAW,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC;KACpC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,8BAA8B;IAC5C,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE;QACjD,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;QACzC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC;QAC/B,IAAI,QAAQ,YAAY,OAAO,EAAE;YAC/B,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;SACpD;aAAM;YACL,QAAQ,CAAC,UAAU,EAAE,CAAC;SACvB;KACF;AACH,CAAC","sourcesContent":["import type { DevToolsPluginClient } from './DevToolsPluginClient';\nimport { DevToolsPluginClientImplApp } from './DevToolsPluginClientImplApp';\nimport { DevToolsPluginClientImplBrowser } from './DevToolsPluginClientImplBrowser';\nimport type { ConnectionInfo, DevToolsPluginClientOptions } from './devtools.types';\nimport { getConnectionInfo } from './getConnectionInfo';\n\nconst instanceMap: Record<string, DevToolsPluginClient | Promise<DevToolsPluginClient>> = {};\n\n/**\n * Factory of DevToolsPluginClient based on sender types.\n * @hidden\n */\nexport async function createDevToolsPluginClient(\n connectionInfo: ConnectionInfo,\n options?: DevToolsPluginClientOptions\n): Promise<DevToolsPluginClient> {\n let client: DevToolsPluginClient;\n if (connectionInfo.sender === 'app') {\n client = new DevToolsPluginClientImplApp(connectionInfo, options);\n } else {\n client = new DevToolsPluginClientImplBrowser(connectionInfo, options);\n }\n await client.initAsync();\n return client;\n}\n\n/**\n * Public API to get the DevToolsPluginClient instance.\n */\nexport async function getDevToolsPluginClientAsync(\n pluginName: string,\n options?: DevToolsPluginClientOptions\n): Promise<DevToolsPluginClient> {\n const connectionInfo = getConnectionInfo();\n\n let instance: DevToolsPluginClient | Promise<DevToolsPluginClient> | null =\n instanceMap[pluginName];\n if (instance != null) {\n if (instance instanceof Promise) {\n return instance;\n }\n if (\n instance.isConnected() === false ||\n instance.connectionInfo.devServer !== connectionInfo.devServer\n ) {\n await instance.closeAsync();\n delete instanceMap[pluginName];\n instance = null;\n }\n }\n if (instance == null) {\n const instancePromise = createDevToolsPluginClient({ ...connectionInfo, pluginName }, options);\n instanceMap[pluginName] = instancePromise;\n instance = await instancePromise;\n instanceMap[pluginName] = instance;\n }\n return instance;\n}\n\n/**\n * Internal testing API to cleanup all DevToolsPluginClient instances.\n */\nexport function cleanupDevToolsPluginInstances() {\n for (const pluginName of Object.keys(instanceMap)) {\n const instance = instanceMap[pluginName];\n delete instanceMap[pluginName];\n if (instance instanceof Promise) {\n instance.then((instance) => instance.closeAsync());\n } else {\n instance.closeAsync();\n }\n }\n}\n"]}

View File

@@ -0,0 +1,14 @@
import { DevToolsPluginClient } from './DevToolsPluginClient';
/**
* The DevToolsPluginClient for the app -> browser communication.
*/
export declare class DevToolsPluginClientImplApp extends DevToolsPluginClient {
private browserClientMap;
/**
* Initialize the connection.
* @hidden
*/
initAsync(): Promise<void>;
private addHandshakeHandler;
}
//# sourceMappingURL=DevToolsPluginClientImplApp.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevToolsPluginClientImplApp.d.ts","sourceRoot":"","sources":["../../src/devtools/DevToolsPluginClientImplApp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAI9D;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,oBAAoB;IAEnE,OAAO,CAAC,gBAAgB,CAA8B;IAEtD;;;OAGG;IACY,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzC,OAAO,CAAC,mBAAmB;CAY5B"}

View File

@@ -0,0 +1,28 @@
import { DevToolsPluginClient } from './DevToolsPluginClient';
import * as logger from './logger';
/**
* The DevToolsPluginClient for the app -> browser communication.
*/
export class DevToolsPluginClientImplApp extends DevToolsPluginClient {
// Map of pluginName -> browserClientId
browserClientMap = {};
/**
* Initialize the connection.
* @hidden
*/
async initAsync() {
await super.initAsync();
this.addHandshakeHandler();
}
addHandshakeHandler() {
this.addMessageListener('handshake', (params) => {
const previousBrowserClientId = this.browserClientMap[params.pluginName];
if (previousBrowserClientId != null && previousBrowserClientId !== params.browserClientId) {
logger.info(`Terminate the previous browser client connection - previousBrowserClientId[${previousBrowserClientId}]`);
this.sendMessage('terminateBrowserClient', { browserClientId: previousBrowserClientId });
}
this.browserClientMap[params.pluginName] = params.browserClientId;
});
}
}
//# sourceMappingURL=DevToolsPluginClientImplApp.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevToolsPluginClientImplApp.js","sourceRoot":"","sources":["../../src/devtools/DevToolsPluginClientImplApp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AAEnC;;GAEG;AACH,MAAM,OAAO,2BAA4B,SAAQ,oBAAoB;IACnE,uCAAuC;IAC/B,gBAAgB,GAA2B,EAAE,CAAC;IAEtD;;;OAGG;IACM,KAAK,CAAC,SAAS;QACtB,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC;QACxB,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IAEO,mBAAmB;QACzB,IAAI,CAAC,kBAAkB,CAAC,WAAW,EAAE,CAAC,MAA8B,EAAE,EAAE;YACtE,MAAM,uBAAuB,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACzE,IAAI,uBAAuB,IAAI,IAAI,IAAI,uBAAuB,KAAK,MAAM,CAAC,eAAe,EAAE;gBACzF,MAAM,CAAC,IAAI,CACT,8EAA8E,uBAAuB,GAAG,CACzG,CAAC;gBACF,IAAI,CAAC,WAAW,CAAC,wBAAwB,EAAE,EAAE,eAAe,EAAE,uBAAuB,EAAE,CAAC,CAAC;aAC1F;YACD,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC;QACpE,CAAC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import { DevToolsPluginClient } from './DevToolsPluginClient';\nimport type { HandshakeMessageParams } from './devtools.types';\nimport * as logger from './logger';\n\n/**\n * The DevToolsPluginClient for the app -> browser communication.\n */\nexport class DevToolsPluginClientImplApp extends DevToolsPluginClient {\n // Map of pluginName -> browserClientId\n private browserClientMap: Record<string, string> = {};\n\n /**\n * Initialize the connection.\n * @hidden\n */\n override async initAsync(): Promise<void> {\n await super.initAsync();\n this.addHandshakeHandler();\n }\n\n private addHandshakeHandler() {\n this.addMessageListener('handshake', (params: HandshakeMessageParams) => {\n const previousBrowserClientId = this.browserClientMap[params.pluginName];\n if (previousBrowserClientId != null && previousBrowserClientId !== params.browserClientId) {\n logger.info(\n `Terminate the previous browser client connection - previousBrowserClientId[${previousBrowserClientId}]`\n );\n this.sendMessage('terminateBrowserClient', { browserClientId: previousBrowserClientId });\n }\n this.browserClientMap[params.pluginName] = params.browserClientId;\n });\n }\n}\n"]}

View File

@@ -0,0 +1,14 @@
import { DevToolsPluginClient } from './DevToolsPluginClient';
/**
* The DevToolsPluginClient for the browser -> app communication.
*/
export declare class DevToolsPluginClientImplBrowser extends DevToolsPluginClient {
private browserClientId;
/**
* Initialize the connection.
* @hidden
*/
initAsync(): Promise<void>;
private startHandshake;
}
//# sourceMappingURL=DevToolsPluginClientImplBrowser.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevToolsPluginClientImplBrowser.d.ts","sourceRoot":"","sources":["../../src/devtools/DevToolsPluginClientImplBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG9D;;GAEG;AACH,qBAAa,+BAAgC,SAAQ,oBAAoB;IACvE,OAAO,CAAC,eAAe,CAAiC;IAExD;;;OAGG;IACY,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzC,OAAO,CAAC,cAAc;CAavB"}

View File

@@ -0,0 +1,30 @@
import { DevToolsPluginClient } from './DevToolsPluginClient';
import * as logger from './logger';
/**
* The DevToolsPluginClient for the browser -> app communication.
*/
export class DevToolsPluginClientImplBrowser extends DevToolsPluginClient {
browserClientId = Date.now().toString();
/**
* Initialize the connection.
* @hidden
*/
async initAsync() {
await super.initAsync();
this.startHandshake();
}
startHandshake() {
this.addMessageListener('terminateBrowserClient', (params) => {
if (this.browserClientId !== params.browserClientId) {
return;
}
logger.info('Received terminateBrowserClient messages and terminate the current connection');
this.closeAsync();
});
this.sendMessage('handshake', {
browserClientId: this.browserClientId,
pluginName: this.connectionInfo.pluginName,
});
}
}
//# sourceMappingURL=DevToolsPluginClientImplBrowser.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevToolsPluginClientImplBrowser.js","sourceRoot":"","sources":["../../src/devtools/DevToolsPluginClientImplBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AAEnC;;GAEG;AACH,MAAM,OAAO,+BAAgC,SAAQ,oBAAoB;IAC/D,eAAe,GAAW,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAExD;;;OAGG;IACM,KAAK,CAAC,SAAS;QACtB,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC;QACxB,IAAI,CAAC,cAAc,EAAE,CAAC;IACxB,CAAC;IAEO,cAAc;QACpB,IAAI,CAAC,kBAAkB,CAAC,wBAAwB,EAAE,CAAC,MAAM,EAAE,EAAE;YAC3D,IAAI,IAAI,CAAC,eAAe,KAAK,MAAM,CAAC,eAAe,EAAE;gBACnD,OAAO;aACR;YACD,MAAM,CAAC,IAAI,CAAC,+EAA+E,CAAC,CAAC;YAC7F,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE;YAC5B,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU;SAC3C,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import { DevToolsPluginClient } from './DevToolsPluginClient';\nimport * as logger from './logger';\n\n/**\n * The DevToolsPluginClient for the browser -> app communication.\n */\nexport class DevToolsPluginClientImplBrowser extends DevToolsPluginClient {\n private browserClientId: string = Date.now().toString();\n\n /**\n * Initialize the connection.\n * @hidden\n */\n override async initAsync(): Promise<void> {\n await super.initAsync();\n this.startHandshake();\n }\n\n private startHandshake() {\n this.addMessageListener('terminateBrowserClient', (params) => {\n if (this.browserClientId !== params.browserClientId) {\n return;\n }\n logger.info('Received terminateBrowserClient messages and terminate the current connection');\n this.closeAsync();\n });\n this.sendMessage('handshake', {\n browserClientId: this.browserClientId,\n pluginName: this.connectionInfo.pluginName,\n });\n }\n}\n"]}

View File

@@ -0,0 +1,40 @@
/**
* A message frame packer that serializes a messageKey and a payload into a binary format.
*
* +------------------+-------------------+----------------------------+--------------------------+
* | 4 bytes (Uint32) | Variable length | 1 byte (Uint8) | Variable length |
* | MessageKeyLength | MessageKey (JSON) | PayloadTypeIndicator (enum)| Payload (binary data) |
* +------------------+-------------------+----------------------------+--------------------------+
*
* MessageFrame Format:
*
* 1. MessageKeyLength (4 bytes):
* - A 4-byte unsigned integer indicating the length of the MessageKey JSON string.
*
* 2. MessageKey (Variable length):
* - The JSON string representing the message key, encoded as UTF-8.
*
* 3. PayloadTypeIndicator (1 byte):
* - A single byte enum value representing the type of the payload (e.g., Uint8Array, String, Object, ArrayBuffer, Blob).
*
* 4. Payload (Variable length):
* - The actual payload data, which can vary in type and length depending on the PayloadType.
*
*/
type MessageKeyTypeBase = string | object;
type PayloadType = Uint8Array | string | number | null | undefined | object | ArrayBuffer | Blob;
interface MessageFrame<T extends MessageKeyTypeBase> {
messageKey: T;
payload?: PayloadType;
}
export declare class MessageFramePacker<T extends MessageKeyTypeBase> {
private textEncoder;
private textDecoder;
pack({ messageKey, payload }: MessageFrame<T>): Promise<Uint8Array>;
unpack(packedData: ArrayBuffer): Promise<MessageFrame<T>>;
private payloadToUint8Array;
private deserializePayload;
private static getPayloadTypeIndicator;
}
export {};
//# sourceMappingURL=MessageFramePacker.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MessageFramePacker.d.ts","sourceRoot":"","sources":["../../src/devtools/MessageFramePacker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,KAAK,kBAAkB,GAAG,MAAM,GAAG,MAAM,CAAC;AAC1C,KAAK,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;AAajG,UAAU,YAAY,CAAC,CAAC,SAAS,kBAAkB;IACjD,UAAU,EAAE,CAAC,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,qBAAa,kBAAkB,CAAC,CAAC,SAAS,kBAAkB;IAC1D,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,WAAW,CAAqB;IAE3B,IAAI,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IA2BnE,MAAM,CAAC,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAqBxD,mBAAmB;YAuBnB,kBAAkB;IAqChC,OAAO,CAAC,MAAM,CAAC,uBAAuB;CAqBvC"}

View File

@@ -0,0 +1,167 @@
/**
* A message frame packer that serializes a messageKey and a payload into a binary format.
*
* +------------------+-------------------+----------------------------+--------------------------+
* | 4 bytes (Uint32) | Variable length | 1 byte (Uint8) | Variable length |
* | MessageKeyLength | MessageKey (JSON) | PayloadTypeIndicator (enum)| Payload (binary data) |
* +------------------+-------------------+----------------------------+--------------------------+
*
* MessageFrame Format:
*
* 1. MessageKeyLength (4 bytes):
* - A 4-byte unsigned integer indicating the length of the MessageKey JSON string.
*
* 2. MessageKey (Variable length):
* - The JSON string representing the message key, encoded as UTF-8.
*
* 3. PayloadTypeIndicator (1 byte):
* - A single byte enum value representing the type of the payload (e.g., Uint8Array, String, Object, ArrayBuffer, Blob).
*
* 4. Payload (Variable length):
* - The actual payload data, which can vary in type and length depending on the PayloadType.
*
*/
import { blobToArrayBufferAsync } from './blobUtils';
var PayloadTypeIndicator;
(function (PayloadTypeIndicator) {
PayloadTypeIndicator[PayloadTypeIndicator["Uint8Array"] = 1] = "Uint8Array";
PayloadTypeIndicator[PayloadTypeIndicator["String"] = 2] = "String";
PayloadTypeIndicator[PayloadTypeIndicator["Number"] = 3] = "Number";
PayloadTypeIndicator[PayloadTypeIndicator["Null"] = 4] = "Null";
PayloadTypeIndicator[PayloadTypeIndicator["Undefined"] = 5] = "Undefined";
PayloadTypeIndicator[PayloadTypeIndicator["Object"] = 6] = "Object";
PayloadTypeIndicator[PayloadTypeIndicator["ArrayBuffer"] = 7] = "ArrayBuffer";
PayloadTypeIndicator[PayloadTypeIndicator["Blob"] = 8] = "Blob";
})(PayloadTypeIndicator || (PayloadTypeIndicator = {}));
export class MessageFramePacker {
textEncoder = new TextEncoder();
textDecoder = new TextDecoder();
async pack({ messageKey, payload }) {
const messageKeyString = JSON.stringify(messageKey);
const messageKeyBytes = this.textEncoder.encode(messageKeyString);
const messageKeyLength = messageKeyBytes.length;
const payloadBinary = await this.payloadToUint8Array(payload);
const totalLength = 4 + messageKeyLength + 1 + payloadBinary.byteLength;
const buffer = new ArrayBuffer(totalLength);
const packedArray = new Uint8Array(buffer);
// [0] messageKeyLength (4 bytes)
const messageKeyLengthView = new DataView(buffer, 0, 4);
messageKeyLengthView.setUint32(0, messageKeyLength, false);
// [1] messageKey (variable length)
packedArray.set(messageKeyBytes, 4);
// [2] payloadTypeIndicator (1 byte)
const payloadTypeView = new DataView(buffer, 4 + messageKeyLength, 1);
payloadTypeView.setUint8(0, MessageFramePacker.getPayloadTypeIndicator(payload));
// [3] payload (variable length)
packedArray.set(payloadBinary, 4 + messageKeyLength + 1);
return packedArray;
}
async unpack(packedData) {
// [0] messageKeyLength (4 bytes)
const messageKeyLengthView = new DataView(packedData, 0, 4);
const messageKeyLength = messageKeyLengthView.getUint32(0, false);
// [1] messageKey (variable length)
const messageKeyBytes = packedData.slice(4, 4 + messageKeyLength);
const messageKeyString = this.textDecoder.decode(messageKeyBytes);
const messageKey = JSON.parse(messageKeyString);
// [2] payloadTypeIndicator (1 byte)
const payloadTypeView = new DataView(packedData, 4 + messageKeyLength, 1);
const payloadType = payloadTypeView.getUint8(0);
// [3] payload (variable length)
const payloadBinary = packedData.slice(4 + messageKeyLength + 1);
const payload = await this.deserializePayload(payloadBinary, payloadType);
return { messageKey, payload };
}
async payloadToUint8Array(payload) {
if (payload instanceof Uint8Array) {
return payload;
}
else if (typeof payload === 'string') {
return this.textEncoder.encode(payload);
}
else if (typeof payload === 'number') {
const buffer = new ArrayBuffer(8);
const view = new DataView(buffer);
view.setFloat64(0, payload, false);
return new Uint8Array(buffer);
}
else if (payload === null) {
return new Uint8Array(0);
}
else if (payload === undefined) {
return new Uint8Array(0);
}
else if (payload instanceof ArrayBuffer) {
return new Uint8Array(payload);
}
else if (payload instanceof Blob) {
return new Uint8Array(await blobToArrayBufferAsync(payload));
}
else {
return this.textEncoder.encode(JSON.stringify(payload));
}
}
async deserializePayload(payloadBinary, payloadTypeIndicator) {
switch (payloadTypeIndicator) {
case PayloadTypeIndicator.Uint8Array: {
return new Uint8Array(payloadBinary);
}
case PayloadTypeIndicator.String: {
return this.textDecoder.decode(payloadBinary);
}
case PayloadTypeIndicator.Number: {
const view = new DataView(payloadBinary);
return view.getFloat64(0, false);
}
case PayloadTypeIndicator.Null: {
return null;
}
case PayloadTypeIndicator.Undefined: {
return undefined;
}
case PayloadTypeIndicator.Object: {
const jsonString = this.textDecoder.decode(payloadBinary);
return JSON.parse(jsonString);
}
case PayloadTypeIndicator.ArrayBuffer: {
return payloadBinary;
}
case PayloadTypeIndicator.Blob: {
// @ts-ignore: We only test ArrayBuffer -> Blob on Node.js runtime
return new Blob([payloadBinary]);
}
default:
throw new Error('Unsupported payload type');
}
}
static getPayloadTypeIndicator(payload) {
if (payload instanceof Uint8Array) {
return PayloadTypeIndicator.Uint8Array;
}
else if (typeof payload === 'string') {
return PayloadTypeIndicator.String;
}
else if (typeof payload === 'number') {
return PayloadTypeIndicator.Number;
}
else if (payload === null) {
return PayloadTypeIndicator.Null;
}
else if (payload === undefined) {
return PayloadTypeIndicator.Undefined;
}
else if (payload instanceof ArrayBuffer) {
return PayloadTypeIndicator.ArrayBuffer;
}
else if (payload instanceof Blob) {
return PayloadTypeIndicator.Blob;
}
else if (typeof payload === 'object') {
return PayloadTypeIndicator.Object;
}
else {
throw new Error('Unsupported payload type');
}
}
}
//# sourceMappingURL=MessageFramePacker.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
/**
* The backing store for the WebSocket connection and reference count.
* This is used for connection multiplexing.
*/
export declare class WebSocketBackingStore {
ws: WebSocket | null;
refCount: number;
constructor(ws?: WebSocket | null, refCount?: number);
}
//# sourceMappingURL=WebSocketBackingStore.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WebSocketBackingStore.d.ts","sourceRoot":"","sources":["../../src/devtools/WebSocketBackingStore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,qBAAqB;IAEvB,EAAE,EAAE,SAAS,GAAG,IAAI;IACpB,QAAQ,EAAE,MAAM;gBADhB,EAAE,GAAE,SAAS,GAAG,IAAW,EAC3B,QAAQ,GAAE,MAAU;CAE9B"}

View File

@@ -0,0 +1,13 @@
/**
* The backing store for the WebSocket connection and reference count.
* This is used for connection multiplexing.
*/
export class WebSocketBackingStore {
ws;
refCount;
constructor(ws = null, refCount = 0) {
this.ws = ws;
this.refCount = refCount;
}
}
//# sourceMappingURL=WebSocketBackingStore.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WebSocketBackingStore.js","sourceRoot":"","sources":["../../src/devtools/WebSocketBackingStore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,OAAO,qBAAqB;IAEvB;IACA;IAFT,YACS,KAAuB,IAAI,EAC3B,WAAmB,CAAC;QADpB,OAAE,GAAF,EAAE,CAAyB;QAC3B,aAAQ,GAAR,QAAQ,CAAY;IAC1B,CAAC;CACL","sourcesContent":["/**\n * The backing store for the WebSocket connection and reference count.\n * This is used for connection multiplexing.\n */\nexport class WebSocketBackingStore {\n constructor(\n public ws: WebSocket | null = null,\n public refCount: number = 0\n ) {}\n}\n"]}

View File

@@ -0,0 +1,81 @@
import type { DevToolsPluginClientOptions } from './devtools.types';
export interface Options {
/**
* Reconnect interval in milliseconds.
* @default 1500
*/
retriesInterval?: number;
/**
* The maximum number of retries.
* @default 200
*/
maxRetries?: number;
/**
* The timeout in milliseconds for the WebSocket connecting.
*/
connectTimeout?: number;
/**
* The error handler.
* @default throwing an error
*/
onError?: (error: Error) => void;
/**
* The callback to be called when the WebSocket is reconnected.
* @default no-op
*/
onReconnect?: (reason: string) => void;
/**
* The [`binaryType`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType).
*/
binaryType?: DevToolsPluginClientOptions['websocketBinaryType'];
}
export declare class WebSocketWithReconnect implements WebSocket {
readonly url: string;
private readonly retriesInterval;
private readonly maxRetries;
private readonly connectTimeout;
private readonly onError;
private readonly onReconnect;
private ws;
private retries;
private connectTimeoutHandle;
private isClosed;
private sendQueue;
private lastCloseEvent;
private readonly emitter;
private readonly eventSubscriptions;
private readonly wsBinaryType?;
constructor(url: string, options?: Options);
close(code?: number, reason?: string): void;
addEventListener(event: 'message', listener: (event: WebSocketMessageEvent) => void): void;
addEventListener(event: 'open', listener: () => void): void;
addEventListener(event: 'error', listener: (event: WebSocketErrorEvent) => void): void;
addEventListener(event: 'close', listener: (event: WebSocketCloseEvent) => void): void;
removeEventListener(event: string, listener: (event: any) => void): void;
private connect;
send(data: string | ArrayBufferView | Blob | ArrayBufferLike): void;
private handleOpen;
private handleMessage;
private handleError;
private handleClose;
private handleConnectTimeout;
private clearConnectTimeoutIfNeeded;
private reconnectIfNeeded;
private wsClose;
get readyState(): number;
readonly CONNECTING = 0;
readonly OPEN = 1;
readonly CLOSING = 2;
readonly CLOSED = 3;
get binaryType(): BinaryType;
get bufferedAmount(): number;
get extensions(): string;
get protocol(): string;
ping(): void;
dispatchEvent(event: Event): boolean;
set onclose(value: ((e: WebSocketCloseEvent) => any) | null);
set onerror(value: ((e: Event) => any) | null);
set onmessage(value: ((e: WebSocketMessageEvent) => any) | null);
set onopen(value: (() => any) | null);
}
//# sourceMappingURL=WebSocketWithReconnect.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"WebSocketWithReconnect.d.ts","sourceRoot":"","sources":["../../src/devtools/WebSocketWithReconnect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAEpE,MAAM,WAAW,OAAO;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAEjC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAEvC;;OAEG;IACH,UAAU,CAAC,EAAE,2BAA2B,CAAC,qBAAqB,CAAC,CAAC;CACjE;AAED,qBAAa,sBAAuB,YAAW,SAAS;aAmBpC,GAAG,EAAE,MAAM;IAlB7B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA2B;IAEvD,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,oBAAoB,CAA8C;IAC1E,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAA6D;IAC9E,OAAO,CAAC,cAAc,CAAqE;IAE3F,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA2B;IAC9D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAwB;gBAGpC,GAAG,EAAE,MAAM,EAC3B,OAAO,CAAC,EAAE,OAAO;IAgBZ,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAqBpC,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,GAAG,IAAI;IAC1F,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAC3D,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,IAAI;IACtF,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,GAAG,IAAI;IAKtF,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI;IAYxE,OAAO,CAAC,OAAO;IAiBR,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,GAAG,eAAe,GAAG,IAAI;IAqB1E,OAAO,CAAC,UAAU,CAUhB;IAEF,OAAO,CAAC,aAAa,CAEnB;IAEF,OAAO,CAAC,WAAW,CAIjB;IAEF,OAAO,CAAC,WAAW,CAQjB;IAEF,OAAO,CAAC,oBAAoB,CAE1B;IAEF,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,iBAAiB;IAsBzB,OAAO,CAAC,OAAO;IAWf,IAAW,UAAU,WAWpB;IAMD,SAAgB,UAAU,KAAK;IAC/B,SAAgB,IAAI,KAAK;IACzB,SAAgB,OAAO,KAAK;IAC5B,SAAgB,MAAM,KAAK;IAE3B,IAAW,UAAU,eAEpB;IAED,IAAW,cAAc,WAExB;IAED,IAAW,UAAU,WAEpB;IAED,IAAW,QAAQ,WAElB;IAEM,IAAI,IAAI,IAAI;IAIZ,aAAa,CAAC,KAAK,EAAE,KAAK;IAQjC,IAAW,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,mBAAmB,KAAK,GAAG,CAAC,GAAG,IAAI,EAEjE;IAED,IAAW,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,IAAI,EAEnD;IAED,IAAW,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,qBAAqB,KAAK,GAAG,CAAC,GAAG,IAAI,EAErE;IAED,IAAW,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,GAAG,IAAI,EAE1C;CAGF"}

View File

@@ -0,0 +1,208 @@
import { EventEmitter } from 'fbemitter';
export class WebSocketWithReconnect {
url;
retriesInterval;
maxRetries;
connectTimeout;
onError;
onReconnect;
ws = null;
retries = 0;
connectTimeoutHandle = null;
isClosed = false;
sendQueue = [];
lastCloseEvent = null;
emitter = new EventEmitter();
eventSubscriptions = [];
wsBinaryType;
constructor(url, options) {
this.url = url;
this.retriesInterval = options?.retriesInterval ?? 1500;
this.maxRetries = options?.maxRetries ?? 200;
this.connectTimeout = options?.connectTimeout ?? 5000;
this.onError =
options?.onError ??
((error) => {
throw error;
});
this.onReconnect = options?.onReconnect ?? (() => { });
this.wsBinaryType = options?.binaryType;
this.connect();
}
close(code, reason) {
this.clearConnectTimeoutIfNeeded();
this.emitter.emit('close', this.lastCloseEvent ?? {
code: code ?? 1000,
reason: reason ?? 'Explicit closing',
message: 'Explicit closing',
});
this.lastCloseEvent = null;
this.isClosed = true;
this.emitter.removeAllListeners();
this.sendQueue = [];
if (this.ws != null) {
const ws = this.ws;
this.ws = null;
this.wsClose(ws);
}
}
addEventListener(event, listener) {
this.eventSubscriptions.push(this.emitter.addListener(event, listener));
}
removeEventListener(event, listener) {
const index = this.eventSubscriptions.findIndex((subscription) => subscription.listener === listener);
if (index >= 0) {
this.eventSubscriptions[index].remove();
this.eventSubscriptions.splice(index, 1);
}
}
//#region Internals
connect() {
if (this.ws != null) {
return;
}
this.connectTimeoutHandle = setTimeout(this.handleConnectTimeout, this.connectTimeout);
this.ws = new WebSocket(this.url.toString());
if (this.wsBinaryType != null) {
this.ws.binaryType = this.wsBinaryType;
}
this.ws.addEventListener('message', this.handleMessage);
this.ws.addEventListener('open', this.handleOpen);
// @ts-ignore TypeScript expects (e: Event) => any, but we want (e: WebSocketErrorEvent) => any
this.ws.addEventListener('error', this.handleError);
this.ws.addEventListener('close', this.handleClose);
}
send(data) {
if (this.isClosed) {
this.onError(new Error('Unable to send data: WebSocket is closed'));
return;
}
if (this.retries >= this.maxRetries) {
this.onError(new Error(`Unable to send data: Exceeded max retries - retries[${this.retries}]`));
return;
}
const ws = this.ws;
if (ws != null && ws.readyState === WebSocket.OPEN) {
ws.send(data);
}
else {
this.sendQueue.push(data);
}
}
handleOpen = () => {
this.clearConnectTimeoutIfNeeded();
this.lastCloseEvent = null;
this.emitter.emit('open');
const sendQueue = this.sendQueue;
this.sendQueue = [];
for (const data of sendQueue) {
this.send(data);
}
};
handleMessage = (event) => {
this.emitter.emit('message', event);
};
handleError = (event) => {
this.clearConnectTimeoutIfNeeded();
this.emitter.emit('error', event);
this.reconnectIfNeeded(`WebSocket error - ${event.message}`);
};
handleClose = (event) => {
this.clearConnectTimeoutIfNeeded();
this.lastCloseEvent = {
code: event.code,
reason: event.reason,
message: event.message,
};
this.reconnectIfNeeded(`WebSocket closed - code[${event.code}] reason[${event.reason}]`);
};
handleConnectTimeout = () => {
this.reconnectIfNeeded('Timeout from connecting to the WebSocket');
};
clearConnectTimeoutIfNeeded() {
if (this.connectTimeoutHandle != null) {
clearTimeout(this.connectTimeoutHandle);
this.connectTimeoutHandle = null;
}
}
reconnectIfNeeded(reason) {
if (this.ws != null) {
this.wsClose(this.ws);
this.ws = null;
}
if (this.isClosed) {
return;
}
if (this.retries >= this.maxRetries) {
this.onError(new Error('Exceeded max retries'));
this.close();
return;
}
setTimeout(() => {
this.retries += 1;
this.connect();
this.onReconnect(reason);
}, this.retriesInterval);
}
wsClose(ws) {
try {
ws.removeEventListener('message', this.handleMessage);
ws.removeEventListener('open', this.handleOpen);
// @ts-ignore: TypeScript expects (e: Event) => any, but we want (e: WebSocketErrorEvent) => any
ws.removeEventListener('error', this.handleError);
ws.removeEventListener('close', this.handleClose);
ws.close();
}
catch { }
}
get readyState() {
// Only return closed if the WebSocket is explicitly closed or exceeds max retries.
if (this.isClosed) {
return WebSocket.CLOSED;
}
const readyState = this.ws?.readyState;
if (readyState === WebSocket.CLOSED) {
return WebSocket.CONNECTING;
}
return readyState ?? WebSocket.CONNECTING;
}
//#endregion
//#region WebSocket API proxy
CONNECTING = 0;
OPEN = 1;
CLOSING = 2;
CLOSED = 3;
get binaryType() {
return this.ws?.binaryType ?? 'blob';
}
get bufferedAmount() {
return this.ws?.bufferedAmount ?? 0;
}
get extensions() {
return this.ws?.extensions ?? '';
}
get protocol() {
return this.ws?.protocol ?? '';
}
ping() {
return this.ws?.ping();
}
dispatchEvent(event) {
return this.ws?.dispatchEvent(event) ?? false;
}
//#endregion
//#regions Unsupported legacy properties
set onclose(value) {
throw new Error('Unsupported legacy property, use addEventListener instead');
}
set onerror(value) {
throw new Error('Unsupported legacy property, use addEventListener instead');
}
set onmessage(value) {
throw new Error('Unsupported legacy property, use addEventListener instead');
}
set onopen(value) {
throw new Error('Unsupported legacy property, use addEventListener instead');
}
}
//# sourceMappingURL=WebSocketWithReconnect.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
/**
* Converts a Blob to an ArrayBuffer.
*/
export declare function blobToArrayBufferAsync(blob: Blob): Promise<ArrayBuffer>;
/**
* Converts a Blob to an ArrayBuffer using the FileReader API.
*/
export declare function legacyBlobToArrayBufferAsync(blob: Blob): Promise<ArrayBuffer>;
//# sourceMappingURL=blobUtils.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"blobUtils.d.ts","sourceRoot":"","sources":["../../src/devtools/blobUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAKvE;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CASnF"}

View File

@@ -0,0 +1,23 @@
/**
* Converts a Blob to an ArrayBuffer.
*/
export function blobToArrayBufferAsync(blob) {
if (typeof blob.arrayBuffer === 'function') {
return blob.arrayBuffer();
}
return legacyBlobToArrayBufferAsync(blob);
}
/**
* Converts a Blob to an ArrayBuffer using the FileReader API.
*/
export async function legacyBlobToArrayBufferAsync(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result);
};
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
}
//# sourceMappingURL=blobUtils.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"blobUtils.js","sourceRoot":"","sources":["../../src/devtools/blobUtils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAU;IAC/C,IAAI,OAAO,IAAI,CAAC,WAAW,KAAK,UAAU,EAAE;QAC1C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;KAC3B;IACD,OAAO,4BAA4B,CAAC,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,IAAU;IAC3D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAChC,MAAM,CAAC,MAAM,GAAG,GAAG,EAAE;YACnB,OAAO,CAAC,MAAM,CAAC,MAAqB,CAAC,CAAC;QACxC,CAAC,CAAC;QACF,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC;QACxB,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * Converts a Blob to an ArrayBuffer.\n */\nexport function blobToArrayBufferAsync(blob: Blob): Promise<ArrayBuffer> {\n if (typeof blob.arrayBuffer === 'function') {\n return blob.arrayBuffer();\n }\n return legacyBlobToArrayBufferAsync(blob);\n}\n\n/**\n * Converts a Blob to an ArrayBuffer using the FileReader API.\n */\nexport async function legacyBlobToArrayBufferAsync(blob: Blob): Promise<ArrayBuffer> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => {\n resolve(reader.result as ArrayBuffer);\n };\n reader.onerror = reject;\n reader.readAsArrayBuffer(blob);\n });\n}\n"]}

View File

@@ -0,0 +1,40 @@
import type { WebSocketBackingStore } from './WebSocketBackingStore';
/**
* The connection info for devtools plugins client.
*/
export interface ConnectionInfo {
/** Indicates the sender towards the devtools plugin. */
sender: 'app' | 'browser';
/** Dev server address. */
devServer: string;
/** The plugin name. */
pluginName: string;
/**
* The backing store for the WebSocket connection. Exposed for testing.
* If not provided, the default singleton instance will be used.
* @hidden
*/
wsStore?: WebSocketBackingStore;
}
/**
* Parameters for the `handshake` message.
* @hidden
*/
export interface HandshakeMessageParams {
browserClientId: string;
pluginName: string;
}
/**
* Options for the devtools plugin client.
*/
export interface DevToolsPluginClientOptions {
/**
* The underlying WebSocket [`binaryType`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType).
*/
websocketBinaryType?: 'arraybuffer' | 'blob';
/**
* Whether to use the new transportation protocol.
*/
useTransportationNext?: boolean;
}
//# sourceMappingURL=devtools.types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"devtools.types.d.ts","sourceRoot":"","sources":["../../src/devtools/devtools.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,MAAM,EACF,KAAK,GACL,SAAS,CAAC;IAEd,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAElB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,qBAAqB,CAAC;CACjC;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,mBAAmB,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IAE7C;;OAEG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=devtools.types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"devtools.types.js","sourceRoot":"","sources":["../../src/devtools/devtools.types.ts"],"names":[],"mappings":"","sourcesContent":["import type { WebSocketBackingStore } from './WebSocketBackingStore';\n\n/**\n * The connection info for devtools plugins client.\n */\nexport interface ConnectionInfo {\n /** Indicates the sender towards the devtools plugin. */\n sender:\n | 'app' // client running in the app environment.\n | 'browser'; // client running in the browser environment.\n\n /** Dev server address. */\n devServer: string;\n\n /** The plugin name. */\n pluginName: string;\n\n /**\n * The backing store for the WebSocket connection. Exposed for testing.\n * If not provided, the default singleton instance will be used.\n * @hidden\n */\n wsStore?: WebSocketBackingStore;\n}\n\n/**\n * Parameters for the `handshake` message.\n * @hidden\n */\nexport interface HandshakeMessageParams {\n browserClientId: string;\n pluginName: string;\n}\n\n/**\n * Options for the devtools plugin client.\n */\nexport interface DevToolsPluginClientOptions {\n /**\n * The underlying WebSocket [`binaryType`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType).\n */\n websocketBinaryType?: 'arraybuffer' | 'blob';\n\n /**\n * Whether to use the new transportation protocol.\n */\n useTransportationNext?: boolean;\n}\n"]}

View File

@@ -0,0 +1,6 @@
/**
* Get the dev server address.
*/
import type { ConnectionInfo } from './devtools.types';
export declare function getConnectionInfo(): Omit<ConnectionInfo, 'pluginName'>;
//# sourceMappingURL=getConnectionInfo.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getConnectionInfo.d.ts","sourceRoot":"","sources":["../../src/devtools/getConnectionInfo.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,wBAAgB,iBAAiB,IAAI,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,CAOtE"}

View File

@@ -0,0 +1,12 @@
/**
* Get the dev server address.
*/
export function getConnectionInfo() {
const devServerQuery = new URLSearchParams(window.location.search).get('devServer');
const host = window.location.origin.replace(/^https?:\/\//, '');
return {
sender: 'browser',
devServer: devServerQuery || host,
};
}
//# sourceMappingURL=getConnectionInfo.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getConnectionInfo.js","sourceRoot":"","sources":["../../src/devtools/getConnectionInfo.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,cAAc,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACpF,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IAChE,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,SAAS,EAAE,cAAc,IAAI,IAAI;KAClC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Get the dev server address.\n */\n\nimport type { ConnectionInfo } from './devtools.types';\n\nexport function getConnectionInfo(): Omit<ConnectionInfo, 'pluginName'> {\n const devServerQuery = new URLSearchParams(window.location.search).get('devServer');\n const host = window.location.origin.replace(/^https?:\\/\\//, '');\n return {\n sender: 'browser',\n devServer: devServerQuery || host,\n };\n}\n"]}

View File

@@ -0,0 +1,6 @@
/**
* Get the dev server address.
*/
import type { ConnectionInfo } from './devtools.types';
export declare function getConnectionInfo(): Omit<ConnectionInfo, 'pluginName'>;
//# sourceMappingURL=getConnectionInfo.native.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getConnectionInfo.native.d.ts","sourceRoot":"","sources":["../../src/devtools/getConnectionInfo.native.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,wBAAgB,iBAAiB,IAAI,IAAI,CAAC,cAAc,EAAE,YAAY,CAAC,CAStE"}

View File

@@ -0,0 +1,14 @@
/**
* Get the dev server address.
*/
export function getConnectionInfo() {
const getDevServer = require('react-native/Libraries/Core/Devtools/getDevServer');
const devServer = getDevServer()
.url.replace(/^https?:\/\//, '')
.replace(/\/?$/, '');
return {
sender: 'app',
devServer,
};
}
//# sourceMappingURL=getConnectionInfo.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"getConnectionInfo.native.js","sourceRoot":"","sources":["../../src/devtools/getConnectionInfo.native.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,YAAY,GAAG,OAAO,CAAC,mDAAmD,CAAC,CAAC;IAClF,MAAM,SAAS,GAAG,YAAY,EAAE;SAC7B,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;SAC/B,OAAO,CAAC,MAAM,EAAE,EAAE,CAAW,CAAC;IACjC,OAAO;QACL,MAAM,EAAE,KAAK;QACb,SAAS;KACV,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Get the dev server address.\n */\n\nimport type { ConnectionInfo } from './devtools.types';\n\nexport function getConnectionInfo(): Omit<ConnectionInfo, 'pluginName'> {\n const getDevServer = require('react-native/Libraries/Core/Devtools/getDevServer');\n const devServer = getDevServer()\n .url.replace(/^https?:\\/\\//, '')\n .replace(/\\/?$/, '') as string;\n return {\n sender: 'app',\n devServer,\n };\n}\n"]}

View File

@@ -0,0 +1,12 @@
import { DevToolsPluginClient } from './DevToolsPluginClient';
import { getDevToolsPluginClientAsync } from './DevToolsPluginClientFactory';
import type { DevToolsPluginClientOptions } from './devtools.types';
export { getDevToolsPluginClientAsync, DevToolsPluginClient };
export type { DevToolsPluginClientOptions };
export type { EventSubscription } from 'fbemitter';
export { setEnableLogging } from './logger';
/**
* A React hook to get the DevToolsPluginClient instance.
*/
export declare function useDevToolsPluginClient(pluginName: string, options?: DevToolsPluginClientOptions): DevToolsPluginClient | null;
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/devtools/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAEpE,OAAO,EAAE,4BAA4B,EAAE,oBAAoB,EAAE,CAAC;AAC9D,YAAY,EAAE,2BAA2B,EAAE,CAAC;AAE5C,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,2BAA2B,GACpC,oBAAoB,GAAG,IAAI,CAkC7B"}

View File

@@ -0,0 +1,40 @@
import { useState, useEffect } from 'react';
import { DevToolsPluginClient } from './DevToolsPluginClient';
import { getDevToolsPluginClientAsync } from './DevToolsPluginClientFactory';
export { getDevToolsPluginClientAsync, DevToolsPluginClient };
export { setEnableLogging } from './logger';
/**
* A React hook to get the DevToolsPluginClient instance.
*/
export function useDevToolsPluginClient(pluginName, options) {
const [client, setClient] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
async function setup() {
try {
const client = await getDevToolsPluginClientAsync(pluginName, options);
setClient(client);
}
catch (e) {
setError(new Error('Failed to setup client from useDevToolsPluginClient: ' + e.toString()));
}
}
async function teardown() {
try {
await client?.closeAsync();
}
catch (e) {
setError(new Error('Failed to teardown client from useDevToolsPluginClient: ' + e.toString()));
}
}
setup();
return () => {
teardown();
};
}, [pluginName]);
if (error != null) {
throw error;
}
return client;
}
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/devtools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAG7E,OAAO,EAAE,4BAA4B,EAAE,oBAAoB,EAAE,CAAC;AAI9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C;;GAEG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAkB,EAClB,OAAqC;IAErC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAA8B,IAAI,CAAC,CAAC;IACxE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,UAAU,KAAK;YAClB,IAAI;gBACF,MAAM,MAAM,GAAG,MAAM,4BAA4B,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;gBACvE,SAAS,CAAC,MAAM,CAAC,CAAC;aACnB;YAAC,OAAO,CAAC,EAAE;gBACV,QAAQ,CAAC,IAAI,KAAK,CAAC,uDAAuD,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;aAC7F;QACH,CAAC;QAED,KAAK,UAAU,QAAQ;YACrB,IAAI;gBACF,MAAM,MAAM,EAAE,UAAU,EAAE,CAAC;aAC5B;YAAC,OAAO,CAAC,EAAE;gBACV,QAAQ,CACN,IAAI,KAAK,CAAC,0DAA0D,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CACrF,CAAC;aACH;QACH,CAAC;QAED,KAAK,EAAE,CAAC;QACR,OAAO,GAAG,EAAE;YACV,QAAQ,EAAE,CAAC;QACb,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,IAAI,KAAK,IAAI,IAAI,EAAE;QACjB,MAAM,KAAK,CAAC;KACb;IACD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { useState, useEffect } from 'react';\n\nimport { DevToolsPluginClient } from './DevToolsPluginClient';\nimport { getDevToolsPluginClientAsync } from './DevToolsPluginClientFactory';\nimport type { DevToolsPluginClientOptions } from './devtools.types';\n\nexport { getDevToolsPluginClientAsync, DevToolsPluginClient };\nexport type { DevToolsPluginClientOptions };\n// Export the EventSubscription type if people need to use explicit type from `addMessageListener`\nexport type { EventSubscription } from 'fbemitter';\nexport { setEnableLogging } from './logger';\n\n/**\n * A React hook to get the DevToolsPluginClient instance.\n */\nexport function useDevToolsPluginClient(\n pluginName: string,\n options?: DevToolsPluginClientOptions\n): DevToolsPluginClient | null {\n const [client, setClient] = useState<DevToolsPluginClient | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n async function setup() {\n try {\n const client = await getDevToolsPluginClientAsync(pluginName, options);\n setClient(client);\n } catch (e) {\n setError(new Error('Failed to setup client from useDevToolsPluginClient: ' + e.toString()));\n }\n }\n\n async function teardown() {\n try {\n await client?.closeAsync();\n } catch (e) {\n setError(\n new Error('Failed to teardown client from useDevToolsPluginClient: ' + e.toString())\n );\n }\n }\n\n setup();\n return () => {\n teardown();\n };\n }, [pluginName]);\n\n if (error != null) {\n throw error;\n }\n return client;\n}\n"]}

View File

@@ -0,0 +1,6 @@
export declare function log(...params: Parameters<typeof console.log>): void;
export declare function debug(...params: Parameters<typeof console.debug>): void;
export declare function info(...params: Parameters<typeof console.info>): void;
export declare function warn(...params: Parameters<typeof console.info>): void;
export declare function setEnableLogging(enabled: boolean): void;
//# sourceMappingURL=logger.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/devtools/logger.ts"],"names":[],"mappings":"AAEA,wBAAgB,GAAG,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,GAAG,CAAC,QAI5D;AAED,wBAAgB,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,KAAK,CAAC,QAIhE;AAED,wBAAgB,IAAI,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,IAAI,CAAC,QAI9D;AAED,wBAAgB,IAAI,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,IAAI,CAAC,QAI9D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,QAEhD"}

View File

@@ -0,0 +1,25 @@
let enableLogging = false;
export function log(...params) {
if (enableLogging) {
console.log(...params);
}
}
export function debug(...params) {
if (enableLogging) {
console.debug(...params);
}
}
export function info(...params) {
if (enableLogging) {
console.info(...params);
}
}
export function warn(...params) {
if (enableLogging) {
console.warn(...params);
}
}
export function setEnableLogging(enabled) {
enableLogging = enabled;
}
//# sourceMappingURL=logger.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/devtools/logger.ts"],"names":[],"mappings":"AAAA,IAAI,aAAa,GAAG,KAAK,CAAC;AAE1B,MAAM,UAAU,GAAG,CAAC,GAAG,MAAsC;IAC3D,IAAI,aAAa,EAAE;QACjB,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;KACxB;AACH,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAG,MAAwC;IAC/D,IAAI,aAAa,EAAE;QACjB,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC;KAC1B;AACH,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAG,MAAuC;IAC7D,IAAI,aAAa,EAAE;QACjB,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;KACzB;AACH,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,GAAG,MAAuC;IAC7D,IAAI,aAAa,EAAE;QACjB,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;KACzB;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,aAAa,GAAG,OAAO,CAAC;AAC1B,CAAC","sourcesContent":["let enableLogging = false;\n\nexport function log(...params: Parameters<typeof console.log>) {\n if (enableLogging) {\n console.log(...params);\n }\n}\n\nexport function debug(...params: Parameters<typeof console.debug>) {\n if (enableLogging) {\n console.debug(...params);\n }\n}\n\nexport function info(...params: Parameters<typeof console.info>) {\n if (enableLogging) {\n console.info(...params);\n }\n}\n\nexport function warn(...params: Parameters<typeof console.info>) {\n if (enableLogging) {\n console.warn(...params);\n }\n}\n\nexport function setEnableLogging(enabled: boolean) {\n enableLogging = enabled;\n}\n"]}

View File

@@ -0,0 +1,2 @@
export default function DevLoadingView(): JSX.Element | null;
//# sourceMappingURL=DevLoadingView.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingView.d.ts","sourceRoot":"","sources":["../../src/environment/DevLoadingView.tsx"],"names":[],"mappings":"AAQA,MAAM,CAAC,OAAO,UAAU,cAAc,uBAiFrC"}

View File

@@ -0,0 +1,110 @@
// Prevent pulling in all of expo-modules-core on web
import { EventEmitter } from 'expo-modules-core/build/EventEmitter';
import React, { useEffect, useState, useRef, useMemo } from 'react';
import { Animated, StyleSheet, Text, Platform, View } from 'react-native';
import DevLoadingViewNativeModule from './DevLoadingViewNativeModule';
import { getInitialSafeArea } from './getInitialSafeArea';
export default function DevLoadingView() {
const [message, setMessage] = useState('Refreshing...');
const [isDevLoading, setIsDevLoading] = useState(false);
const [isAnimating, setIsAnimating] = useState(false);
const translateY = useRef(new Animated.Value(0)).current;
const emitter = useMemo(() => {
try {
return new EventEmitter(DevLoadingViewNativeModule);
}
catch (error) {
throw new Error('Failed to instantiate native emitter in `DevLoadingView` because the native module `DevLoadingView` is undefined: ' +
error.message);
}
}, []);
useEffect(() => {
if (!emitter)
return;
function handleShowMessage(event) {
setMessage(event.message);
// TODO: if we show the refreshing banner and don't get a hide message
// for 3 seconds, warn the user that it's taking a while and suggest
// they reload
translateY.setValue(0);
setIsDevLoading(true);
}
function handleHide() {
// TODO: if we showed the 'refreshing' banner less than 250ms ago, delay
// switching to the 'finished' banner
setIsAnimating(true);
setIsDevLoading(false);
Animated.timing(translateY, {
toValue: 150,
delay: 1000,
duration: 350,
useNativeDriver: Platform.OS !== 'web',
}).start(({ finished }) => {
if (finished) {
setIsAnimating(false);
translateY.setValue(0);
}
});
}
const showMessageSubscription = emitter.addListener('devLoadingView:showMessage', handleShowMessage);
const hideSubscription = emitter.addListener('devLoadingView:hide', handleHide);
return function cleanup() {
showMessageSubscription.remove();
hideSubscription.remove();
};
}, [translateY, emitter]);
if (!isDevLoading && !isAnimating) {
return null;
}
return (<Animated.View style={[styles.animatedContainer, { transform: [{ translateY }] }]}>
<View style={styles.banner}>
<View style={styles.contentContainer}>
<View style={{ flexDirection: 'row' }}>
<Text style={styles.text}>{message}</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={styles.subtitle}>
{isDevLoading ? 'Using Fast Refresh' : "Don't see your changes? Reload the app"}
</Text>
</View>
</View>
</View>
</Animated.View>);
}
const styles = StyleSheet.create({
animatedContainer: {
// @ts-expect-error: fixed is not a valid value for position in Yoga but it is on web.
position: Platform.select({
web: 'fixed',
default: 'absolute',
}),
pointerEvents: 'none',
bottom: 0,
left: 0,
right: 0,
zIndex: 42, // arbitrary
},
banner: {
flex: 1,
overflow: 'visible',
backgroundColor: 'rgba(0,0,0,0.75)',
paddingBottom: getInitialSafeArea().bottom,
},
contentContainer: {
flex: 1,
paddingTop: 10,
paddingBottom: 5,
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center',
},
text: {
color: '#fff',
fontSize: 15,
},
subtitle: {
color: 'rgba(255,255,255,0.8)',
},
});
//# sourceMappingURL=DevLoadingView.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
declare const _default: Readonly<{
name: "DevLoadingView";
startObserving(): void;
stopObserving(): void;
addListener(): void;
removeListeners(): void;
}>;
export default _default;
//# sourceMappingURL=DevLoadingViewNativeModule.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingViewNativeModule.d.ts","sourceRoot":"","sources":["../../src/environment/DevLoadingViewNativeModule.ts"],"names":[],"mappings":";;;;;;;AAAA,wBAMG"}

View File

@@ -0,0 +1,8 @@
export default Object.freeze({
name: 'DevLoadingView',
startObserving() { },
stopObserving() { },
addListener() { },
removeListeners() { },
});
//# sourceMappingURL=DevLoadingViewNativeModule.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingViewNativeModule.js","sourceRoot":"","sources":["../../src/environment/DevLoadingViewNativeModule.ts"],"names":[],"mappings":"AAAA,eAAe,MAAM,CAAC,MAAM,CAAC;IAC3B,IAAI,EAAE,gBAAgB;IACtB,cAAc,KAAI,CAAC;IACnB,aAAa,KAAI,CAAC;IAClB,WAAW,KAAI,CAAC;IAChB,eAAe,KAAI,CAAC;CACrB,CAAC,CAAC","sourcesContent":["export default Object.freeze({\n name: 'DevLoadingView',\n startObserving() {},\n stopObserving() {},\n addListener() {},\n removeListeners() {},\n});\n"]}

View File

@@ -0,0 +1,3 @@
declare const _default: any;
export default _default;
//# sourceMappingURL=DevLoadingViewNativeModule.native.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingViewNativeModule.native.d.ts","sourceRoot":"","sources":["../../src/environment/DevLoadingViewNativeModule.native.ts"],"names":[],"mappings":";AAEA,wBAA4C"}

View File

@@ -0,0 +1,3 @@
import { NativeModules } from 'react-native';
export default NativeModules.DevLoadingView;
//# sourceMappingURL=DevLoadingViewNativeModule.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DevLoadingViewNativeModule.native.js","sourceRoot":"","sources":["../../src/environment/DevLoadingViewNativeModule.native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,eAAe,aAAa,CAAC,cAAc,CAAC","sourcesContent":["import { NativeModules } from 'react-native';\n\nexport default NativeModules.DevLoadingView;\n"]}

View File

@@ -0,0 +1,30 @@
type ExpoGoProjectConfig = {
mainModuleName?: string;
debuggerHost?: string;
logUrl?: string;
developer?: {
tool?: string;
[key: string]: any;
};
packagerOpts?: ExpoGoPackagerOpts;
};
type ExpoGoPackagerOpts = {
hostType?: string;
dev?: boolean;
strict?: boolean;
minify?: boolean;
urlType?: string;
urlRandomness?: string;
lanType?: string;
[key: string]: any;
};
/**
* Returns a boolean value whether the app is running in Expo Go.
*/
export declare function isRunningInExpoGo(): boolean;
/**
* Returns an Expo Go project config from the manifest or `null` if the app is not running in Expo Go.
*/
export declare function getExpoGoProjectConfig(): ExpoGoProjectConfig | null;
export {};
//# sourceMappingURL=ExpoGo.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoGo.d.ts","sourceRoot":"","sources":["../../src/environment/ExpoGo.ts"],"names":[],"mappings":"AAOA,KAAK,mBAAmB,GAAG;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE;QACV,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,YAAY,CAAC,EAAE,kBAAkB,CAAC;CACnC,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB,CAAC;AAYF;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,IAAI,CAEnE"}

View File

@@ -0,0 +1,24 @@
import { requireNativeModule } from 'expo-modules-core';
// ExpoGo module is available only when the app is run in Expo Go,
// otherwise we use `null` instead of throwing an error.
const NativeExpoGoModule = (() => {
try {
return requireNativeModule('ExpoGo');
}
catch {
return null;
}
})();
/**
* Returns a boolean value whether the app is running in Expo Go.
*/
export function isRunningInExpoGo() {
return NativeExpoGoModule != null;
}
/**
* Returns an Expo Go project config from the manifest or `null` if the app is not running in Expo Go.
*/
export function getExpoGoProjectConfig() {
return NativeExpoGoModule?.projectConfig ?? null;
}
//# sourceMappingURL=ExpoGo.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoGo.js","sourceRoot":"","sources":["../../src/environment/ExpoGo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AA6BxD,kEAAkE;AAClE,wDAAwD;AACxD,MAAM,kBAAkB,GAAG,CAAC,GAAwB,EAAE;IACpD,IAAI;QACF,OAAO,mBAAmB,CAAC,QAAQ,CAAC,CAAC;KACtC;IAAC,MAAM;QACN,OAAO,IAAI,CAAC;KACb;AACH,CAAC,CAAC,EAAE,CAAC;AAEL;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,kBAAkB,IAAI,IAAI,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,kBAAkB,EAAE,aAAa,IAAI,IAAI,CAAC;AACnD,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\ntype ExpoGoModule = {\n expoVersion: string;\n projectConfig: ExpoGoProjectConfig;\n};\n\ntype ExpoGoProjectConfig = {\n mainModuleName?: string;\n debuggerHost?: string;\n logUrl?: string;\n developer?: {\n tool?: string;\n [key: string]: any;\n };\n packagerOpts?: ExpoGoPackagerOpts;\n};\n\ntype ExpoGoPackagerOpts = {\n hostType?: string;\n dev?: boolean;\n strict?: boolean;\n minify?: boolean;\n urlType?: string;\n urlRandomness?: string;\n lanType?: string;\n [key: string]: any;\n};\n\n// ExpoGo module is available only when the app is run in Expo Go,\n// otherwise we use `null` instead of throwing an error.\nconst NativeExpoGoModule = ((): ExpoGoModule | null => {\n try {\n return requireNativeModule('ExpoGo');\n } catch {\n return null;\n }\n})();\n\n/**\n * Returns a boolean value whether the app is running in Expo Go.\n */\nexport function isRunningInExpoGo(): boolean {\n return NativeExpoGoModule != null;\n}\n\n/**\n * Returns an Expo Go project config from the manifest or `null` if the app is not running in Expo Go.\n */\nexport function getExpoGoProjectConfig(): ExpoGoProjectConfig | null {\n return NativeExpoGoModule?.projectConfig ?? null;\n}\n"]}

View File

@@ -0,0 +1,3 @@
export declare function isRunningInExpoGo(): boolean;
export declare function getExpoGoProjectConfig(): null;
//# sourceMappingURL=ExpoGo.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoGo.web.d.ts","sourceRoot":"","sources":["../../src/environment/ExpoGo.web.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,YAEhC;AAED,wBAAgB,sBAAsB,SAErC"}

View File

@@ -0,0 +1,7 @@
export function isRunningInExpoGo() {
return false;
}
export function getExpoGoProjectConfig() {
return null;
}
//# sourceMappingURL=ExpoGo.web.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoGo.web.js","sourceRoot":"","sources":["../../src/environment/ExpoGo.web.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB;IAC/B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["export function isRunningInExpoGo() {\n return false;\n}\n\nexport function getExpoGoProjectConfig() {\n return null;\n}\n"]}

View File

@@ -0,0 +1,7 @@
export declare function getInitialSafeArea(): {
top: number;
bottom: number;
left: number;
right: number;
};
//# sourceMappingURL=getInitialSafeArea.d.ts.map

Some files were not shown because too many files have changed in this diff Show More