diff --git a/README.md b/README.md
index 7f88bb3..bfd36a1 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,10 @@
-
+


+
**A powerful, easy-to-use QR code & Barcode Scanner for React Native with New Architecture support**
@@ -36,6 +37,7 @@ The `@pushpendersingh/react-native-scanner` package also includes a flashlight f
- đ¯ **Easy Integration** - Simple API with event-based scanning
- đĄ **Flash Support** - Toggle flashlight on/off
- đ **Lifecycle Management** - Automatic camera resource handling
+- đ **Thread-Safe** - Modern concurrency patterns (Swift Actors & Kotlin synchronization)
- đ¨ **Customizable** - Flexible styling options
---
@@ -516,18 +518,24 @@ This library supports a wide range of barcode formats across different categorie
## đ ī¸ Technical Details
### Android
+
- **CameraX 1.5.0** - Modern camera API with lifecycle awareness
- **ML Kit Barcode Scanning 17.3.0** - Google's ML-powered barcode detection
-- **Kotlin** - Native implementation
+- **Kotlin** - Native implementation with thread-safe synchronization
+- **Thread Safety** - Uses `@Volatile` and `@Synchronized` for concurrent access protection
### iOS
+
- **AVFoundation** - Native camera framework
- **Vision Framework** - Apple's barcode detection
-- **Swift 5.0** - Native implementation
+- **Swift 5.0+** - Native implementation with modern concurrency
+- **Thread Safety** - Uses Swift Actors for isolated state management and thread-safe operations
### React Native
+
- **New Architecture** - Turbo Modules + Fabric support
-- **React Native 0.81+** - Minimum version requirement
+- **React Native 0.80+** - Minimum version requirement
+- **Codegen** - Automatic native interface generation
---
diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/CameraManager.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/CameraManager.kt
index 048f175..c3012e2 100644
--- a/android/src/main/java/com/pushpendersingh/reactnativescanner/CameraManager.kt
+++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/CameraManager.kt
@@ -22,21 +22,38 @@ import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicReference
+import java.util.concurrent.locks.ReentrantLock
+import kotlin.concurrent.withLock
class CameraManager(private val reactContext: ReactApplicationContext) {
private val TAG = "CameraManager"
+ @Volatile
private var cameraProvider: ProcessCameraProvider? = null
- private val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
+ @Volatile
+ private var cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
private var scanner: BarcodeScanner? = null
+ @Volatile
private var cameraControl: CameraControl? = null
private var imageAnalysis: ImageAnalysis? = null
private var preview: Preview? = null
private var previewView: PreviewView? = null
+ // AtomicBoolean for lock-free scanning flag
+ private val isScanning = AtomicBoolean(false)
+ // AtomicReference for thread-safe callback
+ private val scanCallbackRef = AtomicReference<((WritableMap) -> Unit)?>(null)
+
+ // Lock for synchronizing camera binding operations
+ private val cameraBindLock = ReentrantLock()
+ // Flag to prevent concurrent binding
@Volatile
- private var isScanning: Boolean = false
- private var scanCallback: ((WritableMap) -> Unit)? = null
+ private var isBinding = false
+ // Lock for executor lifecycle management
+ private val executorLock = ReentrantLock()
companion object {
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
@@ -50,25 +67,69 @@ class CameraManager(private val reactContext: ReactApplicationContext) {
fun bindPreviewView(view: PreviewView) {
this.previewView = view
- if (isScanning) {
- // If already scanning, rebind with preview
- bindCameraUseCases()
+ // Only rebind if scanning AND not already in binding process
+ if (isScanning.get() && !isBinding) {
+ // Schedule rebind on main executor to avoid race with initializeCamera callback
+ ContextCompat.getMainExecutor(reactContext).execute {
+ bindCameraUseCases()
+ }
+ }
+ }
+
+ /**
+ * Thread-safe executor lifecycle management
+ * Ensures the camera executor is available and not shutdown.
+ * Creates a new executor if the current one has been shutdown.
+ * Uses dedicated lock to prevent race with releaseCamera()
+ */
+ private fun ensureExecutor() {
+ executorLock.withLock {
+ if (cameraExecutor.isShutdown) {
+ cameraExecutor = Executors.newSingleThreadExecutor()
+ Log.d(TAG, "âģī¸ Recreated camera executor")
+ }
+ }
+ }
+
+ /**
+ * Safe executor access with validation
+ * Returns the executor only if it's not shutdown
+ */
+ private fun getExecutorSafely(): ExecutorService? {
+ return executorLock.withLock {
+ if (!cameraExecutor.isShutdown) cameraExecutor else null
}
}
- @Synchronized
+ /**
+ * Lock-free scanning with atomic CAS operation
+ * Eliminates race condition between check and set
+ */
fun startScanning(callback: (WritableMap) -> Unit) {
if (!hasCameraPermission()) {
throw SecurityException("Camera permission not granted")
}
- if (isScanning) {
- Log.w(TAG, "Scanning already in progress")
+ // Atomic compare-and-set: only first caller proceeds
+ if (!isScanning.compareAndSet(false, true)) {
+ Log.w(TAG, "Scanning already in progress, updating callback")
+ scanCallbackRef.set(callback)
return
}
- scanCallback = callback
- initializeCamera()
+ // At this point, we're guaranteed to be the only thread starting scanning
+ // Set callback immediately after winning the CAS race
+ scanCallbackRef.set(callback)
+
+ try {
+ ensureExecutor()
+ initializeCamera()
+ } catch (e: Exception) {
+ // Reset flag on error
+ isScanning.set(false)
+ scanCallbackRef.set(null)
+ throw e
+ }
}
private fun initializeCamera() {
@@ -105,20 +166,47 @@ class CameraManager(private val reactContext: ReactApplicationContext) {
bindCameraUseCases()
} catch (exc: Exception) {
Log.e(TAG, "Error initializing camera: ${exc.message}", exc)
- isScanning = false
+ // Reset state on error
+ isScanning.set(false)
+ scanCallbackRef.set(null)
}
}, ContextCompat.getMainExecutor(reactContext))
}
+ /**
+ * Thread-safe camera binding with lock
+ * Prevents concurrent binding operations that could cause IllegalStateException
+ */
private fun bindCameraUseCases() {
- Log.d(TAG, "Binding camera use cases...")
- val currentActivity = reactContext.currentActivity as? AppCompatActivity
- if (currentActivity == null) {
- Log.e(TAG, "â Current activity is not available")
- return
+ // Use lock to serialize all binding operations
+ cameraBindLock.withLock {
+ // Check and set binding flag atomically within lock
+ if (isBinding) {
+ Log.w(TAG, "â ī¸ Camera binding already in progress, skipping")
+ return
+ }
+ isBinding = true
}
-
+
try {
+ Log.d(TAG, "Binding camera use cases...")
+ val currentActivity = reactContext.currentActivity as? AppCompatActivity
+ if (currentActivity == null || currentActivity.isDestroyed || currentActivity.isFinishing) {
+ Log.e(TAG, "â Current activity is not available")
+ isScanning.set(false)
+ scanCallbackRef.set(null)
+ return
+ }
+
+ // Get executor safely - may be null if shutdown in progress
+ val executor = getExecutorSafely()
+ if (executor == null) {
+ Log.e(TAG, "â Executor is shutdown, cannot bind camera")
+ isScanning.set(false)
+ scanCallbackRef.set(null)
+ return
+ }
+
// Unbind any existing use cases first
cameraProvider?.unbindAll()
Log.d(TAG, "Unbound previous camera use cases")
@@ -134,12 +222,14 @@ class CameraManager(private val reactContext: ReactApplicationContext) {
}
}
+ // Use the safely-obtained executor reference
imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also { analysis ->
- analysis.setAnalyzer(cameraExecutor) { imageProxy ->
- if (!isScanning) {
+ analysis.setAnalyzer(executor) { imageProxy ->
+ // Check atomic boolean
+ if (!isScanning.get()) {
imageProxy.close()
return@setAnalyzer
}
@@ -164,11 +254,17 @@ class CameraManager(private val reactContext: ReactApplicationContext) {
)
cameraControl = camera?.cameraControl
- isScanning = true
- Log.d(TAG, "Camera successfully bound and scanning started (preview: ${preview != null})")
+ Log.d(TAG, "â
Camera successfully bound and scanning started (preview: ${preview != null})")
} catch (exc: Exception) {
Log.e(TAG, "Error binding camera use cases: ${exc.message}", exc)
- isScanning = false
+ // Reset state on binding error
+ isScanning.set(false)
+ scanCallbackRef.set(null)
+ } finally {
+ // Always reset binding flag in finally block
+ cameraBindLock.withLock {
+ isBinding = false
+ }
}
}
@@ -189,7 +285,8 @@ class CameraManager(private val reactContext: ReactApplicationContext) {
for (barcode in barcodes) {
if (!barcode.rawValue.isNullOrEmpty()) {
val result = createBarcodeResult(barcode)
- scanCallback?.invoke(result)
+ val callback = scanCallbackRef.get()
+ callback?.invoke(result)
break // Process only the first barcode
}
}
@@ -251,33 +348,46 @@ class CameraManager(private val reactContext: ReactApplicationContext) {
}
}
- @Synchronized
+ /**
+ * Uses atomic operation to stop scanning
+ * Thread-safe: Can be called from any thread, camera operations executed on main thread
+ */
fun stopScanning() {
- if (!isScanning) {
+ // Atomic CAS: only proceed if actually scanning
+ if (!isScanning.compareAndSet(true, false)) {
Log.w(TAG, "Scanning is not in progress")
return
}
try {
Log.d(TAG, "Stopping scanning...")
- isScanning = false
- scanCallback = null
+ scanCallbackRef.set(null)
- // Clear the analyzer to stop processing frames
- imageAnalysis?.clearAnalyzer()
-
- // Unbind all use cases from the camera
- cameraProvider?.unbindAll()
-
- // Clear references but don't shut down executor or scanner
- // so they can be reused if scanning starts again
- cameraControl = null
- imageAnalysis = null
- preview = null
-
- Log.d(TAG, "â
Scanning stopped successfully")
+ // Execute camera operations on main thread to avoid IllegalStateException
+ ContextCompat.getMainExecutor(reactContext).execute {
+ try {
+ // Clear the analyzer to stop processing frames
+ imageAnalysis?.clearAnalyzer()
+
+ // Unbind all use cases from the camera
+ cameraProvider?.unbindAll()
+
+ // Clear references but DON'T shutdown executor or scanner
+ // They will be reused if scanning starts again
+ cameraControl = null
+ imageAnalysis = null
+ preview = null
+
+ Log.d(TAG, "â
Scanning stopped successfully (executor kept alive for reuse)")
+ } catch (e: Exception) {
+ Log.e(TAG, "Error stopping scanning on main thread: ${e.message}", e)
+ }
+ }
} catch (e: Exception) {
Log.e(TAG, "Error stopping scanning: ${e.message}", e)
+ // Ensure flag is still set to false even on error
+ isScanning.set(false)
+ throw e
}
}
@@ -305,29 +415,63 @@ class CameraManager(private val reactContext: ReactApplicationContext) {
try {
Log.d(TAG, "Releasing camera resources...")
- // Stop scanning first
- if (isScanning) {
- stopScanning()
+ // Stop scanning first using atomic operation
+ if (isScanning.compareAndSet(true, false)) {
+ scanCallbackRef.set(null)
+ }
+
+ // Wait for binding to complete WITHOUT holding the lock
+ // This prevents deadlock when startScanning() is called during release
+ var attempts = 0
+ while (isBinding && attempts < 100) { // Max wait: 5 seconds
+ Log.d(TAG, "âŗ Waiting for camera binding to complete before release...")
+ Thread.sleep(50)
+ attempts++
}
- // Unbind all camera use cases
- cameraProvider?.unbindAll()
-
+ if (isBinding) {
+ Log.w(TAG, "â ī¸ Binding still in progress after 5s, forcing release")
+ }
+
+ // Now safely unbind with lock (binding should be complete)
+ cameraBindLock.withLock {
+ // Double-check binding state and unbind
+ if (isBinding) {
+ Log.w(TAG, "â ī¸ Binding flag still set, unbinding anyway")
+ }
+ cameraProvider?.unbindAll()
+ }
+
// Clear all references
cameraProvider = null
cameraControl = null
imageAnalysis = null
preview = null
previewView = null
- scanCallback = null
-
+ scanCallbackRef.set(null)
+
// Close the barcode scanner
scanner?.close()
scanner = null
-
- // Note: We don't shutdown cameraExecutor here because it's a singleton
- // and we want to reuse it if scanning starts again
-
+
+ // Use dedicated executor lock for shutdown
+ // This prevents race with ensureExecutor() and getExecutorSafely()
+ executorLock.withLock {
+ if (!cameraExecutor.isShutdown) {
+ cameraExecutor.shutdown()
+ try {
+ if (!cameraExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
+ cameraExecutor.shutdownNow()
+ Log.w(TAG, "â ī¸ Executor did not terminate gracefully, forced shutdown")
+ }
+ } catch (e: InterruptedException) {
+ cameraExecutor.shutdownNow()
+ Thread.currentThread().interrupt()
+ Log.w(TAG, "â ī¸ Executor shutdown interrupted")
+ }
+ }
+ }
+
Log.d(TAG, "â
Camera resources released successfully")
} catch (e: Exception) {
Log.e(TAG, "â Error releasing camera: ${e.message}", e)
diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerModule.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerModule.kt
index 0b40240..0e664b1 100644
--- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerModule.kt
+++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerModule.kt
@@ -8,10 +8,10 @@ import androidx.core.content.ContextCompat
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
-import com.facebook.react.modules.core.DeviceEventManagerModule
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.modules.core.PermissionAwareActivity
import com.facebook.react.modules.core.PermissionListener
+import com.pushpendersingh.reactnativescanner.NativeReactNativeScannerSpec
@ReactModule(name = ReactNativeScannerModule.NAME)
class ReactNativeScannerModule(reactContext: ReactApplicationContext) :
@@ -53,10 +53,7 @@ class ReactNativeScannerModule(reactContext: ReactApplicationContext) :
}
cameraManager.startScanning { result ->
- // Send barcode result as event to JavaScript
- reactApplicationContext
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
- .emit("onBarcodeScanned", result)
+ emitOnBarcodeScanned(result)
}
promise.resolve(null)
} catch (e: Exception) {
@@ -158,16 +155,6 @@ class ReactNativeScannerModule(reactContext: ReactApplicationContext) :
}
}
- @ReactMethod
- override fun addListener(eventName: String) {
- // Required for event emitters
- }
-
- @ReactMethod
- override fun removeListeners(count: Double) {
- // Required for event emitters
- }
-
override fun invalidate() {
super.invalidate()
permissionPromise = null
diff --git a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt
index d1f653a..e02798c 100644
--- a/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt
+++ b/android/src/main/java/com/pushpendersingh/reactnativescanner/ReactNativeScannerView.kt
@@ -12,6 +12,7 @@ class ReactNativeScannerView(context: Context) : FrameLayout(context) {
private val previewView: PreviewView
private var cameraManager: CameraManager? = null
+ private var layoutCallback: Choreographer.FrameCallback? = null
init {
// Create PreviewView
@@ -30,13 +31,21 @@ class ReactNativeScannerView(context: Context) : FrameLayout(context) {
}
private fun setupLayoutHack() {
- Choreographer.getInstance().postFrameCallback(object : Choreographer.FrameCallback {
+ layoutCallback = object : Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
manuallyLayoutChildren()
viewTreeObserver.dispatchOnGlobalLayout()
Choreographer.getInstance().postFrameCallback(this)
}
- })
+ }
+ layoutCallback?.let { Choreographer.getInstance().postFrameCallback(it) }
+ }
+
+ private fun removeLayoutCallback() {
+ layoutCallback?.let {
+ Choreographer.getInstance().removeFrameCallback(it)
+ layoutCallback = null
+ }
}
private fun manuallyLayoutChildren() {
@@ -59,9 +68,17 @@ class ReactNativeScannerView(context: Context) : FrameLayout(context) {
// Bind the preview view to the camera manager
manager.bindPreviewView(previewView)
}
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+ // Restart layout callback when view is reattached
+ setupLayoutHack()
+ }
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
+ // Remove Choreographer callback to prevent memory leak
+ removeLayoutCallback()
// Clean up camera when view is detached
cameraManager?.releaseCamera()
}
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 3360670..8340652 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -2340,7 +2340,7 @@ PODS:
- React-perflogger (= 0.81.1)
- React-utils (= 0.81.1)
- SocketRocket
- - ReactNativeScanner (1.6.0):
+ - ReactNativeScanner (1.9.0):
- boost
- DoubleConversion
- fast_float
@@ -2704,11 +2704,11 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 3eb9096cb139eb433965693bbe541d96eb3d3ec9
ReactCodegen: 4d203eddf6f977caa324640a20f92e70408d648b
ReactCommon: ce5d4226dfaf9d5dacbef57b4528819e39d3a120
- ReactNativeScanner: 40e66f0f9bcf41dc30f6c4c6a655f492da7a7a4b
+ ReactNativeScanner: a12dcaf9a2b833163139c0372e17d819e60b7b99
RNPermissions: 380b0ddaff0bba3d4d0bbe4ed402044bc695752b
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 11c9686a21e2cd82a094a723649d9f4507200fb0
PODFILE CHECKSUM: 39409e5f2910df3585d821342022cbe2b38bffab
-COCOAPODS: 1.15.2
+COCOAPODS: 1.16.2
diff --git a/ios/CameraManager.swift b/ios/CameraManager.swift
index 70bc75e..3f655d1 100644
--- a/ios/CameraManager.swift
+++ b/ios/CameraManager.swift
@@ -5,24 +5,97 @@
// Created by Pushpender Singh
//
-import Foundation
@preconcurrency import AVFoundation
+import Foundation
@preconcurrency import Vision
-@objc public class CameraManager: NSObject, @unchecked Sendable {
-
+// Actor that manages camera session state and operations
+// Ensures all camera operations are thread-safe using Swift's actor isolation
+actor CameraSessionActor {
private var captureSession: AVCaptureSession?
private var videoDevice: AVCaptureDevice?
private var videoOutput: AVCaptureVideoDataOutput?
- private let sessionQueue = DispatchQueue(label: "com.pushpendersingh.scanner.sessionQueue")
private var isScanning = false
+
+ func getSession() -> AVCaptureSession? {
+ return captureSession
+ }
+
+ func setSession(_ session: AVCaptureSession?) {
+ captureSession = session
+ }
+
+ func getVideoDevice() -> AVCaptureDevice? {
+ return videoDevice
+ }
+
+ func setVideoDevice(_ device: AVCaptureDevice?) {
+ videoDevice = device
+ }
+
+ func getVideoOutput() -> AVCaptureVideoDataOutput? {
+ return videoOutput
+ }
+
+ func setVideoOutput(_ output: AVCaptureVideoDataOutput?) {
+ videoOutput = output
+ }
+
+ func getScanningState() -> Bool {
+ return isScanning
+ }
+
+ func setScanningState(_ scanning: Bool) {
+ isScanning = scanning
+ }
+
+ // Eliminates race condition between check and set
+ func startScanningIfNotActive() -> Bool {
+ guard !isScanning else { return false }
+ isScanning = true
+ return true
+ }
+
+ // Safe stop operation
+ func stopScanningIfActive() -> Bool {
+ guard isScanning else { return false }
+ isScanning = false
+ return true
+ }
+}
+
+// Actor that manages scan callbacks thread-safely
+actor CallbackActor {
private var scanCallback: (([String: Any]) -> Void)?
+ func setCallback(_ callback: (([String: Any]) -> Void)?) {
+ scanCallback = callback
+ }
+
+ func getCallback() -> (([String: Any]) -> Void)? {
+ return scanCallback
+ }
+
+ func invokeCallback(with result: [String: Any]) {
+ if let callback = scanCallback {
+ Task { @MainActor in
+ callback(result)
+ }
+ }
+ }
+}
+
+@objc public class CameraManager: NSObject {
+
+ private let sessionActor = CameraSessionActor()
+ private let callbackActor = CallbackActor()
+ private let sessionQueue = DispatchQueue(label: "com.pushpendersingh.scanner.sessionQueue")
+
// Session interruption handling
private var sessionInterruptionObserver: NSObjectProtocol?
private var sessionInterruptionEndedObserver: NSObjectProtocol?
@objc public var onSessionReady: ((AVCaptureSession) -> Void)?
-
+
// Barcode types to detect
private let supportedBarcodeTypes: [VNBarcodeSymbology] = [
.qr,
@@ -36,55 +109,108 @@ import Foundation
.ean8,
.itf14,
.pdf417,
- .upce
+ .upce,
]
-
+
@objc public func hasCameraPermission() -> Bool {
let status = AVCaptureDevice.authorizationStatus(for: .video)
return status == .authorized
}
-
+
@objc public func requestCameraPermission(completion: @escaping (Bool) -> Void) {
AVCaptureDevice.requestAccess(for: .video) { granted in
completion(granted)
}
}
-
- @objc public func currentSession() -> AVCaptureSession? {
- // CHANGE: Synchronize the read on sessionQueue to avoid cross-thread access warnings.
- // Reason: The session is produced/mutated on sessionQueue; reading it from main without
- // synchronization can trigger structural concurrency diagnostics.
- return sessionQueue.sync { captureSession }
+
+ // Async version - don't block main thread
+ // Use this from Swift async contexts
+ public func getCurrentSession() async -> AVCaptureSession? {
+ return await sessionActor.getSession()
}
+ // Callback version for Objective-C bridge
+ // Non-blocking alternative for synchronous contexts
+ @objc public func getCurrentSession(completion: @escaping (AVCaptureSession?) -> Void) {
+ Task {
+ let session = await sessionActor.getSession()
+ await MainActor.run {
+ completion(session)
+ }
+ }
+ }
+
@objc(startScanningWithCallback:error:)
public func startScanning(callback: @escaping ([String: Any]) -> Void) throws {
- guard hasCameraPermission() else {
- throw NSError(domain: "CameraManager", code: 1, userInfo: [NSLocalizedDescriptionKey: "Camera permission not granted"])
+ // Check permission status immediately, but asynchronously request if needed.
+ switch AVCaptureDevice.authorizationStatus(for: .video) {
+ case .authorized:
+ // Permission already granted. Proceed with scanning setup.
+ sessionQueue.async { [weak self] in
+ guard let self = self else { return }
+ Task {
+ await self.configureAndStartScanning(callback: callback)
+ }
+ }
+ case .notDetermined:
+ // Request permission. The result is handled asynchronously.
+ AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in
+ guard let self = self else { return }
+ self.sessionQueue.async {
+ Task {
+ if granted {
+ await self.configureAndStartScanning(callback: callback)
+ } else {
+ // Handle denial.
+ let errorInfo = ["error": "Camera permission denied"]
+ await self.callbackActor.invokeCallback(with: errorInfo)
+ }
+ }
+ }
+ }
+ case .denied, .restricted:
+ // Handle denied/restricted status immediately.
+ let errorInfo = ["error": "Camera permission not granted"]
+ Task {
+ await callbackActor.invokeCallback(with: errorInfo)
+ }
+ @unknown default:
+ // Handle any future unknown cases.
+ let errorInfo = ["error": "Unknown camera permission status"]
+ Task {
+ await callbackActor.invokeCallback(with: errorInfo)
+ }
+ }
+ }
+
+ // Private helper with atomic check-and-set
+ // Eliminates race condition in session initialization
+ private func configureAndStartScanning(callback: @escaping ([String: Any]) -> Void) async {
+ // Atomic operation - no race condition
+ guard await sessionActor.startScanningIfNotActive() else {
+ print("â ī¸ Session already running, updating callback only")
+ await callbackActor.setCallback(callback)
+ return
}
- // Execute on sessionQueue for thread safety
- sessionQueue.async { [weak self] in
- guard let self = self else { return }
-
- // Check if session is already running
- if self.isScanning, let session = self.captureSession, session.isRunning {
- print("â ī¸ Session already running, updating callback only")
- self.scanCallback = callback
- return
- }
-
- // Set flag on the same queue where it's checked
- self.isScanning = true
- self.scanCallback = callback
-
- self.setupCaptureSession()
+ // At this point, we're guaranteed to be the only thread starting scanning
+ await callbackActor.setCallback(callback)
+
+ do {
+ try await setupCaptureSession()
+ } catch {
+ // Reset state on error
+ await sessionActor.setScanningState(false)
+ await callbackActor.setCallback(nil)
+ print("â Failed to setup camera session: \(error.localizedDescription)")
}
}
-
- private func setupCaptureSession() {
+
+ private func setupCaptureSession() async throws {
// Reuse existing session if available
- if let existingSession = captureSession {
+ let existingSession = await sessionActor.getSession()
+
+ if let existingSession = existingSession {
// If session exists but not running, just restart it
if !existingSession.isRunning {
print("âģī¸ Restarting existing session")
@@ -94,27 +220,29 @@ import Foundation
print("â ī¸ Session already running")
}
if let onSessionReady = self.onSessionReady {
- DispatchQueue.main.async {
+ DispatchQueue.main.async { [weak self] in
+ guard self != nil else { return }
onSessionReady(existingSession)
}
}
return
}
-
+
// Create new session only if none exists
let newSession = AVCaptureSession()
-
+
newSession.beginConfiguration()
newSession.sessionPreset = .high
-
+
// Setup video device
- guard let videoDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else {
+ guard let videoDevice = AVCaptureDevice.default(
+ .builtInWideAngleCamera, for: .video, position: .back)
+ else {
print("â Failed to get video device")
- isScanning = false
- return
+ throw CameraError.deviceNotAvailable
}
- self.videoDevice = videoDevice
-
+ await sessionActor.setVideoDevice(videoDevice)
+
// Setup video input
do {
let videoInput = try AVCaptureDeviceInput(device: videoDevice)
@@ -122,118 +250,157 @@ import Foundation
newSession.addInput(videoInput)
} else {
print("â Cannot add video input")
- isScanning = false
- return
+ throw CameraError.cannotAddInput
}
} catch {
print("â Error creating video input: \(error.localizedDescription)")
- isScanning = false
- return
+ throw error
}
-
+
// Setup video output
let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: sessionQueue)
videoOutput.alwaysDiscardsLateVideoFrames = true
-
+
if newSession.canAddOutput(videoOutput) {
newSession.addOutput(videoOutput)
- self.videoOutput = videoOutput
+ await sessionActor.setVideoOutput(videoOutput)
} else {
print("â Cannot add video output")
- isScanning = false
- return
+ throw CameraError.cannotAddOutput
}
-
+
newSession.commitConfiguration()
-
- // Assign to property AFTER configuration
- self.captureSession = newSession
-
+
+ // Assign to actor AFTER configuration
+ await sessionActor.setSession(newSession)
+
// Start session (already on sessionQueue)
newSession.startRunning()
print("â
Camera session started - Running: \(newSession.isRunning)")
- print("â
Video output delegate set: \(self.videoOutput?.sampleBufferDelegate != nil)")
- // Notify UI after the session is running to avoid startRunning during configuration
+ let output = await sessionActor.getVideoOutput()
+ print("â
Video output delegate set: \(output?.sampleBufferDelegate != nil)")
+
+ // Notify UI after the session is running
if let onSessionReady = self.onSessionReady {
- DispatchQueue.main.async {
+ DispatchQueue.main.async { [weak self] in
+ guard self != nil else { return }
onSessionReady(newSession)
}
}
}
+ // Camera error types for better error handling
+ enum CameraError: Error {
+ case deviceNotAvailable
+ case cannotAddInput
+ case cannotAddOutput
+ case sessionNotRunning
+ case torchUnavailable
+
+ var localizedDescription: String {
+ switch self {
+ case .deviceNotAvailable:
+ return "Camera device not available"
+ case .cannotAddInput:
+ return "Cannot add video input to session"
+ case .cannotAddOutput:
+ return "Cannot add video output to session"
+ case .sessionNotRunning:
+ return "Camera session is not running"
+ case .torchUnavailable:
+ return "Torch/flashlight is not available on this device"
+ }
+ }
+ }
+
@objc public func stopScanning() {
sessionQueue.async { [weak self] in
guard let self = self else { return }
-
- // Set flag first, then stop session
- self.isScanning = false
- self.scanCallback = nil
-
- if let session = self.captureSession, session.isRunning {
- session.stopRunning()
- print("â
Scanning stopped")
- } else {
- print("â ī¸ No active session to stop")
+
+ Task {
+ // â
Use atomic operation to stop scanning
+ guard await self.sessionActor.stopScanningIfActive() else {
+ print("â ī¸ Scanning is not active")
+ return
+ }
+
+ await self.callbackActor.setCallback(nil)
+
+ let session = await self.sessionActor.getSession()
+ if let session = session, session.isRunning {
+ session.stopRunning()
+ print("â
Scanning stopped")
+ } else {
+ print("â ī¸ No active session to stop")
+ }
}
}
}
-
+
@objc public func enableFlashlight() {
// Execute on sessionQueue for thread safety
sessionQueue.async { [weak self] in
guard let self = self else { return }
-
- // Check if session is running
- guard let session = self.captureSession, session.isRunning else {
- print("â ī¸ Cannot enable flashlight - camera not running")
- return
- }
-
- guard let device = self.videoDevice, device.hasTorch else {
- print("â ī¸ Torch not available")
- return
- }
-
- do {
- try device.lockForConfiguration()
- device.torchMode = .on
- device.unlockForConfiguration()
- print("â
Flashlight enabled")
- } catch {
- print("â Failed to enable torch: \(error.localizedDescription)")
+
+ Task {
+ // Check if session is running
+ let session = await self.sessionActor.getSession()
+ guard let session = session, session.isRunning else {
+ print("â ī¸ Cannot enable flashlight - camera not running")
+ return
+ }
+
+ let device = await self.sessionActor.getVideoDevice()
+ guard let device = device, device.hasTorch else {
+ print("â ī¸ Torch not available")
+ return
+ }
+
+ do {
+ try device.lockForConfiguration()
+ device.torchMode = .on
+ device.unlockForConfiguration()
+ print("â
Flashlight enabled")
+ } catch {
+ print("â Failed to enable torch: \(error.localizedDescription)")
+ }
}
}
}
-
+
@objc public func disableFlashlight() {
// Execute on sessionQueue for thread safety
sessionQueue.async { [weak self] in
guard let self = self else { return }
-
- // Check if session is running
- guard let session = self.captureSession, session.isRunning else {
- print("â ī¸ Cannot disable flashlight - camera not running")
- return
- }
-
- guard let device = self.videoDevice, device.hasTorch else {
- print("â ī¸ Torch not available")
- return
- }
-
- do {
- try device.lockForConfiguration()
- device.torchMode = .off
- device.unlockForConfiguration()
- print("â
Flashlight disabled")
- } catch {
- print("â Failed to disable torch: \(error.localizedDescription)")
+
+ Task {
+ // Check if session is running
+ let session = await self.sessionActor.getSession()
+ guard let session = session, session.isRunning else {
+ print("â ī¸ Cannot disable flashlight - camera not running")
+ return
+ }
+
+ let device = await self.sessionActor.getVideoDevice()
+ guard let device = device, device.hasTorch else {
+ print("â ī¸ Torch not available")
+ return
+ }
+
+ do {
+ try device.lockForConfiguration()
+ device.torchMode = .off
+ device.unlockForConfiguration()
+ print("â
Flashlight disabled")
+ } catch {
+ print("â Failed to disable torch: \(error.localizedDescription)")
+ }
}
}
}
-
+
// Setup interruption observers in init
public override init() {
super.init()
@@ -242,7 +409,7 @@ import Foundation
self.setupInterruptionObservers()
}
}
-
+
@MainActor
private func setupInterruptionObservers() {
// Handle session interruption (e.g., incoming call, alarm)
@@ -252,18 +419,20 @@ import Foundation
queue: nil
) { [weak self] notification in
guard let self = self else { return }
-
- if let reasonValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? Int,
- let reason = AVCaptureSession.InterruptionReason(rawValue: reasonValue) {
+
+ if let reasonValue = notification.userInfo?[AVCaptureSessionInterruptionReasonKey]
+ as? Int,
+ let reason = AVCaptureSession.InterruptionReason(rawValue: reasonValue)
+ {
print("â ī¸ Session interrupted: \(reason)")
-
- // Optionally pause scanning during interruption
- sessionQueue.async {
- self.isScanning = false
+
+ // Pause scanning during interruption
+ Task {
+ await self.sessionActor.setScanningState(false)
}
}
}
-
+
// Handle session interruption ended
sessionInterruptionEndedObserver = NotificationCenter.default.addObserver(
forName: .AVCaptureSessionInterruptionEnded,
@@ -272,18 +441,25 @@ import Foundation
) { [weak self] _ in
guard let self = self else { return }
print("â
Session interruption ended")
-
- // Resume scanning only if we had a callback (means scanning was active)
- self.sessionQueue.async {
- if self.scanCallback != nil, let session = self.captureSession, !session.isRunning {
+
+ // Resume scanning only if we were actually scanning before interruption
+ Task {
+ // Check scanning state first to prevent restarting released session
+ let isScanning = await self.sessionActor.getScanningState()
+ let callback = await self.callbackActor.getCallback()
+ let session = await self.sessionActor.getSession()
+
+ // Only resume if still supposed to be scanning
+ if isScanning, callback != nil, let session = session, !session.isRunning {
session.startRunning()
- self.isScanning = true
print("â
Scanning resumed after interruption")
+ } else if !isScanning {
+ print("âšī¸ Not resuming - scanning was stopped during interruption")
}
}
}
}
-
+
@objc public func releaseCamera() {
// Remove observers on main thread to keep lifecycle consistent
DispatchQueue.main.async { [weak self] in
@@ -297,45 +473,52 @@ import Foundation
self.sessionInterruptionEndedObserver = nil
}
}
-
+
sessionQueue.async { [weak self] in
guard let self = self else { return }
-
- // Stop scanning first
- self.isScanning = false
- self.scanCallback = nil
-
- // Stop session before modifying it
- if let session = self.captureSession {
- if session.isRunning {
- session.stopRunning()
- }
-
- // Use beginConfiguration when removing inputs/outputs
- session.beginConfiguration()
-
- for input in session.inputs {
- session.removeInput(input)
- }
- for output in session.outputs {
- session.removeOutput(output)
+
+ Task {
+ // Stop scanning first
+ await self.sessionActor.setScanningState(false)
+ await self.callbackActor.setCallback(nil)
+
+ // Stop session before modifying it
+ let session = await self.sessionActor.getSession()
+ if let session = session {
+ if session.isRunning {
+ session.stopRunning()
+ }
+
+ // Use beginConfiguration when removing inputs/outputs
+ session.beginConfiguration()
+
+ for input in session.inputs {
+ session.removeInput(input)
+ }
+ for output in session.outputs {
+ session.removeOutput(output)
+ }
+
+ session.commitConfiguration()
}
-
- session.commitConfiguration()
+
+ await self.sessionActor.setSession(nil)
+ await self.sessionActor.setVideoDevice(nil)
+ await self.sessionActor.setVideoOutput(nil)
+
+ print("â
Camera resources released")
}
-
- self.captureSession = nil
- self.videoDevice = nil
- self.videoOutput = nil
-
- print("â
Camera resources released")
}
-
+
+ // Clear the onSessionReady callback to prevent retain cycles
+ onSessionReady = nil
+
+ // Re-setup observers for next use
Task { @MainActor in
self.setupInterruptionObservers()
}
}
-
+
// Deinit to cleanup observers
deinit {
if let observer = sessionInterruptionObserver {
@@ -348,77 +531,73 @@ import Foundation
}
}
-// MARK: - AVCaptureVideoDataOutputSampleBufferDelegate
+// AVCaptureVideoDataOutputSampleBufferDelegate
extension CameraManager: AVCaptureVideoDataOutputSampleBufferDelegate {
-
- public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
- // Early exit if not scanning
- guard isScanning else { return }
-
- guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
- print("â ī¸ Failed to get pixel buffer")
- return
- }
-
- let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
-
- let barcodeRequest = VNDetectBarcodesRequest { [weak self] request, error in
- guard let self = self else { return }
-
- // Double-check isScanning inside async callback
- guard self.isScanning else {
- print("â ī¸ Barcode detected but scanning stopped, ignoring")
- return
- }
-
- if let error = error {
- print("Barcode detection error: \(error.localizedDescription)")
- return
- }
-
- guard let results = request.results as? [VNBarcodeObservation],
- let firstBarcode = results.first,
- let payloadString = firstBarcode.payloadStringValue else {
- return
- }
-
- print("â
Barcode detected: \(payloadString) - Type: \(firstBarcode.symbology.rawValue)")
-
- // Create result dictionary
- let result = self.createBarcodeResult(barcode: firstBarcode, payloadString: payloadString)
-
- // Capture callback safely
- guard let callback = self.scanCallback else {
- print("â ī¸ No callback available")
+
+ public func captureOutput(
+ _ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer,
+ from connection: AVCaptureConnection
+ ) {
+ // Check scanning state using async context
+ Task {
+ let isCurrentlyScanning = await sessionActor.getScanningState()
+ guard isCurrentlyScanning else { return }
+
+ guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
+ print("â ī¸ Failed to get pixel buffer")
return
}
-
- // Call the callback on main thread
- DispatchQueue.main.async {
- if self.isScanning {
- callback(result)
+
+ let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
+
+ let barcodeRequest = VNDetectBarcodesRequest { [weak self] request, error in
+ guard let self = self else { return }
+
+ Task {
+ // Double-check isScanning inside async callback
+ let stillScanning = await self.sessionActor.getScanningState()
+ guard stillScanning else { return }
+
+ if let error = error {
+ print("Barcode detection error: \(error.localizedDescription)")
+ return
+ }
+
+ guard let results = request.results as? [VNBarcodeObservation],
+ let firstBarcode = results.first,
+ let payloadString = firstBarcode.payloadStringValue
+ else {
+ return
+ }
+
+ // Create result dictionary
+ let result = self.createBarcodeResult(
+ barcode: firstBarcode, payloadString: payloadString)
+
+ // Invoke callback through the actor for thread safety
+ await self.callbackActor.invokeCallback(with: result)
print("đ¤ Callback invoked with barcode data")
- } else {
- print("â ī¸ Scanning stopped before callback, ignoring")
}
}
- }
-
- barcodeRequest.symbologies = supportedBarcodeTypes
-
- do {
- try imageRequestHandler.perform([barcodeRequest])
- } catch {
- print("Failed to perform barcode detection: \(error.localizedDescription)")
+
+ barcodeRequest.symbologies = supportedBarcodeTypes
+
+ do {
+ try imageRequestHandler.perform([barcodeRequest])
+ } catch {
+ print("Failed to perform barcode detection: \(error.localizedDescription)")
+ }
}
}
-
- private func createBarcodeResult(barcode: VNBarcodeObservation, payloadString: String) -> [String: Any] {
+
+ private func createBarcodeResult(barcode: VNBarcodeObservation, payloadString: String)
+ -> [String: Any]
+ {
var result: [String: Any] = [
"data": payloadString,
- "type": getBarcodeTypeName(barcode.symbology)
+ "type": getBarcodeTypeName(barcode.symbology),
]
-
+
// Add bounds if available
let boundingBox = barcode.boundingBox
let bounds: [String: Any] = [
@@ -428,15 +607,15 @@ extension CameraManager: AVCaptureVideoDataOutputSampleBufferDelegate {
"topLeft": ["x": boundingBox.minX, "y": boundingBox.maxY],
"bottomLeft": ["x": boundingBox.minX, "y": boundingBox.minY],
"bottomRight": ["x": boundingBox.maxX, "y": boundingBox.minY],
- "topRight": ["x": boundingBox.maxX, "y": boundingBox.maxY]
- ]
+ "topRight": ["x": boundingBox.maxX, "y": boundingBox.maxY],
+ ],
]
-
+
result["bounds"] = bounds
-
+
return result
}
-
+
private func getBarcodeTypeName(_ symbology: VNBarcodeSymbology) -> String {
switch symbology {
case .qr:
@@ -468,4 +647,3 @@ extension CameraManager: AVCaptureVideoDataOutputSampleBufferDelegate {
}
}
}
-
diff --git a/ios/CameraView.swift b/ios/CameraView.swift
index 91f8cc6..23f6b80 100644
--- a/ios/CameraView.swift
+++ b/ios/CameraView.swift
@@ -44,7 +44,8 @@ public class CameraView: UIView {
// keeping UI work on main and avoiding concurrency violations.
manager.onSessionReady = { [weak self] session in
guard let self = self else { return }
- DispatchQueue.main.async {
+ DispatchQueue.main.async { [weak self] in
+ guard let self = self else { return }
if let previewLayer = self.previewLayer {
previewLayer.session = session
previewLayer.connection?.videoOrientation = .portrait
@@ -55,11 +56,11 @@ public class CameraView: UIView {
}
}
- // CHANGE: If a session already exists, bind it immediately on the main thread.
- // Reason: Ensures the preview shows even if the session was created before the view.
- if let existingSession = manager.currentSession() {
- DispatchQueue.main.async { [weak self] in
- guard let self = self, let previewLayer = self.previewLayer else { return }
+ // Use non-blocking callback version
+ // Reason: Avoids blocking main thread with semaphore
+ manager.getCurrentSession { [weak self] existingSession in
+ guard let self = self, let previewLayer = self.previewLayer else { return }
+ if let existingSession = existingSession {
previewLayer.session = existingSession
previewLayer.connection?.videoOrientation = .portrait
print("â
Preview layer bound to existing session")
@@ -79,6 +80,12 @@ public class CameraView: UIView {
previewLayer?.session = nil
previewLayer?.removeFromSuperlayer()
previewLayer = nil
+
+ // Clear the onSessionReady callback to prevent potential retain cycles
+ cameraManager?.onSessionReady = nil
+ cameraManager = nil
+
+ print("đī¸ CameraView deallocated")
}
}
diff --git a/ios/CameraViewManager.mm b/ios/CameraViewManager.mm
index 1598df5..de0f8ab 100644
--- a/ios/CameraViewManager.mm
+++ b/ios/CameraViewManager.mm
@@ -5,18 +5,17 @@
// Created by Pushpender Singh
//
-#import
-#import
-#import
#import "ReactNativeScanner.h"
+#import
+#import
// Import the Swift bridging header
#if __has_include()
- // For dynamic frameworks or when installed as a framework
- #import
+// For dynamic frameworks or when installed as a framework
+#import
#else
- // For static libraries or when included directly in the project
- #import "ReactNativeScanner-Swift.h"
+// For static libraries or when included directly in the project
+#import "ReactNativeScanner-Swift.h"
#endif
@interface ReactNativeScannerViewManager : RCTViewManager
@@ -26,26 +25,20 @@ @implementation ReactNativeScannerViewManager
RCT_EXPORT_MODULE(ReactNativeScannerView)
-- (UIView *)view
-{
- CameraView *cameraView = [[CameraView alloc] init];
-
- // Bind immediately on the current thread (main queue)
- // Since requiresMainQueueSetup returns YES, we're already on main queue
- ReactNativeScanner *scannerModule = [self.bridge moduleForClass:[ReactNativeScanner class]];
- if (scannerModule && scannerModule.cameraManager) {
- [cameraView setCameraManager:scannerModule.cameraManager];
- NSLog(@"â
Camera manager bound to view");
- } else {
- NSLog(@"â ī¸ Scanner module or camera manager not available");
- }
-
- return cameraView;
+- (UIView *)view {
+ CameraView *cameraView = [[CameraView alloc] init];
+
+ ReactNativeScanner *scannerModule =
+ [self.bridge moduleForClass:[ReactNativeScanner class]];
+ if (scannerModule && scannerModule.cameraManager) {
+ [cameraView setCameraManager:scannerModule.cameraManager];
+ }
+
+ return cameraView;
}
-+ (BOOL)requiresMainQueueSetup
-{
- return YES;
++ (BOOL)requiresMainQueueSetup {
+ return YES;
}
@end
diff --git a/ios/ReactNativeScanner-Bridging-Header.h b/ios/ReactNativeScanner-Bridging-Header.h
index d3e9f4c..c628714 100644
--- a/ios/ReactNativeScanner-Bridging-Header.h
+++ b/ios/ReactNativeScanner-Bridging-Header.h
@@ -5,8 +5,6 @@
// Created by Pushpender Singh
//
-#import
-#import
#import
#import
#import
diff --git a/ios/ReactNativeScanner.h b/ios/ReactNativeScanner.h
index 602780b..cb8ea2c 100644
--- a/ios/ReactNativeScanner.h
+++ b/ios/ReactNativeScanner.h
@@ -1,10 +1,12 @@
-#import
+#import
#import
@class CameraManager;
-@interface ReactNativeScanner : RCTEventEmitter
+@interface ReactNativeScanner : NativeReactNativeScannerSpecBase
@property (nonatomic, strong, readonly) CameraManager *cameraManager;
@end
+
+
diff --git a/ios/ReactNativeScanner.mm b/ios/ReactNativeScanner.mm
index e1dffac..61036f7 100644
--- a/ios/ReactNativeScanner.mm
+++ b/ios/ReactNativeScanner.mm
@@ -7,13 +7,8 @@
#import
#endif
-@interface ReactNativeScanner ()
-@property(nonatomic, strong) CameraManager *cameraManager;
-@end
-
@implementation ReactNativeScanner
-
-RCT_EXPORT_MODULE()
+@synthesize cameraManager = _cameraManager;
- (instancetype)init {
if (self = [super init]) {
@@ -27,10 +22,6 @@ - (CameraManager *)cameraManager {
return _cameraManager;
}
-- (NSArray *)supportedEvents {
- return @[ @"onBarcodeScanned" ];
-}
-
- (void)startScanning:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
if (![_cameraManager hasCameraPermission]) {
@@ -42,7 +33,7 @@ - (void)startScanning:(RCTPromiseResolveBlock)resolve
@try {
[_cameraManager
startScanningWithCallback:^(NSDictionary *result) {
- [self sendEventWithName:@"onBarcodeScanned" body:result];
+ [self emitOnBarcodeScanned:result];
}
error:&error];
if (error) {
@@ -108,32 +99,26 @@ - (void)requestCameraPermission:(RCTPromiseResolveBlock)resolve
}];
}
-- (void)addListener:(NSString *)eventName {
- // Required for RCTEventEmitter
- // Keep track of listeners to avoid "no listeners" warning
- [super addListener:eventName];
-}
-
-- (void)removeListeners:(double)count {
- // Required for RCTEventEmitter
- [super removeListeners:count];
-}
-
- (void)invalidate {
- // Capture strong reference to camera manager first to ensure it stays alive during cleanup
+ // Capture strong reference to camera manager first to ensure it stays alive
+ // during cleanup
CameraManager *cameraManager = _cameraManager;
-
+
if (cameraManager) {
[cameraManager releaseCamera];
}
-
- [super invalidate];
}
- (std::shared_ptr)getTurboModule:
- (const facebook::react::ObjCTurboModule::InitParams &)params {
- return std::make_shared(
- params);
+ (const facebook::react::ObjCTurboModule::InitParams &)params
+{
+ return std::make_shared(params);
+}
+
++ (NSString *)moduleName
+{
+ return @"ReactNativeScanner";
}
@end
+
diff --git a/src/CameraView.tsx b/src/CameraView.tsx
index 0141a8d..6618d4b 100644
--- a/src/CameraView.tsx
+++ b/src/CameraView.tsx
@@ -1,18 +1,14 @@
import React from 'react';
-import { requireNativeComponent } from 'react-native';
import type { ViewProps } from 'react-native';
+import ReactNativeScannerViewNativeComponent from './ReactNativeScannerViewNativeComponent';
export interface CameraViewProps extends ViewProps {
style?: ViewProps['style'];
}
-const NativeCameraView = requireNativeComponent(
- 'ReactNativeScannerView'
-);
-
export const CameraView = React.forwardRef(
(props, ref) => {
- return ;
+ return ;
}
);
diff --git a/src/NativeReactNativeScanner.ts b/src/NativeReactNativeScanner.ts
index 1202d57..cff7c8e 100644
--- a/src/NativeReactNativeScanner.ts
+++ b/src/NativeReactNativeScanner.ts
@@ -1,6 +1,21 @@
-import type { TurboModule } from 'react-native';
+import type { CodegenTypes, TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
+export type BarcodeScannedEvent = {
+ data: string;
+ type: string;
+ bounds?: {
+ width: number;
+ height: number;
+ origin: {
+ topLeft: { x: number; y: number };
+ bottomLeft: { x: number; y: number };
+ bottomRight: { x: number; y: number };
+ topRight: { x: number; y: number };
+ };
+ };
+};
+
export interface Spec extends TurboModule {
// Start scanning - results will be emitted via events
startScanning(): Promise;
@@ -23,9 +38,7 @@ export interface Spec extends TurboModule {
// Request camera permission
requestCameraPermission(): Promise;
- // Add event emitter support
- addListener(eventName: string): void;
- removeListeners(count: number): void;
+ readonly onBarcodeScanned: CodegenTypes.EventEmitter;
}
export default TurboModuleRegistry.getEnforcing('ReactNativeScanner');
diff --git a/src/ReactNativeScannerViewNativeComponent.ts b/src/ReactNativeScannerViewNativeComponent.ts
index 16f542a..bc6a001 100644
--- a/src/ReactNativeScannerViewNativeComponent.ts
+++ b/src/ReactNativeScannerViewNativeComponent.ts
@@ -1,5 +1,5 @@
import type { ViewProps, HostComponent } from 'react-native';
-import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
+import { codegenNativeComponent } from 'react-native';
export interface NativeProps extends ViewProps {
// Add any custom props here if needed in the future
diff --git a/src/index.tsx b/src/index.tsx
index 711f0b4..8cdc38e 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,29 +1,5 @@
-import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
import NativeReactNativeScanner from './NativeReactNativeScanner';
-// Augment global type for TurboModule detection
-declare global {
- // eslint-disable-next-line no-var
- var __turboModuleProxy: any | undefined;
-}
-
-const LINKING_ERROR =
- `The package '@pushpendersingh/react-native-scanner' doesn't seem to be linked. Make sure: \n\n` +
- Platform.select({ ios: "- Run 'pod install'\n", default: '' }) +
- '- Rebuild the app';
-
-const isTurboModuleEnabled = global.__turboModuleProxy != null;
-
-const ReactNativeScannerModule = isTurboModuleEnabled
- ? NativeReactNativeScanner
- : NativeModules.ReactNativeScanner;
-
-if (!ReactNativeScannerModule) {
- throw new Error(LINKING_ERROR);
-}
-
-const eventEmitter = new NativeEventEmitter(ReactNativeScannerModule);
-
// Types
export type BarcodeType =
| 'QR_CODE'
@@ -62,88 +38,51 @@ export type BarcodeScannerCallback = (result: BarcodeResult) => void;
export class BarcodeScanner {
private static listener: any = null;
- /**
- * Start scanning for barcodes
- * @param callback Function to call when a barcode is detected
- * @returns Promise that resolves when scanning starts
- */
static async startScanning(callback: BarcodeScannerCallback): Promise {
- // Remove any existing listener first
+ // Remove existing listener
if (this.listener) {
this.listener.remove();
this.listener = null;
}
- // Add new listener BEFORE starting scanning
- this.listener = eventEmitter.addListener(
- 'onBarcodeScanned',
- (result: any) => {
- console.log('đą Event received in JS:', result);
- callback(result as BarcodeResult);
- }
- );
-
- // Small delay to ensure listener is fully registered
- await new Promise((resolve) => setTimeout(resolve, 10));
+ this.listener = NativeReactNativeScanner.onBarcodeScanned((event) => {
+ callback(event as BarcodeResult);
+ });
// Start scanning
- return ReactNativeScannerModule.startScanning();
+ return NativeReactNativeScanner.startScanning();
}
- /**
- * Stop scanning for barcodes
- * @returns Promise that resolves when scanning stops
- */
static async stopScanning(): Promise {
if (this.listener) {
this.listener.remove();
this.listener = null;
}
- return ReactNativeScannerModule.stopScanning();
+ return NativeReactNativeScanner.stopScanning();
}
- /**
- * Enable device flashlight/torch
- * @returns Promise that resolves when flashlight is enabled
- */
static async enableFlashlight(): Promise {
- return ReactNativeScannerModule.enableFlashlight();
+ return NativeReactNativeScanner.enableFlashlight();
}
- /**
- * Disable device flashlight/torch
- * @returns Promise that resolves when flashlight is disabled
- */
static async disableFlashlight(): Promise {
- return ReactNativeScannerModule.disableFlashlight();
+ return NativeReactNativeScanner.disableFlashlight();
}
- /**
- * Release camera resources
- * @returns Promise that resolves when camera is released
- */
static async releaseCamera(): Promise {
if (this.listener) {
this.listener.remove();
this.listener = null;
}
- return ReactNativeScannerModule.releaseCamera();
+ return NativeReactNativeScanner.releaseCamera();
}
- /**
- * Check if camera permission is granted
- * @returns Promise that resolves with permission status
- */
static async hasCameraPermission(): Promise {
- return ReactNativeScannerModule.hasCameraPermission();
+ return NativeReactNativeScanner.hasCameraPermission();
}
- /**
- * Request camera permission
- * @returns Promise that resolves with whether permission was granted
- */
static async requestCameraPermission(): Promise {
- return ReactNativeScannerModule.requestCameraPermission();
+ return NativeReactNativeScanner.requestCameraPermission();
}
}
diff --git a/yarn.lock b/yarn.lock
index 1bdd3c7..1c31458 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5,19 +5,28 @@ __metadata:
version: 6
cacheKey: 8
-"@ark/schema@npm:0.49.0":
- version: 0.49.0
- resolution: "@ark/schema@npm:0.49.0"
+"@ark/regex@npm:0.0.0":
+ version: 0.0.0
+ resolution: "@ark/regex@npm:0.0.0"
dependencies:
- "@ark/util": 0.49.0
- checksum: 9901123581afa0eef63305fc47a1a725ff17c8958a80694464b0d08d3c398be26160763fed91864b8f8fb9553f3bf57d7075e436b6f7902220074f86310ee9d8
+ "@ark/util": 0.50.0
+ checksum: b89d50a610393a4025a0e2cb4444c16c4f2fb16708ee6e4afe36160ee3503c3a7a5df8a7477bbf4b75099509329fc62f388f64819002d2f93642b2188618b5e5
languageName: node
linkType: hard
-"@ark/util@npm:0.49.0":
- version: 0.49.0
- resolution: "@ark/util@npm:0.49.0"
- checksum: 01ae677327cd585d9bbdc9373d5d5d70e10a14be151976c7d86f27cc7289d6e4d51e3da3993c69aed1657f3aa4abe409834e6338a7a7391a30209fa34c066c14
+"@ark/schema@npm:0.50.0":
+ version: 0.50.0
+ resolution: "@ark/schema@npm:0.50.0"
+ dependencies:
+ "@ark/util": 0.50.0
+ checksum: 6a080104865ec4a0be91d6bffab95f69923f4a85b6087f67cf04555b30b65544084eeebbfa4cf9759ec27b964b0fc4dc7e19603b055b472a096463f13c084343
+ languageName: node
+ linkType: hard
+
+"@ark/util@npm:0.50.0":
+ version: 0.50.0
+ resolution: "@ark/util@npm:0.50.0"
+ checksum: 50aa1d506bbf70ef502f0f424370ab831fcb891f5a71fdec51c46d06c504eab751ccfa2920bbeae7c97d22bcfc71c755a29121489984eacf052040c61c696fc9
languageName: node
linkType: hard
@@ -1775,19 +1784,12 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/config-helpers@npm:^0.3.1":
- version: 0.3.1
- resolution: "@eslint/config-helpers@npm:0.3.1"
- checksum: b95c239264078a430761afb344402d517134289a7d8b69a6ff1378ebe5eec9da6ad22b5e6d193b9e02899aeda30817ac47178d5927247092cc6d73a52f8d07c9
- languageName: node
- linkType: hard
-
-"@eslint/core@npm:^0.15.2":
- version: 0.15.2
- resolution: "@eslint/core@npm:0.15.2"
+"@eslint/config-helpers@npm:^0.4.0":
+ version: 0.4.0
+ resolution: "@eslint/config-helpers@npm:0.4.0"
dependencies:
- "@types/json-schema": ^7.0.15
- checksum: 535fc4e657760851826ceae325a72dde664b99189bd975715de3526db655c66d7a35b72dbb1c7641ab9201ed4e2130f79c5be51f96c820b5407c3766dcf94f23
+ "@eslint/core": ^0.16.0
+ checksum: f17af9d6de60e0d8be5131451ef489f32984f92aff00cb1c5c8f1790baf07ea7ad803e0f21f1519eded4ce247871ffe593b7e51ddc094b5337d22f29dd720ba5
languageName: node
linkType: hard
@@ -1817,10 +1819,10 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/js@npm:9.36.0, @eslint/js@npm:^9.35.0":
- version: 9.36.0
- resolution: "@eslint/js@npm:9.36.0"
- checksum: 17ff28272337357783b55e76417e61306e528dced99bb49d49e06298023b4071cb30f4aeb0bf30a337817d3eb3132784db6b8edd3a90118c5217833136712713
+"@eslint/js@npm:9.37.0, @eslint/js@npm:^9.35.0":
+ version: 9.37.0
+ resolution: "@eslint/js@npm:9.37.0"
+ checksum: 916f2ff7f70eadaa3a1c3f7d6d375fccfb676723484e1c54c5d63ff8a462746090097b73d21f4cb876ff2276d04af3f1c4c9e9a93729a9305213ca3aaa75008c
languageName: node
linkType: hard
@@ -1831,22 +1833,22 @@ __metadata:
languageName: node
linkType: hard
-"@eslint/plugin-kit@npm:^0.3.5":
- version: 0.3.5
- resolution: "@eslint/plugin-kit@npm:0.3.5"
+"@eslint/plugin-kit@npm:^0.4.0":
+ version: 0.4.0
+ resolution: "@eslint/plugin-kit@npm:0.4.0"
dependencies:
- "@eslint/core": ^0.15.2
+ "@eslint/core": ^0.16.0
levn: ^0.4.1
- checksum: 1808d7e2538335b8e4536ef372840e93468ecc6f4a5bf72ad665795290b6a8a72f51ef4ffd8bcfc601b133a5d5f67b59ab256d945f8c825c5c307aad29efaf86
+ checksum: bb82be19c99eea256f7ec8e0996d28bd4b95b796bd1b27659b92e83278ef813485ada55995314887e7812cca02b0a9672d63f547c2a110eb5a7f0022c8e0f23d
languageName: node
linkType: hard
"@evilmartians/lefthook@npm:^1.12.3":
- version: 1.13.5
- resolution: "@evilmartians/lefthook@npm:1.13.5"
+ version: 1.13.6
+ resolution: "@evilmartians/lefthook@npm:1.13.6"
bin:
lefthook: bin/index.js
- checksum: 139f12fec89c1517c4fe26bb9c4abc5ee1fd75f39314bbcfe749f9f93a6fa071b851d77b8650a6c64b0da24821e5a024108ecd995627c0cee35f6bb1bc875952
+ checksum: 6cceca3e874015678f50818ae14a74d959816cfaba6638f8852d007332404d6819b15c71538985a3650a1ef057aa6975c17fadfe43ece7a0da1aeb9faaf02946
conditions: (os=darwin | os=linux | os=win32) & (cpu=x64 | cpu=arm64 | cpu=ia32)
languageName: node
linkType: hard
@@ -1905,53 +1907,53 @@ __metadata:
languageName: node
linkType: hard
-"@inquirer/ansi@npm:^1.0.0":
- version: 1.0.0
- resolution: "@inquirer/ansi@npm:1.0.0"
- checksum: 153b619c1178ece3e28a66ab41b7827b9ee64c84180f779bcc1c38c8c3e87979130bba109dd7e648ccdd3786da75c4a3a0945e816dc6afec9219f54ac7fbbb69
+"@inquirer/ansi@npm:^1.0.0, @inquirer/ansi@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "@inquirer/ansi@npm:1.0.1"
+ checksum: 0dda65720736f3e730715f3778e0e90f039ebd1382c277495a4d1cdbd2b2863095aa7291cd8ea7d3c0618bdee04a375db6e10a7bae5fb904df0b632a1c7774f9
languageName: node
linkType: hard
-"@inquirer/checkbox@npm:^4.2.4":
- version: 4.2.4
- resolution: "@inquirer/checkbox@npm:4.2.4"
+"@inquirer/checkbox@npm:^4.3.0":
+ version: 4.3.0
+ resolution: "@inquirer/checkbox@npm:4.3.0"
dependencies:
- "@inquirer/ansi": ^1.0.0
- "@inquirer/core": ^10.2.2
- "@inquirer/figures": ^1.0.13
- "@inquirer/type": ^3.0.8
+ "@inquirer/ansi": ^1.0.1
+ "@inquirer/core": ^10.3.0
+ "@inquirer/figures": ^1.0.14
+ "@inquirer/type": ^3.0.9
yoctocolors-cjs: ^2.1.2
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 4e55f2795016c63f1ee0d9c48a83d067f485f23fb2fe404eeb47c239031b12f5c3c20ec5e26f99c5c103bb47ce341cef918038268bee9e70d512425614d56840
+ checksum: d4957d0ce205c5c0bc70eb9491ca4ebe983cce0abaf552cc8ad521179db94841fb25603121d0af1b31757bb8381377a410c21cde2a48754af18f694b31477c14
languageName: node
linkType: hard
-"@inquirer/confirm@npm:^5.1.18":
- version: 5.1.18
- resolution: "@inquirer/confirm@npm:5.1.18"
+"@inquirer/confirm@npm:^5.1.19":
+ version: 5.1.19
+ resolution: "@inquirer/confirm@npm:5.1.19"
dependencies:
- "@inquirer/core": ^10.2.2
- "@inquirer/type": ^3.0.8
+ "@inquirer/core": ^10.3.0
+ "@inquirer/type": ^3.0.9
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 59a27eedf9b8e5ff1ca5eb738121caf56c94d9ec80f0ff02021300a7894c608e9c32e06b79ba21714f6977a277c84025e62b141c50c580be2a30697f52ef4941
+ checksum: d65e0addf80c146d71a74057d77048bd78a4a80d74a9e0d774b759ff1adf38a33cde6c06a6d6ef802bb61ef9158770315dec3931f89b3624c0e63c595c0473c1
languageName: node
linkType: hard
-"@inquirer/core@npm:^10.2.2":
- version: 10.2.2
- resolution: "@inquirer/core@npm:10.2.2"
+"@inquirer/core@npm:^10.2.2, @inquirer/core@npm:^10.3.0":
+ version: 10.3.0
+ resolution: "@inquirer/core@npm:10.3.0"
dependencies:
- "@inquirer/ansi": ^1.0.0
- "@inquirer/figures": ^1.0.13
- "@inquirer/type": ^3.0.8
+ "@inquirer/ansi": ^1.0.1
+ "@inquirer/figures": ^1.0.14
+ "@inquirer/type": ^3.0.9
cli-width: ^4.1.0
mute-stream: ^2.0.0
signal-exit: ^4.1.0
@@ -1962,39 +1964,39 @@ __metadata:
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 79d528ecb5f485a0f63bd5e48273a2ffba9457240e2a1971da8ea97a97c8398b932260691b8d5e5134f306ba8b08ce7a4800dfa7bd8cc9143a86760714215927
+ checksum: 42607c2e8388bf6505f5ce1716d47750f9386085f3080733b7f27bfe59d576d480ec622d7468fcf1bd9b854ff117311421d9eae0c083873c67324023635e103a
languageName: node
linkType: hard
-"@inquirer/editor@npm:^4.2.20":
- version: 4.2.20
- resolution: "@inquirer/editor@npm:4.2.20"
+"@inquirer/editor@npm:^4.2.21":
+ version: 4.2.21
+ resolution: "@inquirer/editor@npm:4.2.21"
dependencies:
- "@inquirer/core": ^10.2.2
+ "@inquirer/core": ^10.3.0
"@inquirer/external-editor": ^1.0.2
- "@inquirer/type": ^3.0.8
+ "@inquirer/type": ^3.0.9
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: d87ddb4d2668a8860ea48dee4f26323d568a50be8dfa645d29fcd97d9457a20bc5a80b7f79c725af253e363dee740b28f77a1f9748eb6833fc002b00a6f5d67a
+ checksum: 8467c192f9c1573853718c15c020146268cf4b076d99a14e014a61d124c46157c57780d770caeeba94f309259504f3602248591842d11b9465ad12fd82185276
languageName: node
linkType: hard
-"@inquirer/expand@npm:^4.0.20":
- version: 4.0.20
- resolution: "@inquirer/expand@npm:4.0.20"
+"@inquirer/expand@npm:^4.0.21":
+ version: 4.0.21
+ resolution: "@inquirer/expand@npm:4.0.21"
dependencies:
- "@inquirer/core": ^10.2.2
- "@inquirer/type": ^3.0.8
+ "@inquirer/core": ^10.3.0
+ "@inquirer/type": ^3.0.9
yoctocolors-cjs: ^2.1.2
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 975d2159dc38ae4a4fd9e7fe1f731bcb01f20a80f49d79a43232cbf9310d868cbc20c19c25fb9d3970d1415be772fd1a793065b4d939e60045b13abecb45d057
+ checksum: eb1900c443895377c03652c3e2b6ca29c572fe6ee2682e264572957b9b4a596d3d55c9ea271934846fb05d5cc5195cca0dffde1386e41358ac5c308698320e93
languageName: node
linkType: hard
@@ -2013,142 +2015,142 @@ __metadata:
languageName: node
linkType: hard
-"@inquirer/figures@npm:^1.0.13":
- version: 1.0.13
- resolution: "@inquirer/figures@npm:1.0.13"
- checksum: 1042cbefad8c69b004396ce6be2d0b135c303317d870ddd0cee75bac429fc7c7f577bac9e3c1ec1cd3668a709f49a591edb2f714193778e7d7b140a622f2a1ef
+"@inquirer/figures@npm:^1.0.14":
+ version: 1.0.14
+ resolution: "@inquirer/figures@npm:1.0.14"
+ checksum: 37eec986f119eabb6c231c8c1481c6a48ab2347e9f57b2d6442161f7b83936678221fccb7ead60582026c2ae20d457467d0727c485ff53aee2cf965077b0f51b
languageName: node
linkType: hard
-"@inquirer/input@npm:^4.2.4":
- version: 4.2.4
- resolution: "@inquirer/input@npm:4.2.4"
+"@inquirer/input@npm:^4.2.5":
+ version: 4.2.5
+ resolution: "@inquirer/input@npm:4.2.5"
dependencies:
- "@inquirer/core": ^10.2.2
- "@inquirer/type": ^3.0.8
+ "@inquirer/core": ^10.3.0
+ "@inquirer/type": ^3.0.9
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 725b9e3152e15bdcc5c7043cd8a15cc5573cb78ea7a273aca802f0a673ddd3f543e4cb2a94445e88f7ca66facef6faaa6040f959bf70c28f53c0fad25a8cecda
+ checksum: 9d192556aefc8f8fbc70626f9a90cd2806032ec9e7d323b46afff0a0813f5c0f766ff9a5d2f8bdc39863688f8fdd081ce23b782c19aebf61ff8692c9135528b6
languageName: node
linkType: hard
-"@inquirer/number@npm:^3.0.20":
- version: 3.0.20
- resolution: "@inquirer/number@npm:3.0.20"
+"@inquirer/number@npm:^3.0.21":
+ version: 3.0.21
+ resolution: "@inquirer/number@npm:3.0.21"
dependencies:
- "@inquirer/core": ^10.2.2
- "@inquirer/type": ^3.0.8
+ "@inquirer/core": ^10.3.0
+ "@inquirer/type": ^3.0.9
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 9f5d3c7484cd8d0e798ac6ab4e00b71dcc46450b5d9e39f40def15dd76363aa0fac17158583cad1b64af90968b72bfa4cc6ac3338f19651023f2cb686db9c394
+ checksum: 445ba93639ecfc3755efa7ee9cf7cf972919abc1cd022ada27e7d73b93e01680ffcf56a8ca6fe090775358c8d2aec259890aa33b6c0e1c3aeba7306f25ba633a
languageName: node
linkType: hard
-"@inquirer/password@npm:^4.0.20":
- version: 4.0.20
- resolution: "@inquirer/password@npm:4.0.20"
+"@inquirer/password@npm:^4.0.21":
+ version: 4.0.21
+ resolution: "@inquirer/password@npm:4.0.21"
dependencies:
- "@inquirer/ansi": ^1.0.0
- "@inquirer/core": ^10.2.2
- "@inquirer/type": ^3.0.8
+ "@inquirer/ansi": ^1.0.1
+ "@inquirer/core": ^10.3.0
+ "@inquirer/type": ^3.0.9
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 9b3c46498cf09e3d3da1e2539b23468ab4020904dc40efb8a8f50b1c5bf7af7e7355c18ee313dac00d750b4b83d8b0a2c72b10cfcef475f742a5a738fe4300f2
+ checksum: 07fb1527ea2d44a81b79d9263f59713e66977e21fbf44efedb6bf08d27d617900ef481c49c91b0a749caf1d282f2b5e19fe6b7474acc98db3edd174eb5d45416
languageName: node
linkType: hard
"@inquirer/prompts@npm:^7.8.6":
- version: 7.8.6
- resolution: "@inquirer/prompts@npm:7.8.6"
- dependencies:
- "@inquirer/checkbox": ^4.2.4
- "@inquirer/confirm": ^5.1.18
- "@inquirer/editor": ^4.2.20
- "@inquirer/expand": ^4.0.20
- "@inquirer/input": ^4.2.4
- "@inquirer/number": ^3.0.20
- "@inquirer/password": ^4.0.20
- "@inquirer/rawlist": ^4.1.8
- "@inquirer/search": ^3.1.3
- "@inquirer/select": ^4.3.4
+ version: 7.9.0
+ resolution: "@inquirer/prompts@npm:7.9.0"
+ dependencies:
+ "@inquirer/checkbox": ^4.3.0
+ "@inquirer/confirm": ^5.1.19
+ "@inquirer/editor": ^4.2.21
+ "@inquirer/expand": ^4.0.21
+ "@inquirer/input": ^4.2.5
+ "@inquirer/number": ^3.0.21
+ "@inquirer/password": ^4.0.21
+ "@inquirer/rawlist": ^4.1.9
+ "@inquirer/search": ^3.2.0
+ "@inquirer/select": ^4.4.0
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: a6e554516743de1f5781503c4a402df6d24af7d526e89ac5cd17a30d5761bfcc16f283f49382aa57c67e7b88c0e63e91e9aa5367a1dbe4de6373c873fae60202
+ checksum: 1dd6a87bcf77d1a8b728c781a7d34c0dd4028d7ec96e4e41e173a260d3ef9a76cba5eb8715d8674d75b18681d3f7eac9bd9f3ff1d82d8e786fb5222893498ea3
languageName: node
linkType: hard
-"@inquirer/rawlist@npm:^4.1.8":
- version: 4.1.8
- resolution: "@inquirer/rawlist@npm:4.1.8"
+"@inquirer/rawlist@npm:^4.1.9":
+ version: 4.1.9
+ resolution: "@inquirer/rawlist@npm:4.1.9"
dependencies:
- "@inquirer/core": ^10.2.2
- "@inquirer/type": ^3.0.8
+ "@inquirer/core": ^10.3.0
+ "@inquirer/type": ^3.0.9
yoctocolors-cjs: ^2.1.2
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: ece338071d8070d7be123b2cac0bffabe3a1b1c197bf18d518e4eeed910aaa9359c287b3bdfc6943f0552839c312e36ef87aebae62ed31b10d59d7442f8b3064
+ checksum: ec23e087bfa9497b36d51b53e8da18c837e4a0c5c091bce7d1a6b52d9664035d7e22c3753993dd3c7c9ebfd5e9b71f1738873f2c25422668733ddb28d74bf26b
languageName: node
linkType: hard
-"@inquirer/search@npm:^3.1.3":
- version: 3.1.3
- resolution: "@inquirer/search@npm:3.1.3"
+"@inquirer/search@npm:^3.2.0":
+ version: 3.2.0
+ resolution: "@inquirer/search@npm:3.2.0"
dependencies:
- "@inquirer/core": ^10.2.2
- "@inquirer/figures": ^1.0.13
- "@inquirer/type": ^3.0.8
+ "@inquirer/core": ^10.3.0
+ "@inquirer/figures": ^1.0.14
+ "@inquirer/type": ^3.0.9
yoctocolors-cjs: ^2.1.2
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 7b908673b28b2651ecba33974b99b684d253a911d1e6123f386e648911060add083981889de81d2ed89c0cfc504047624b1ba15921da92a95a28cfd73decec6b
+ checksum: 5f8f86368513d29d9119a04cc65bbe075d0d93ec55af27a555f02bf740c7a65497e36df80da722cbee020ab348c9038856f0e070e9a22615ff9d5c3155c3296a
languageName: node
linkType: hard
-"@inquirer/select@npm:^4.3.4":
- version: 4.3.4
- resolution: "@inquirer/select@npm:4.3.4"
+"@inquirer/select@npm:^4.4.0":
+ version: 4.4.0
+ resolution: "@inquirer/select@npm:4.4.0"
dependencies:
- "@inquirer/ansi": ^1.0.0
- "@inquirer/core": ^10.2.2
- "@inquirer/figures": ^1.0.13
- "@inquirer/type": ^3.0.8
+ "@inquirer/ansi": ^1.0.1
+ "@inquirer/core": ^10.3.0
+ "@inquirer/figures": ^1.0.14
+ "@inquirer/type": ^3.0.9
yoctocolors-cjs: ^2.1.2
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 59da7ce52c324879e7c7b239ae4ddbdc9519a6593ded099eb1efa9cb45854d62e6565cab29d4df38fdc64bf7fc1ba7d0192b686a4039f9501739a189dcee303c
+ checksum: cb0441f5ec981011f3e451fa062897e8991ca3b0cadca99432d2556a1f90e9c0251b277233056db63b717b5f77caaf32b2c5db7a1881fcc78d5c357d1e912616
languageName: node
linkType: hard
-"@inquirer/type@npm:^3.0.8":
- version: 3.0.8
- resolution: "@inquirer/type@npm:3.0.8"
+"@inquirer/type@npm:^3.0.8, @inquirer/type@npm:^3.0.9":
+ version: 3.0.9
+ resolution: "@inquirer/type@npm:3.0.9"
peerDependencies:
"@types/node": ">=18"
peerDependenciesMeta:
"@types/node":
optional: true
- checksum: 361fa75c98f274462aaa3f2baf40ee43f284daaa64e3689a92863ed4ff63236ca3d40c6e715b3ff80c45feb6ab679792a6162e2d4521daff3929c490b0dddfcf
+ checksum: 960ba4737405f70bac17e7cdc4696c60064b06c8dd13a4b3d0783763ba1714bdadbd598b88d537ab9415b7d5d61e011ac042cfbd1438b2a35298e2868724b853
languageName: node
linkType: hard
@@ -3560,11 +3562,11 @@ __metadata:
linkType: hard
"@types/node@npm:*":
- version: 24.6.0
- resolution: "@types/node@npm:24.6.0"
+ version: 24.8.0
+ resolution: "@types/node@npm:24.8.0"
dependencies:
- undici-types: ~7.13.0
- checksum: 5550f2c93824f9dde6b4277f2774a833f5d276e3ebd5cf2f071d76553dff6fb896965fccc0ebed79374720034f24936841a7c544188f4d3d9ae52dc00b7aa63b
+ undici-types: ~7.14.0
+ checksum: 34a0bff50f96c704310fdcab6e2f04e67745a4ead63faadaeea578b306c59b192d19160b42fa69f59976206e6696a45363c395298d8b3a1eb0caa50750ecd5e2
languageName: node
linkType: hard
@@ -3583,11 +3585,11 @@ __metadata:
linkType: hard
"@types/react@npm:^19.1.0":
- version: 19.1.16
- resolution: "@types/react@npm:19.1.16"
+ version: 19.2.2
+ resolution: "@types/react@npm:19.2.2"
dependencies:
csstype: ^3.0.2
- checksum: 2c58c6be656b111774da23b98d9f797ab5befb1e0a7900c698f04701de66977c36d5a9c989b10b2da1d575ca9a45a6f3bf75292a0ad24a0c91457549cd65f9e6
+ checksum: 7eb2d316dd5a6c02acb416524b50bae932c38d055d26e0f561ca23c009c686d16a2b22fcbb941eecbe2ecb167f119e29b9d0142d9d056dd381352c43413b60da
languageName: node
linkType: hard
@@ -4027,12 +4029,13 @@ __metadata:
linkType: hard
"arktype@npm:^2.1.15":
- version: 2.1.22
- resolution: "arktype@npm:2.1.22"
+ version: 2.1.23
+ resolution: "arktype@npm:2.1.23"
dependencies:
- "@ark/schema": 0.49.0
- "@ark/util": 0.49.0
- checksum: 46947539b550912f709908bcb973114607a8d61124f7f4ea1090bcaab85ca5c49d68afd6928bf05ce80fe403b6906e7d31d58ed346b408bb8519b9ffdf08e0cb
+ "@ark/regex": 0.0.0
+ "@ark/schema": 0.50.0
+ "@ark/util": 0.50.0
+ checksum: 5874ef1c0140aff0a99cd88537e11851b4d0a1a49ee7b097eb766c941f9de6bbd04427bbda8023c69171db0ee02c7d99f08e59114b63fa83a93c4130964fb616
languageName: node
linkType: hard
@@ -4360,12 +4363,12 @@ __metadata:
languageName: node
linkType: hard
-"baseline-browser-mapping@npm:^2.8.3":
- version: 2.8.9
- resolution: "baseline-browser-mapping@npm:2.8.9"
+"baseline-browser-mapping@npm:^2.8.9":
+ version: 2.8.16
+ resolution: "baseline-browser-mapping@npm:2.8.16"
bin:
baseline-browser-mapping: dist/cli.js
- checksum: ad426d6e239ffaad388e126c52263c0bf4248b9482b56db219f7c866a3f5a6cef39a5c08b7ace1bdf3f87f26ac96b0e9f9bfc199fc148d838ea68e43e3104d92
+ checksum: 929b3f2b5f4beb3b9e02b660a4a8b24f62e43cc7efdc38a726ef061137f9529d3372e1c70298eacde191ac70f5defd21ae4a29ff9e470c4c2b7fd7be68320241
languageName: node
linkType: hard
@@ -4442,18 +4445,18 @@ __metadata:
languageName: node
linkType: hard
-"browserslist@npm:^4.20.4, browserslist@npm:^4.24.0, browserslist@npm:^4.25.3":
- version: 4.26.2
- resolution: "browserslist@npm:4.26.2"
+"browserslist@npm:^4.20.4, browserslist@npm:^4.24.0, browserslist@npm:^4.26.3":
+ version: 4.26.3
+ resolution: "browserslist@npm:4.26.3"
dependencies:
- baseline-browser-mapping: ^2.8.3
- caniuse-lite: ^1.0.30001741
- electron-to-chromium: ^1.5.218
+ baseline-browser-mapping: ^2.8.9
+ caniuse-lite: ^1.0.30001746
+ electron-to-chromium: ^1.5.227
node-releases: ^2.0.21
update-browserslist-db: ^1.1.3
bin:
browserslist: cli.js
- checksum: ebd96e8895cdfc72be074281eb377332b69ceb944ec0c063739d8eeb8e513b168ac1e27d26ce5cc260e69a340a44c6bb5e9408565449d7a16739e5844453d4c7
+ checksum: aa5bbcda9db1eeb9952b4c2f11f9a5a2247da7bcce7fa14d3cc215e67246a93394eda2f86378a41c3f73e6e1a1561bf0e7eade93c5392cb6d37bc66f70d0c53f
languageName: node
linkType: hard
@@ -4597,10 +4600,10 @@ __metadata:
languageName: node
linkType: hard
-"caniuse-lite@npm:^1.0.30001741":
- version: 1.0.30001746
- resolution: "caniuse-lite@npm:1.0.30001746"
- checksum: 3d2a310b49bc414d87184a73ca7c3b1aea0a1547cc9e3bfd8a6ee45129d269917a94b8e773e7b2821cfc3b9e1256759a34e4b7242c56e3f70f575213baa6c880
+"caniuse-lite@npm:^1.0.30001746":
+ version: 1.0.30001751
+ resolution: "caniuse-lite@npm:1.0.30001751"
+ checksum: d11e25c44e40c21e7b7492a25c9fd60f4c04e94aa265573f7c487666f5e1b5ca3ed09d09560336f959237063616255cb294d415511bb6cf0486eb2cb6a3a4318
languageName: node
linkType: hard
@@ -4694,9 +4697,9 @@ __metadata:
linkType: hard
"ci-info@npm:^4.3.0":
- version: 4.3.0
- resolution: "ci-info@npm:4.3.0"
- checksum: 77a851ec826e1fbcd993e0e3ef402e6a5e499c733c475af056b7808dea9c9ede53e560ed433020489a8efea2d824fd68ca203446c9988a0bac8475210b0d4491
+ version: 4.3.1
+ resolution: "ci-info@npm:4.3.1"
+ checksum: 66c159d92648e8a07acab0a3a0681bff6ccc39aa44916263208c4d97bbbeedbbc886d7611fd30c21df1aa624ce3c6fcdfde982e74689e3e014e064e1d0805f94
languageName: node
linkType: hard
@@ -4799,9 +4802,9 @@ __metadata:
linkType: hard
"collect-v8-coverage@npm:^1.0.0":
- version: 1.0.2
- resolution: "collect-v8-coverage@npm:1.0.2"
- checksum: c10f41c39ab84629d16f9f6137bc8a63d332244383fc368caf2d2052b5e04c20cd1fd70f66fcf4e2422b84c8226598b776d39d5f2d2a51867cc1ed5d1982b4da
+ version: 1.0.3
+ resolution: "collect-v8-coverage@npm:1.0.3"
+ checksum: ed1d1ebc9c05e7263fffa3ad6440031db6a1fdd9f574435aa689effcdfe9f2b93aba8ec600f9c7b99124cd6ff5d9415c17961d84ae829a72251a4fe668a49b63
languageName: node
linkType: hard
@@ -5170,24 +5173,24 @@ __metadata:
linkType: hard
"core-js-compat@npm:^3.43.0":
- version: 3.45.1
- resolution: "core-js-compat@npm:3.45.1"
+ version: 3.46.0
+ resolution: "core-js-compat@npm:3.46.0"
dependencies:
- browserslist: ^4.25.3
- checksum: 817286f6b7deb90278fd1f46131664fda36b74983e2fc4859a36ae85ef9361868b307964eea0e364251763e415eab7589e9abe2a4ec4d1672c2870f03c52b1ac
+ browserslist: ^4.26.3
+ checksum: 16d381c51e34d38ecc65d429d5a5c1dbd198f70b5a0a6256a3a41dcb8523e07f0a8682f6349298a55ff6e9d039e131d67b07fe863047a28672ae5f10373c57cf
languageName: node
linkType: hard
"cosmiconfig-typescript-loader@npm:^6.1.0":
- version: 6.1.0
- resolution: "cosmiconfig-typescript-loader@npm:6.1.0"
+ version: 6.2.0
+ resolution: "cosmiconfig-typescript-loader@npm:6.2.0"
dependencies:
- jiti: ^2.4.1
+ jiti: ^2.6.1
peerDependencies:
"@types/node": "*"
cosmiconfig: ">=9"
typescript: ">=5"
- checksum: 45114854faaa97178abd2ccad511363faa57c03321c7e39ad16619c63842b3f6147dd20118f9f07c9530a242a39c3107c791708bb0b987dad374e71f23f9468b
+ checksum: 2680bb585de1185aa23ba678cb0426cba1be8fa0a9d286f71c2ce5bd63f23e5b8f726161673a16babb2aa0e7d033fda8774268a025fb63f548d1c75977292212
languageName: node
linkType: hard
@@ -5569,10 +5572,10 @@ __metadata:
languageName: node
linkType: hard
-"electron-to-chromium@npm:^1.5.218":
- version: 1.5.227
- resolution: "electron-to-chromium@npm:1.5.227"
- checksum: 6a798b53216b300f20dbad56c0ef3777da59cb37a761c169fbf8b1dcbeca74ccf137f83a5b3b959ae82cf3cd454aae1d4fda8c7dabd99a72bef645ac18fa6a45
+"electron-to-chromium@npm:^1.5.227":
+ version: 1.5.237
+ resolution: "electron-to-chromium@npm:1.5.237"
+ checksum: 5905e2808dc6243ced0a83537afbafedec20c063feb6403a678b612a7855d79bc6ecb7d094bdab71f54173cf2ae5d1d8070b0c31572025001c94de62af84f5f8
languageName: node
linkType: hard
@@ -5637,11 +5640,11 @@ __metadata:
linkType: hard
"envinfo@npm:^7.13.0":
- version: 7.15.0
- resolution: "envinfo@npm:7.15.0"
+ version: 7.18.0
+ resolution: "envinfo@npm:7.18.0"
bin:
envinfo: dist/cli.js
- checksum: 38595c11134ecb66a40289980d8ca82e89fdcd68849dd72560c1bbc3cfc55c867573b4150967707ff9ff2e5cad6f1d0cb6cc56c333a6eccdcd3533452141c0a8
+ checksum: 1a4d0094d9aede737e3a1ea59756d6dff440ba2a7e87509cae3a8f69ef8748cba952e9a6c0f66e54bc12a90902d32a082b0934dfc156ca4252e18fe5a0c4d11d
languageName: node
linkType: hard
@@ -6063,17 +6066,17 @@ __metadata:
linkType: hard
"eslint@npm:^9.35.0":
- version: 9.36.0
- resolution: "eslint@npm:9.36.0"
+ version: 9.37.0
+ resolution: "eslint@npm:9.37.0"
dependencies:
"@eslint-community/eslint-utils": ^4.8.0
"@eslint-community/regexpp": ^4.12.1
"@eslint/config-array": ^0.21.0
- "@eslint/config-helpers": ^0.3.1
- "@eslint/core": ^0.15.2
+ "@eslint/config-helpers": ^0.4.0
+ "@eslint/core": ^0.16.0
"@eslint/eslintrc": ^3.3.1
- "@eslint/js": 9.36.0
- "@eslint/plugin-kit": ^0.3.5
+ "@eslint/js": 9.37.0
+ "@eslint/plugin-kit": ^0.4.0
"@humanfs/node": ^0.16.6
"@humanwhocodes/module-importer": ^1.0.1
"@humanwhocodes/retry": ^0.4.2
@@ -6108,7 +6111,7 @@ __metadata:
optional: true
bin:
eslint: bin/eslint.js
- checksum: 08a02a1d474cf7ea63ef9328e638751c939a1c08b99f7812f0f44a96e3b8346ab3bbca3af57da8b3e74cbc6619e41645fd3dcb3adda94d1cb826f02664e2d44c
+ checksum: 78e813174acef58d361d557a4d083d2d03f20cd70dd96f59973414305acaedf72bad52271c789174a19ee0407f8bece017ce42a05c89014b93e457d033285aeb
languageName: node
linkType: hard
@@ -6265,9 +6268,9 @@ __metadata:
linkType: hard
"exponential-backoff@npm:^3.1.1":
- version: 3.1.2
- resolution: "exponential-backoff@npm:3.1.2"
- checksum: 7e191e3dd6edd8c56c88f2c8037c98fbb8034fe48778be53ed8cb30ccef371a061a4e999a469aab939b92f8f12698f3b426d52f4f76b7a20da5f9f98c3cbc862
+ version: 3.1.3
+ resolution: "exponential-backoff@npm:3.1.3"
+ checksum: 471fdb70fd3d2c08a74a026973bdd4105b7832911f610ca67bbb74e39279411c1eed2f2a110c9d41c2edd89459ba58fdaba1c174beed73e7a42d773882dcff82
languageName: node
linkType: hard
@@ -6581,9 +6584,9 @@ __metadata:
linkType: hard
"generator-function@npm:^2.0.0":
- version: 2.0.0
- resolution: "generator-function@npm:2.0.0"
- checksum: 12b5ca9c9cb21196aa4d8a53de3104956a917e806e00b0370d442fcd1142e3ae2f9401211204b951352bf22daf0373dd055d080cf6b9392d8c1281fe497c5de9
+ version: 2.0.1
+ resolution: "generator-function@npm:2.0.1"
+ checksum: 3bf87f7b0230de5d74529677e6c3ceb3b7b5d9618b5a22d92b45ce3876defbaf5a77791b25a61b0fa7d13f95675b5ff67a7769f3b9af33f096e34653519e873d
languageName: node
linkType: hard
@@ -7432,15 +7435,15 @@ __metadata:
linkType: hard
"is-generator-function@npm:^1.0.10":
- version: 1.1.1
- resolution: "is-generator-function@npm:1.1.1"
+ version: 1.1.2
+ resolution: "is-generator-function@npm:1.1.2"
dependencies:
- call-bound: ^1.0.3
+ call-bound: ^1.0.4
generator-function: ^2.0.0
- get-proto: ^1.0.0
+ get-proto: ^1.0.1
has-tostringtag: ^1.0.2
safe-regex-test: ^1.1.0
- checksum: 194a7a1654ec8ae1e49df4f6774956e686fba7ba7dbe545de5ba9cd06bb93e2c4c0bfe6924f5211b800d3fa16cb151cd272d1da01d0392d07317a6b4900b923a
+ checksum: 0b81c613752a5e534939e5b3835ff722446837a5b94c3a3934af5ded36a651d9aa31c3f11f8a3453884b9658bf26dbfb7eb855e744d920b07f084bd890a43414
languageName: node
linkType: hard
@@ -8309,12 +8312,12 @@ __metadata:
languageName: node
linkType: hard
-"jiti@npm:^2.4.1, jiti@npm:^2.5.1":
- version: 2.6.0
- resolution: "jiti@npm:2.6.0"
+"jiti@npm:^2.5.1, jiti@npm:^2.6.1":
+ version: 2.6.1
+ resolution: "jiti@npm:2.6.1"
bin:
jiti: lib/jiti-cli.mjs
- checksum: 2bd869527bfbb23b5210344881b4f2f5fd86b7c9c703001036544762411af73fe0f95097ba025a738874085143939664173360aafea7d7cbc4ca3bbc325774a9
+ checksum: 9394e29c5e40d1ca8267923160d8d86706173c9ff30c901097883434b0c4866de2c060427b6a9a5843bb3e42fa3a3c8b5b2228531d3dd4f4f10c5c6af355bb86
languageName: node
linkType: hard
@@ -8838,69 +8841,69 @@ __metadata:
languageName: node
linkType: hard
-"metro-babel-transformer@npm:0.83.2":
- version: 0.83.2
- resolution: "metro-babel-transformer@npm:0.83.2"
+"metro-babel-transformer@npm:0.83.3":
+ version: 0.83.3
+ resolution: "metro-babel-transformer@npm:0.83.3"
dependencies:
"@babel/core": ^7.25.2
flow-enums-runtime: ^0.0.6
hermes-parser: 0.32.0
nullthrows: ^1.1.1
- checksum: 8ca98216c3fc32757cbb445d2e42042617b5a2399d3d409759b168fbd3d52aadf8bb2b8471e4b204ddf5c654b7b146397edb7693f48a0582e7e4e169cf3bbfbb
+ checksum: dd178409d1718dae12dfffb6572ebc5bb78f1e0d7e93dce829c945957f8a686cb1b4c466c69585d7b982b3937fbea28d5c53a80691f2fc66717a0bcc800bc5b8
languageName: node
linkType: hard
-"metro-cache-key@npm:0.83.2":
- version: 0.83.2
- resolution: "metro-cache-key@npm:0.83.2"
+"metro-cache-key@npm:0.83.3":
+ version: 0.83.3
+ resolution: "metro-cache-key@npm:0.83.3"
dependencies:
flow-enums-runtime: ^0.0.6
- checksum: ad60492b1db35b7d4eb1f9ed6f8aa79a051dcb1be3183fcd5b0a810e7c4ba5dba5e9f02e131ccd271d6db2efaa9893ef0e316ef26ebb3ab49cb074fada4de1b5
+ checksum: a6f9d2bf8b810f57d330d6f8f1ebf029e1224f426c5895f73d9bc1007482684048bfc7513a855626ee7f3ae72ca46e1b08cf983aefbfa84321bb7c0cef4ba4ae
languageName: node
linkType: hard
-"metro-cache@npm:0.83.2":
- version: 0.83.2
- resolution: "metro-cache@npm:0.83.2"
+"metro-cache@npm:0.83.3":
+ version: 0.83.3
+ resolution: "metro-cache@npm:0.83.3"
dependencies:
exponential-backoff: ^3.1.1
flow-enums-runtime: ^0.0.6
https-proxy-agent: ^7.0.5
- metro-core: 0.83.2
- checksum: 29e914de2c3da88f94a5cb2708cb87ea1a1d7dba73a0f0f45d974e36e635132190a00330803cc8226e784700322576e68b96c52a03d10725d3a7afbf3a5845df
+ metro-core: 0.83.3
+ checksum: 95606275411d85de071fd95171a9548406cd1154320850a554bf00207804f7844ed252f9750a802d6612ade839c579b23bd87927ae173f43c368e8f5d900149d
languageName: node
linkType: hard
-"metro-config@npm:0.83.2, metro-config@npm:^0.83.1":
- version: 0.83.2
- resolution: "metro-config@npm:0.83.2"
+"metro-config@npm:0.83.3, metro-config@npm:^0.83.1":
+ version: 0.83.3
+ resolution: "metro-config@npm:0.83.3"
dependencies:
connect: ^3.6.5
flow-enums-runtime: ^0.0.6
jest-validate: ^29.7.0
- metro: 0.83.2
- metro-cache: 0.83.2
- metro-core: 0.83.2
- metro-runtime: 0.83.2
+ metro: 0.83.3
+ metro-cache: 0.83.3
+ metro-core: 0.83.3
+ metro-runtime: 0.83.3
yaml: ^2.6.1
- checksum: d8b8ddd0ce77cf6c1173288af1b38676918d6465b8542061a6be6ff61022d0363ae0479a58fc343baac812b38b4876e22d0a50a97d1207ea44cffa7bbc893aa0
+ checksum: a14b77668a9712abbcebe5bf6a0081f0fd46caf8d37405174f261765abcd44d7a99910533fcc05edde3de10f9b22820cc9910c7dee2b01e761692a0a322f2608
languageName: node
linkType: hard
-"metro-core@npm:0.83.2, metro-core@npm:^0.83.1":
- version: 0.83.2
- resolution: "metro-core@npm:0.83.2"
+"metro-core@npm:0.83.3, metro-core@npm:^0.83.1":
+ version: 0.83.3
+ resolution: "metro-core@npm:0.83.3"
dependencies:
flow-enums-runtime: ^0.0.6
lodash.throttle: ^4.1.1
- metro-resolver: 0.83.2
- checksum: 58ce33dcfe0b5803aadd1681b37bf51b481582437738afed701b124da77bf476e082124da8c2b60161f15290043ecc8086c51fdc44f241fcc3bb9d7887fffd0e
+ metro-resolver: 0.83.3
+ checksum: d06871313310cd718094ecbae805bcacea3f325340f6dff3c5044b62457c4690dd729cdb938349bdd3c41efa6f28032ae07696467ef006d5509fec9045c1966f
languageName: node
linkType: hard
-"metro-file-map@npm:0.83.2":
- version: 0.83.2
- resolution: "metro-file-map@npm:0.83.2"
+"metro-file-map@npm:0.83.3":
+ version: 0.83.3
+ resolution: "metro-file-map@npm:0.83.3"
dependencies:
debug: ^4.4.0
fb-watchman: ^2.0.0
@@ -8911,76 +8914,76 @@ __metadata:
micromatch: ^4.0.4
nullthrows: ^1.1.1
walker: ^1.0.7
- checksum: 16ea37fa9c252686aafd1bc5fc5d4791273ff1be606303582035d52865b2ff16f1f13fc0a867c5b2385479563f748e0ee96b6fb83d16e739e413e60c0e22a079
+ checksum: 0dea599206e93b6e8628be2aa98452d4dae16e805b810759ec8b50cebcd83f2d053f7e5865196d464f3793f86b3b5003830c6713f91bf62fa406a4af7c93a776
languageName: node
linkType: hard
-"metro-minify-terser@npm:0.83.2":
- version: 0.83.2
- resolution: "metro-minify-terser@npm:0.83.2"
+"metro-minify-terser@npm:0.83.3":
+ version: 0.83.3
+ resolution: "metro-minify-terser@npm:0.83.3"
dependencies:
flow-enums-runtime: ^0.0.6
terser: ^5.15.0
- checksum: ee164bdd3ddf797e1b0f9fd71960b662b40fc3abead77521b1e1435291d38cc151442348362d6afee0596d52fcff48cc6a055a04a7928905e9557968e05293ac
+ checksum: 1de88b70b7c903147807baa46497491a87600594fd0868b6538bbb9d7785242cabfbe8bccf36cc2285d0e17be72445b512d00c496952a159572545f3e6bcb199
languageName: node
linkType: hard
-"metro-resolver@npm:0.83.2":
- version: 0.83.2
- resolution: "metro-resolver@npm:0.83.2"
+"metro-resolver@npm:0.83.3":
+ version: 0.83.3
+ resolution: "metro-resolver@npm:0.83.3"
dependencies:
flow-enums-runtime: ^0.0.6
- checksum: f3b97ac389c7cbf624db1558a07e48d3e8be5f581c010a3a1d26f8a5ef95ab9ba14bb959d4102da4e637eb66643f178499348e60d06f6cce7fa3068ecb5fd3d6
+ checksum: de2ae5ced6239b004a97712f98934c6e830870d11614e2dba48250930214581f0746df8a4f0f1cb71060fe21c2cf919d3359106ad4f375c2500ba08e10922896
languageName: node
linkType: hard
-"metro-runtime@npm:0.83.2, metro-runtime@npm:^0.83.1":
- version: 0.83.2
- resolution: "metro-runtime@npm:0.83.2"
+"metro-runtime@npm:0.83.3, metro-runtime@npm:^0.83.1":
+ version: 0.83.3
+ resolution: "metro-runtime@npm:0.83.3"
dependencies:
"@babel/runtime": ^7.25.0
flow-enums-runtime: ^0.0.6
- checksum: 1868bffbb7dc8a9c69a2d480d7d8e1019548f68522f9368f5513aa9325c39ed9dfaae052cfe0209cb03bc70a908e08d72eb852e1cff56bc6f32a73c8dc92a5ff
+ checksum: dcbdc5502020d1e20cee1a3a8019323ab2f3ca2aa2d6ddb2b7a2b8547835a20b84fe4afc23c397f788584e108c70411db93df2f61322b44a4f0f119275052d03
languageName: node
linkType: hard
-"metro-source-map@npm:0.83.2, metro-source-map@npm:^0.83.1":
- version: 0.83.2
- resolution: "metro-source-map@npm:0.83.2"
+"metro-source-map@npm:0.83.3, metro-source-map@npm:^0.83.1":
+ version: 0.83.3
+ resolution: "metro-source-map@npm:0.83.3"
dependencies:
"@babel/traverse": ^7.25.3
"@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3"
"@babel/types": ^7.25.2
flow-enums-runtime: ^0.0.6
invariant: ^2.2.4
- metro-symbolicate: 0.83.2
+ metro-symbolicate: 0.83.3
nullthrows: ^1.1.1
- ob1: 0.83.2
+ ob1: 0.83.3
source-map: ^0.5.6
vlq: ^1.0.0
- checksum: 50dc6eebc0a6d36c8a93acc57cc0311cbf0485a0b1fdb81c265c8950afefcf16b7cfb56e2dbb211a04bd0fa59b5a0369cd2e7499ea489ce6f98719aa88b2d097
+ checksum: 5bf3b7a1561bc1f0ad6ab3b7b550d4b4581da31964a7f218727a3201576912076c909a2e50fba4dd3c649d79312324dec683a37228f4559811c37b69ecca8831
languageName: node
linkType: hard
-"metro-symbolicate@npm:0.83.2":
- version: 0.83.2
- resolution: "metro-symbolicate@npm:0.83.2"
+"metro-symbolicate@npm:0.83.3":
+ version: 0.83.3
+ resolution: "metro-symbolicate@npm:0.83.3"
dependencies:
flow-enums-runtime: ^0.0.6
invariant: ^2.2.4
- metro-source-map: 0.83.2
+ metro-source-map: 0.83.3
nullthrows: ^1.1.1
source-map: ^0.5.6
vlq: ^1.0.0
bin:
metro-symbolicate: src/index.js
- checksum: fdf5a0d35dfad39d9cda8beda85f09f26e4ae662cbd05623492574299dde3660561502f54396cce3b25818a9079219d1fdbd217c5000619b8d14d6357739a59c
+ checksum: 943cc2456d56ae2ed8369495c18966d91feff636b37909b5225ffb8ce2a50eba8fbedf116f3bea3059d431ebc621c9c9af8a8bfd181b0cd1fece051507e10ffd
languageName: node
linkType: hard
-"metro-transform-plugins@npm:0.83.2":
- version: 0.83.2
- resolution: "metro-transform-plugins@npm:0.83.2"
+"metro-transform-plugins@npm:0.83.3":
+ version: 0.83.3
+ resolution: "metro-transform-plugins@npm:0.83.3"
dependencies:
"@babel/core": ^7.25.2
"@babel/generator": ^7.25.0
@@ -8988,34 +8991,34 @@ __metadata:
"@babel/traverse": ^7.25.3
flow-enums-runtime: ^0.0.6
nullthrows: ^1.1.1
- checksum: 455cf6811172351ed61ae498f2fed20a1830b23a47d591066bcd1bf52f9b0cc7d0daf8c97ffedc0e0b1e5a7d2da65d16fac869a3c09d0e84ac4ffa5df0777ccb
+ checksum: 6f92b9dfa53bdb63e79038bbd4d68791379ab26cf874679e64563618c578eeed3a828795debf8076ffd518431dff53191990784fb619046bcc03fff114b0cb21
languageName: node
linkType: hard
-"metro-transform-worker@npm:0.83.2":
- version: 0.83.2
- resolution: "metro-transform-worker@npm:0.83.2"
+"metro-transform-worker@npm:0.83.3":
+ version: 0.83.3
+ resolution: "metro-transform-worker@npm:0.83.3"
dependencies:
"@babel/core": ^7.25.2
"@babel/generator": ^7.25.0
"@babel/parser": ^7.25.3
"@babel/types": ^7.25.2
flow-enums-runtime: ^0.0.6
- metro: 0.83.2
- metro-babel-transformer: 0.83.2
- metro-cache: 0.83.2
- metro-cache-key: 0.83.2
- metro-minify-terser: 0.83.2
- metro-source-map: 0.83.2
- metro-transform-plugins: 0.83.2
+ metro: 0.83.3
+ metro-babel-transformer: 0.83.3
+ metro-cache: 0.83.3
+ metro-cache-key: 0.83.3
+ metro-minify-terser: 0.83.3
+ metro-source-map: 0.83.3
+ metro-transform-plugins: 0.83.3
nullthrows: ^1.1.1
- checksum: 955e4f8f190151e62c75167168d85c4cde2cfb5121e72f9f7459ba371f3ce41d131ec3bb6c2d0097c036f66a38183ecdd383375648c29736c2345c45f6f4d4e9
+ checksum: fcb25ebc1ce703d830ef60c9af87325f996af4c3946325ab957b65ca59d12d181fe6c527c9ba1f932cd954d23a400052293117fe56f9a2727dfbc0a118e7bb27
languageName: node
linkType: hard
-"metro@npm:0.83.2, metro@npm:^0.83.1":
- version: 0.83.2
- resolution: "metro@npm:0.83.2"
+"metro@npm:0.83.3, metro@npm:^0.83.1":
+ version: 0.83.3
+ resolution: "metro@npm:0.83.3"
dependencies:
"@babel/code-frame": ^7.24.7
"@babel/core": ^7.25.2
@@ -9038,18 +9041,18 @@ __metadata:
jest-worker: ^29.7.0
jsc-safe-url: ^0.2.2
lodash.throttle: ^4.1.1
- metro-babel-transformer: 0.83.2
- metro-cache: 0.83.2
- metro-cache-key: 0.83.2
- metro-config: 0.83.2
- metro-core: 0.83.2
- metro-file-map: 0.83.2
- metro-resolver: 0.83.2
- metro-runtime: 0.83.2
- metro-source-map: 0.83.2
- metro-symbolicate: 0.83.2
- metro-transform-plugins: 0.83.2
- metro-transform-worker: 0.83.2
+ metro-babel-transformer: 0.83.3
+ metro-cache: 0.83.3
+ metro-cache-key: 0.83.3
+ metro-config: 0.83.3
+ metro-core: 0.83.3
+ metro-file-map: 0.83.3
+ metro-resolver: 0.83.3
+ metro-runtime: 0.83.3
+ metro-source-map: 0.83.3
+ metro-symbolicate: 0.83.3
+ metro-transform-plugins: 0.83.3
+ metro-transform-worker: 0.83.3
mime-types: ^2.1.27
nullthrows: ^1.1.1
serialize-error: ^2.1.0
@@ -9059,7 +9062,7 @@ __metadata:
yargs: ^17.6.2
bin:
metro: src/cli.js
- checksum: 0f2ddde7644f58f1f7580e665e4ea627a8329e73a5c595892cae7d91f5568e0c70e6f8d3cec85db35db5171991a42e265e7615091ef7b78b4a49f321be6da785
+ checksum: 306d8c06b5a1a45e18df6e41f494bbc8b439700985429284eea7b3c3c82108e3c3795d859a8ab3ed7a85793d64e3160519be9aa84c6418d6ed37bd5ae4500b57
languageName: node
linkType: hard
@@ -9350,8 +9353,8 @@ __metadata:
linkType: hard
"node-gyp@npm:latest":
- version: 11.4.2
- resolution: "node-gyp@npm:11.4.2"
+ version: 11.5.0
+ resolution: "node-gyp@npm:11.5.0"
dependencies:
env-paths: ^2.2.0
exponential-backoff: ^3.1.1
@@ -9365,7 +9368,7 @@ __metadata:
which: ^5.0.0
bin:
node-gyp: bin/node-gyp.js
- checksum: d8041cee7ec60c86fb2961d77c12a2d083a481fb28b08e6d9583153186c0e7766044dc30bdb1f3ac01ddc5763b83caeed3d1ea35787ec4ffd8cc4aeedfc34f2b
+ checksum: 6cc29b9d454d9a684c8fe299668db618875bb4282e37717ca5b79689cc5ce99cd553c70944bb367979f2eba40ad6a50afaf7b12a6b214172edc7377384efa051
languageName: node
linkType: hard
@@ -9377,9 +9380,9 @@ __metadata:
linkType: hard
"node-releases@npm:^2.0.21":
- version: 2.0.21
- resolution: "node-releases@npm:2.0.21"
- checksum: 191f8245e18272971650eb45151c5891313bca27507a8f634085bd8c98a9cb9492686ef6182176866ceebff049646ef6cd5fb5ca46d5b5ca00ce2c69185d84c4
+ version: 2.0.25
+ resolution: "node-releases@npm:2.0.25"
+ checksum: 9a23149cf3f6778e62440b1f26f91927aff06c3606a29996f3d196c7c0f5e31c17c24c324b5ef1f571cebef6b5a8db9adce9c09381ca271bc6422aac91463f75
languageName: node
linkType: hard
@@ -9459,12 +9462,12 @@ __metadata:
languageName: node
linkType: hard
-"ob1@npm:0.83.2":
- version: 0.83.2
- resolution: "ob1@npm:0.83.2"
+"ob1@npm:0.83.3":
+ version: 0.83.3
+ resolution: "ob1@npm:0.83.3"
dependencies:
flow-enums-runtime: ^0.0.6
- checksum: 8eb482589b66cf46600d1231c2ea50a365f47ee5db0274795d1d3f5c43112e255b931a41ce1ef8a220f31b4fb985fb269c6a54bf7e9719f90dac3f4001a89a6c
+ checksum: 20dfe91d48d0cadd97159cfd53f5abdca435b55d58b1f562e0687485e8f44f8a95e8ab3c835badd13d0d8c01e3d7b14d639a316aa4bf82841ac78b49611d4e5c
languageName: node
linkType: hard
@@ -10764,7 +10767,7 @@ __metadata:
languageName: node
linkType: hard
-"semver@npm:7.7.2, semver@npm:^7.1.3, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3":
+"semver@npm:7.7.2":
version: 7.7.2
resolution: "semver@npm:7.7.2"
bin:
@@ -10782,6 +10785,15 @@ __metadata:
languageName: node
linkType: hard
+"semver@npm:^7.1.3, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3":
+ version: 7.7.3
+ resolution: "semver@npm:7.7.3"
+ bin:
+ semver: bin/semver.js
+ checksum: f013a3ee4607857bcd3503b6ac1d80165f7f8ea94f5d55e2d3e33df82fce487aa3313b987abf9b39e0793c83c9fc67b76c36c067625141a9f6f704ae0ea18db2
+ languageName: node
+ linkType: hard
+
"send@npm:0.19.0":
version: 0.19.0
resolution: "send@npm:0.19.0"
@@ -11707,22 +11719,22 @@ __metadata:
linkType: hard
"typescript@npm:^5.9.2":
- version: 5.9.2
- resolution: "typescript@npm:5.9.2"
+ version: 5.9.3
+ resolution: "typescript@npm:5.9.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: f619cf6773cfe31409279711afd68cdf0859780006c50bc2a7a0c3227f85dea89a3b97248846326f3a17dad72ea90ec27cf61a8387772c680b2252fd02d8497b
+ checksum: 0d0ffb84f2cd072c3e164c79a2e5a1a1f4f168e84cb2882ff8967b92afe1def6c2a91f6838fb58b168428f9458c57a2ba06a6737711fdd87a256bbe83e9a217f
languageName: node
linkType: hard
"typescript@patch:typescript@^5.9.2#~builtin":
- version: 5.9.2
- resolution: "typescript@patch:typescript@npm%3A5.9.2#~builtin::version=5.9.2&hash=14eedb"
+ version: 5.9.3
+ resolution: "typescript@patch:typescript@npm%3A5.9.3#~builtin::version=5.9.3&hash=14eedb"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
- checksum: e42a701947325500008334622321a6ad073f842f5e7d5e7b588a6346b31fdf51d56082b9ce5cef24312ecd3e48d6c0d4d44da7555f65e2feec18cf62ec540385
+ checksum: 8bb8d86819ac86a498eada254cad7fb69c5f74778506c700c2a712daeaff21d3a6f51fd0d534fe16903cb010d1b74f89437a3d02d4d0ff5ca2ba9a4660de8497
languageName: node
linkType: hard
@@ -11754,10 +11766,10 @@ __metadata:
languageName: node
linkType: hard
-"undici-types@npm:~7.13.0":
- version: 7.13.0
- resolution: "undici-types@npm:7.13.0"
- checksum: fcb3e1195a36615fce3935eb97c21ebe4dbafe968f831ed00e6f22e8e73c0655b8e3242acc6ba4ff0f3c34e3f3f860f19fbb59c00b261bd4e20b515abbc2de7c
+"undici-types@npm:~7.14.0":
+ version: 7.14.0
+ resolution: "undici-types@npm:7.14.0"
+ checksum: bd28cb36b33a51359f02c27b84bfe8563cdad57bdab0aa6ac605ce64d51aff49fd0aa4cb2d3b043caaa93c3ec42e96b5757df5d2d9bcc06a5f3e71899c765035
languageName: node
linkType: hard