feat: Passwordless cross-device authentication

- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
This commit is contained in:
Bernt
2026-07-07 07:11:50 +00:00
parent 4aa984ad74
commit 6989a98d75
61843 changed files with 5491611 additions and 872231 deletions
@@ -285,7 +285,7 @@ export default function quiXzoomApp() {
{/* GPS Status */}
<View style={styles.gpsBadge}>
<Text style={styles.gpsText}>
{location ? '📍 GPS Ready' : '📍 Getting GPS...'}
{location ? 'GPS Ready' : 'Getting GPS...'}
</Text>
</View>
@@ -293,7 +293,7 @@ export default function quiXzoomApp() {
{activeMission && (
<View style={styles.missionBadge}>
<Text style={styles.missionText}>
🎯 {activeMission.title}
{activeMission.title}
</Text>
<Text style={styles.missionReward}>
${activeMission.reward_usd}
@@ -305,7 +305,7 @@ export default function quiXzoomApp() {
{queueCount > 0 && (
<View style={styles.queueBadge}>
<Text style={styles.queueText}>
📤 {queueCount} in queue
{queueCount} in queue
</Text>
</View>
)}
@@ -336,7 +336,7 @@ export default function quiXzoomApp() {
{/* Location Info */}
{location && (
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>📍 Location</Text>
<Text style={styles.infoTitle}>Location</Text>
<Text style={styles.infoText}>
Lat: {location.latitude.toFixed(6)}
</Text>
@@ -352,7 +352,7 @@ export default function quiXzoomApp() {
{/* Evidence */}
{evidence && (
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>🔍 Evidence</Text>
<Text style={styles.infoTitle}>Evidence</Text>
<Text style={styles.infoText}>
Visual Objects: {evidence.evidence_summary?.visual_objects || 0}
</Text>
@@ -371,7 +371,7 @@ export default function quiXzoomApp() {
style={styles.submitButton}
onPress={submitObservation}
>
<Text style={styles.submitText}> Submit Observation</Text>
<Text style={styles.submitText}>Submit Observation</Text>
</TouchableOpacity>
<TouchableOpacity
@@ -381,7 +381,7 @@ export default function quiXzoomApp() {
setEvidence(null);
}}
>
<Text style={styles.retakeText}> Retake</Text>
<Text style={styles.retakeText}>Retake</Text>
</TouchableOpacity>
</View>
</ScrollView>
@@ -392,7 +392,7 @@ export default function quiXzoomApp() {
const renderMissions = () => {
return (
<ScrollView style={styles.missionsContainer}>
<Text style={styles.sectionTitle}>🎯 Available Missions</Text>
<Text style={styles.sectionTitle}>Available Missions</Text>
{missions.map((mission) => (
<TouchableOpacity
@@ -440,7 +440,7 @@ export default function quiXzoomApp() {
{queueCount > 0 && (
<TouchableOpacity onPress={syncQueue}>
<Text style={styles.queueBadge}>
📤 {queueCount}
{queueCount}
</Text>
</TouchableOpacity>
)}
@@ -453,16 +453,16 @@ export default function quiXzoomApp() {
{/* Bottom Navigation */}
<View style={styles.bottomNav}>
<TouchableOpacity style={styles.navButton}>
<Text style={styles.navText}>📷 Capture</Text>
<Text style={styles.navText}>Capture</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navButton}>
<Text style={styles.navText}>🎯 Missions</Text>
<Text style={styles.navText}>Missions</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navButton}>
<Text style={styles.navText}>📊 Stats</Text>
<Text style={styles.navText}>Stats</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navButton}>
<Text style={styles.navText}> Settings</Text>
<Text style={styles.navText}>Settings</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
@@ -0,0 +1,4 @@
{
"name": "quixzoom",
"displayName": "quiXzoom"
}
@@ -0,0 +1,68 @@
{
"name": "quixzoom-app",
"version": "1.0.0",
"description": "quiXzoom Native App — Field data collection for iOS/Android",
"main": "index.js",
"scripts": {
"start": "react-native start",
"android": "react-native run-android",
"ios": "react-native run-ios",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"react": "18.2.0",
"react-native": "0.72.0",
"@react-native-async-storage/async-storage": "^1.19.0",
"@react-native-community/geolocation": "^3.0.0",
"react-native-vision-camera": "^3.0.0",
"react-native-maps": "^1.7.0",
"react-native-vector-icons": "^10.0.0",
"react-native-share": "^9.0.0",
"react-native-fs": "^2.20.0",
"react-native-image-resizer": "^3.0.0",
"react-native-background-upload": "^6.0.0",
"react-native-offline": "^6.0.0",
"react-native-permissions": "^3.8.0",
"react-native-device-info": "^10.0.0",
"react-native-sensors": "^7.3.0",
"react-native-keep-awake": "^4.0.0",
"react-native-haptic-feedback": "^2.0.0",
"react-native-localize": "^3.0.0",
"react-native-keychain": "^8.1.0",
"react-native-encrypted-storage": "^4.0.0",
"@react-navigation/native": "^6.1.0",
"@react-navigation/bottom-tabs": "^6.5.0",
"@react-navigation/stack": "^6.3.0",
"react-native-gesture-handler": "^2.12.0",
"react-native-reanimated": "^3.3.0",
"react-native-screens": "^3.22.0",
"react-native-safe-area-context": "^4.6.0",
"@reduxjs/toolkit": "^1.9.0",
"react-redux": "^8.1.0",
"redux-persist": "^6.0.0",
"axios": "^1.4.0",
"date-fns": "^2.30.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/eslint-config": "^0.72.0",
"@react-native/metro-config": "^0.72.0",
"@tsconfig/react-native": "^3.0.0",
"@types/react": "^18.0.24",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.2.1",
"eslint": "^8.19.0",
"jest": "^29.2.1",
"metro-react-native-babel-preset": "0.76.5",
"prettier": "^2.4.1",
"react-test-renderer": "18.2.0",
"typescript": "4.8.4"
},
"jest": {
"preset": "react-native"
}
}
+6
View File
@@ -0,0 +1,6 @@
node_modules/
.expo/
dist/
.env
*.log
.DS_Store
+22
View File
@@ -0,0 +1,22 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { AppNavigator } from '@/navigation/AppNavigator'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
retry: 2,
},
},
})
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<SafeAreaProvider>
<AppNavigator />
</SafeAreaProvider>
</QueryClientProvider>
)
}
+57 -2
View File
@@ -1,4 +1,59 @@
{
"name": "quixzoom",
"displayName": "quiXzoom"
"expo": {
"name": "quiXzoom",
"slug": "quixzoom",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "dark",
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#0A0A1B"
},
"scheme": "quixzoom",
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.wavult.quixzoom",
"infoPlist": {
"NSCameraUsageDescription": "quiXzoom behöver kameran för att ta uppdragsbilder",
"NSLocationWhenInUseUsageDescription": "quiXzoom behöver din position för att visa uppdrag nära dig och navigera till uppdragsplatser",
"NSLocationAlwaysAndWhenInUseUsageDescription": "quiXzoom behöver din position i bakgrunden för att fortsätta spåra uppdrag även när appen är i bakgrunden",
"NSLocationAlwaysUsageDescription": "quiXzoom behöver din position för att notifiera om uppdrag nära dig",
"UIBackgroundModes": ["location", "remote-notification"]
}
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#0A0A1B"
},
"package": "com.wavult.quixzoom",
"permissions": [
"android.permission.CAMERA",
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION",
"android.permission.ACCESS_BACKGROUND_LOCATION",
"android.permission.FOREGROUND_SERVICE",
"android.permission.RECEIVE_BOOT_COMPLETED",
"android.permission.POST_NOTIFICATIONS"
]
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-camera",
"expo-location",
"expo-secure-store",
[
"expo-notifications",
{
"icon": "./assets/notification-icon.png",
"color": "#6366f1",
"sounds": ["./assets/notification-sound.wav"]
}
]
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+8
View File
@@ -0,0 +1,8 @@
import { registerRootComponent } from 'expo';
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);
+341
View File
@@ -0,0 +1,341 @@
/**
* quiXzoom API client
* Auth: identity-core JWT (stored in SecureStore)
*/
import * as SecureStore from 'expo-secure-store'
const API_BASE = process.env.EXPO_PUBLIC_API_URL ?? 'https://api.quixzoom.com'
const TOKEN_KEY = 'qxz_jwt'
const REFRESH_KEY = 'qxz_refresh'
// ─── Token management ──────────────────────────────────────────────────────
export async function getToken(): Promise<string | null> {
return SecureStore.getItemAsync(TOKEN_KEY)
}
export async function setToken(token: string, refreshToken?: string): Promise<void> {
await SecureStore.setItemAsync(TOKEN_KEY, token)
if (refreshToken) await SecureStore.setItemAsync(REFRESH_KEY, refreshToken)
}
export async function clearTokens(): Promise<void> {
await SecureStore.deleteItemAsync(TOKEN_KEY)
await SecureStore.deleteItemAsync(REFRESH_KEY)
}
// ─── Base fetch with JWT ───────────────────────────────────────────────────
async function apiFetch<T>(
path: string,
options: RequestInit = {},
): Promise<T> {
const token = await getToken()
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
}
if (token) headers['Authorization'] = `Bearer ${token}`
const res = await fetch(`${API_BASE}${path}`, { ...options, headers })
if (res.status === 401) {
// Try refresh
const refreshed = await attemptRefresh()
if (refreshed) {
headers['Authorization'] = `Bearer ${refreshed}`
const retry = await fetch(`${API_BASE}${path}`, { ...options, headers })
if (!retry.ok) throw new ApiError(retry.status, await retry.text())
return retry.json() as Promise<T>
}
throw new ApiError(401, 'Unauthorized')
}
if (!res.ok) throw new ApiError(res.status, await res.text())
return res.json() as Promise<T>
}
async function attemptRefresh(): Promise<string | null> {
const refresh = await SecureStore.getItemAsync(REFRESH_KEY)
if (!refresh) return null
try {
const res = await fetch(`${API_BASE}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refresh }),
})
if (!res.ok) return null
const data = await res.json() as { access_token: string; refresh_token?: string }
await setToken(data.access_token, data.refresh_token)
return data.access_token
} catch {
return null
}
}
export class ApiError extends Error {
constructor(public status: number, message: string) {
super(message)
this.name = 'ApiError'
}
}
// ─── Types ─────────────────────────────────────────────────────────────────
export type MissionStatus = 'open' | 'active' | 'completed' | 'cancelled' | 'expired'
export type MissionCategory = 'infrastruktur' | 'vägar' | 'hamnar' | 'bryggor' | 'fritidshus' | 'miljö' | 'övrigt'
export interface Mission {
id: string
title: string
description: string
status: MissionStatus
category: MissionCategory
latitude: number
longitude: number
city: string
reward_amount: number // in öre (SEK * 100)
reward_currency: string // 'SEK'
deadline_at: string | null // ISO
created_at: string
distance_meters?: number // populated when fetching nearby
}
export interface MissionDetail extends Mission {
instructions: string
requirements: string[]
max_submissions: number
submission_count: number
}
export interface Submission {
id: string
mission_id: string
zoomer_id: string
status: 'pending' | 'approved' | 'rejected'
media_urls: string[]
created_at: string
reward_paid: number
}
export interface Zoomer {
id: string
email: string
display_name: string
avatar_url: string | null
level: number
total_earned: number // öre
missions_completed: number
badges: Badge[]
joined_at: string
}
export interface Badge {
id: string
label: string
emoji: string
earned_at: string
}
export interface EarningsDay {
date: string // YYYY-MM-DD
amount: number // öre
missions: number
}
export interface EarningsSummary {
today: number
this_week: number
total: number
pending: number
currency: string
}
export interface Payout {
id: string
amount: number
currency: string
status: 'pending' | 'approved' | 'paid' | 'rejected'
created_at: string
paid_at: string | null
}
export interface AuthResult {
access_token: string
refresh_token: string
zoomer: Zoomer
}
// ─── Auth ──────────────────────────────────────────────────────────────────
export const auth = {
async login(email: string, password: string): Promise<AuthResult> {
const data = await apiFetch<AuthResult>('/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
})
await setToken(data.access_token, data.refresh_token)
return data
},
async register(email: string, password: string, displayName?: string): Promise<AuthResult> {
const data = await apiFetch<AuthResult>('/auth/register', {
method: 'POST',
body: JSON.stringify({ email, password, display_name: displayName }),
})
await setToken(data.access_token, data.refresh_token)
return data
},
async logout(): Promise<void> {
try {
await apiFetch('/auth/logout', { method: 'POST' })
} catch {
// best-effort
}
await clearTokens()
},
async me(): Promise<Zoomer> {
return apiFetch<Zoomer>('/auth/me')
},
}
// ─── Missions ──────────────────────────────────────────────────────────────
export const missions = {
/** Nearby open missions sorted by distance */
async nearby(lat: number, lng: number, radiusKm = 50): Promise<Mission[]> {
const q = new URLSearchParams({
lat: lat.toString(),
lng: lng.toString(),
radius_km: radiusKm.toString(),
status: 'open',
})
return apiFetch<Mission[]>(`/missions/near?${q}`)
},
/** List all open missions (paginated) */
async list(page = 1, category?: MissionCategory): Promise<Mission[]> {
const q = new URLSearchParams({ page: page.toString(), status: 'open' })
if (category) q.set('category', category)
return apiFetch<Mission[]>(`/missions?${q}`)
},
async get(id: string): Promise<MissionDetail> {
return apiFetch<MissionDetail>(`/missions/${id}`)
},
async accept(id: string): Promise<{ ok: boolean }> {
return apiFetch<{ ok: boolean }>(`/missions/${id}/claim`, { method: 'PATCH' })
},
async submit(id: string, mediaUris: string[]): Promise<Submission> {
const form = new FormData()
for (const uri of mediaUris) {
const filename = uri.split('/').pop() ?? 'image.jpg'
form.append('media', {
uri,
name: filename,
type: 'image/jpeg',
} as unknown as Blob)
}
const token = await getToken()
const res = await fetch(`${API_BASE}/missions/${id}/submit`, {
method: 'POST',
headers: token ? { Authorization: `Bearer ${token}` } : {},
body: form,
})
if (!res.ok) throw new ApiError(res.status, await res.text())
return res.json() as Promise<Submission>
},
/** My active/accepted missions */
async myActive(): Promise<Mission[]> {
return apiFetch<Mission[]>('/missions/claims/active')
},
}
// ─── Wallet / Earnings ─────────────────────────────────────────────────────
export interface WalletBalance {
balance: number // öre
currency: string
pending_payouts: number
}
export const wallet = {
async balance(): Promise<WalletBalance> {
return apiFetch<WalletBalance>('/auth/wallet')
},
}
export const payouts = {
async history(): Promise<Payout[]> {
return apiFetch<Payout[]>('/payouts/history')
},
async pending(): Promise<Payout[]> {
return apiFetch<Payout[]>('/payouts/pending')
},
async request(amount: number): Promise<Payout> {
return apiFetch<Payout>('/payouts/request', {
method: 'POST',
body: JSON.stringify({ amount }),
})
},
}
// ─── Passwordless Auth ───────────────────────────────────────────────────
export interface PasswordlessRequest {
request_id: string
request_token: string
status: 'pending' | 'scanned' | 'approved' | 'denied' | 'expired'
device_info?: {
type: string
name: string
browser?: string
}
created_at: string
expires_at: string
}
export const passwordless = {
/** Approve a passwordless login request */
async approve(requestToken: string): Promise<{ ok: boolean; zoomer?: Zoomer }> {
return apiFetch<{ ok: boolean; zoomer?: Zoomer }>('/auth/passwordless/approve', {
method: 'POST',
body: JSON.stringify({ request_token: requestToken }),
})
},
/** Deny a passwordless login request */
async deny(requestToken: string): Promise<{ ok: boolean }> {
return apiFetch<{ ok: boolean }>('/auth/passwordless/deny', {
method: 'POST',
body: JSON.stringify({ request_token: requestToken }),
})
},
/** Get pending auth requests for current user */
async pending(): Promise<PasswordlessRequest[]> {
return apiFetch<PasswordlessRequest[]>('/auth/passwordless/pending')
},
}
// ─── Profile ───────────────────────────────────────────────────────────────
export const profile = {
async get(): Promise<Zoomer> {
return apiFetch<Zoomer>('/auth/me')
},
async update(data: { display_name?: string }): Promise<Zoomer> {
return apiFetch<Zoomer>('/auth/me', {
method: 'PATCH',
body: JSON.stringify(data),
})
},
}
+9056
View File
@@ -0,0 +1,9056 @@
{
"name": "quixzoom-mobile",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "quixzoom-mobile",
"version": "1.0.0",
"dependencies": {
"@react-navigation/bottom-tabs": "^7.15.7",
"@react-navigation/native": "^7.2.0",
"@react-navigation/native-stack": "^7.14.7",
"@supabase/supabase-js": "^2.100.0",
"@tanstack/react-query": "^5.95.2",
"expo": "~55.0.8",
"expo-camera": "~55.0.10",
"expo-image-picker": "~55.0.13",
"expo-linking": "~7.1.4",
"expo-location": "~55.1.4",
"expo-notifications": "^0.30.7",
"expo-secure-store": "~55.0.9",
"expo-status-bar": "~55.0.4",
"react": "19.2.0",
"react-native": "0.83.2",
"react-native-gesture-handler": "~2.30.0",
"react-native-maps": "^1.27.2",
"react-native-safe-area-context": "^5.7.0",
"react-native-screens": "~4.23.0",
"zustand": "^5.0.12"
},
"devDependencies": {
"@types/react": "~19.2.2",
"typescript": "~5.9.2"
}
},
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
"integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helpers": "^7.28.6",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/babel"
}
},
"node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/generator": {
"version": "7.29.1",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.28.6",
"@babel/helper-validator-option": "^7.27.1",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz",
"integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-member-expression-to-functions": "^7.28.5",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/helper-replace-supers": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
"@babel/traverse": "^7.28.6",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz",
"integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"regexpu-core": "^6.3.1",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz",
"integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"debug": "^4.4.3",
"lodash.debounce": "^4.0.8",
"resolve": "^1.22.11"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/@babel/helper-globals": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz",
"integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-validator-identifier": "^7.28.5",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-optimise-call-expression": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
"integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-remap-async-to-generator": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz",
"integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.1",
"@babel/helper-wrap-function": "^7.27.1",
"@babel/traverse": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-replace-supers": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz",
"integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==",
"license": "MIT",
"dependencies": {
"@babel/helper-member-expression-to-functions": "^7.28.5",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
"integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-wrap-function": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz",
"integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.28.6",
"@babel/types": "^7.29.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz",
"integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.25.9",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@babel/highlight/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/highlight/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/parser": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-proposal-decorators": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz",
"integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/plugin-syntax-decorators": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-proposal-export-default-from": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz",
"integrity": "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-async-generators": {
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
"integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-bigint": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
"integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-class-properties": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
"integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.12.13"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-class-static-block": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
"integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-decorators": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz",
"integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-dynamic-import": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
"integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-export-default-from": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.28.6.tgz",
"integrity": "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-flow": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz",
"integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-import-attributes": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
"integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-import-meta": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
"integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-json-strings": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
"integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
"integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-logical-assignment-operators": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
"integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
"integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-numeric-separator": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
"integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-object-rest-spread": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
"integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-optional-catch-binding": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
"integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-optional-chaining": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
"integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-private-property-in-object": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
"integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-top-level-await": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
"integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-typescript": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
"integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-arrow-functions": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
"integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-async-generator-functions": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz",
"integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-remap-async-to-generator": "^7.27.1",
"@babel/traverse": "^7.29.0"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-async-to-generator": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz",
"integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-remap-async-to-generator": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz",
"integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-class-properties": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz",
"integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-class-static-block": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz",
"integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.12.0"
}
},
"node_modules/@babel/plugin-transform-classes": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz",
"integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-globals": "^7.28.0",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-replace-supers": "^7.28.6",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-computed-properties": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz",
"integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/template": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-destructuring": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz",
"integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-export-namespace-from": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz",
"integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-flow-strip-types": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz",
"integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/plugin-syntax-flow": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-for-of": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz",
"integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-function-name": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz",
"integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-literals": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz",
"integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz",
"integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-modules-commonjs": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz",
"integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz",
"integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.28.5",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz",
"integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-numeric-separator": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz",
"integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz",
"integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/plugin-transform-destructuring": "^7.28.5",
"@babel/plugin-transform-parameters": "^7.27.7",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-optional-catch-binding": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz",
"integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz",
"integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-parameters": {
"version": "7.27.7",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz",
"integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-private-methods": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz",
"integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-private-property-in-object": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz",
"integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-display-name": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz",
"integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz",
"integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/plugin-syntax-jsx": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-development": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz",
"integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==",
"license": "MIT",
"dependencies": {
"@babel/plugin-transform-react-jsx": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-pure-annotations": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz",
"integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz",
"integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-runtime": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz",
"integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"babel-plugin-polyfill-corejs2": "^0.4.14",
"babel-plugin-polyfill-corejs3": "^0.13.0",
"babel-plugin-polyfill-regenerator": "^0.6.5",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-runtime/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/plugin-transform-shorthand-properties": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
"integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-spread": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz",
"integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-sticky-regex": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz",
"integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-typescript": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
"integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
"@babel/plugin-syntax-typescript": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-unicode-regex": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
"integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-react": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz",
"integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-transform-react-display-name": "^7.28.0",
"@babel/plugin-transform-react-jsx": "^7.27.1",
"@babel/plugin-transform-react-jsx-development": "^7.27.1",
"@babel/plugin-transform-react-pure-annotations": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-typescript": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz",
"integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
"@babel/plugin-transform-typescript": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/parser": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/types": "^7.29.0",
"debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse--for-generate-function-map": {
"name": "@babel/traverse",
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/types": "^7.29.0",
"debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"license": "MIT",
"dependencies": {
"@types/hammerjs": "^2.0.36"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@expo/code-signing-certificates": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz",
"integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==",
"license": "MIT",
"dependencies": {
"node-forge": "^1.3.3"
}
},
"node_modules/@expo/config": {
"version": "55.0.10",
"resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.10.tgz",
"integrity": "sha512-qCHxo9H1ZoeW+y0QeMtVZ3JfGmumpGrgUFX60wLWMarraoQZSe47ZUm9kJSn3iyoPjUtUNanO3eXQg+K8k4rag==",
"license": "MIT",
"dependencies": {
"@expo/config-plugins": "~55.0.7",
"@expo/config-types": "^55.0.5",
"@expo/json-file": "^10.0.12",
"@expo/require-utils": "^55.0.3",
"deepmerge": "^4.3.1",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"resolve-from": "^5.0.0",
"resolve-workspace-root": "^2.0.0",
"semver": "^7.6.0",
"slugify": "^1.3.4"
}
},
"node_modules/@expo/config-plugins": {
"version": "55.0.7",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.7.tgz",
"integrity": "sha512-XZUoDWrsHEkH3yasnDSJABM/UxP5a1ixzRwU/M+BToyn/f0nTrSJJe/Ay/FpxkI4JSNz2n0e06I23b2bleXKVA==",
"license": "MIT",
"dependencies": {
"@expo/config-types": "^55.0.5",
"@expo/json-file": "~10.0.12",
"@expo/plist": "^0.5.2",
"@expo/sdk-runtime-versions": "^1.0.0",
"chalk": "^4.1.2",
"debug": "^4.3.5",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"resolve-from": "^5.0.0",
"semver": "^7.5.4",
"slugify": "^1.6.6",
"xcode": "^3.0.1",
"xml2js": "0.6.0"
}
},
"node_modules/@expo/config-types": {
"version": "55.0.5",
"resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-55.0.5.tgz",
"integrity": "sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg==",
"license": "MIT"
},
"node_modules/@expo/devcert": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.1.tgz",
"integrity": "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==",
"license": "MIT",
"dependencies": {
"@expo/sudo-prompt": "^9.3.1",
"debug": "^3.1.0"
}
},
"node_modules/@expo/devcert/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/@expo/devtools": {
"version": "55.0.2",
"resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-55.0.2.tgz",
"integrity": "sha512-4VsFn9MUriocyuhyA+ycJP3TJhUsOFHDc270l9h3LhNpXMf6wvIdGcA0QzXkZtORXmlDybWXRP2KT1k36HcQkA==",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/@expo/env": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@expo/env/-/env-2.1.1.tgz",
"integrity": "sha512-rVvHC4I6xlPcg+mAO09ydUi2Wjv1ZytpLmHOSzvXzBAz9mMrJggqCe4s4dubjJvi/Ino/xQCLhbaLCnTtLpikg==",
"license": "MIT",
"dependencies": {
"chalk": "^4.0.0",
"debug": "^4.3.4",
"getenv": "^2.0.0"
},
"engines": {
"node": ">=20.12.0"
}
},
"node_modules/@expo/fingerprint": {
"version": "0.16.6",
"resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.16.6.tgz",
"integrity": "sha512-nRITNbnu3RKSHPvKVehrSU4KG2VY9V8nvULOHBw98ukHCAU4bGrU5APvcblOkX3JAap+xEHsg/mZvqlvkLInmQ==",
"license": "MIT",
"dependencies": {
"@expo/env": "^2.0.11",
"@expo/spawn-async": "^1.7.2",
"arg": "^5.0.2",
"chalk": "^4.1.2",
"debug": "^4.3.4",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"ignore": "^5.3.1",
"minimatch": "^10.2.2",
"resolve-from": "^5.0.0",
"semver": "^7.6.0"
},
"bin": {
"fingerprint": "bin/cli.js"
}
},
"node_modules/@expo/image-utils": {
"version": "0.8.12",
"resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.12.tgz",
"integrity": "sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A==",
"license": "MIT",
"dependencies": {
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.0.0",
"getenv": "^2.0.0",
"jimp-compact": "0.16.1",
"parse-png": "^2.1.0",
"resolve-from": "^5.0.0",
"semver": "^7.6.0"
}
},
"node_modules/@expo/json-file": {
"version": "10.0.12",
"resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.12.tgz",
"integrity": "sha512-inbDycp1rMAelAofg7h/mMzIe+Owx6F7pur3XdQ3EPTy00tme+4P6FWgHKUcjN8dBSrnbRNpSyh5/shzHyVCyQ==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.20.0",
"json5": "^2.2.3"
}
},
"node_modules/@expo/local-build-cache-provider": {
"version": "55.0.7",
"resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-55.0.7.tgz",
"integrity": "sha512-Qg9uNZn1buv4zJUA4ZQaz+ZnKDCipRgjoEg2Gcp8Qfy+2Gq5yZKX4YN1TThCJ01LJk/pvJsCRxXlXZSwdZppgg==",
"license": "MIT",
"dependencies": {
"@expo/config": "~55.0.10",
"chalk": "^4.1.2"
}
},
"node_modules/@expo/metro": {
"version": "54.2.0",
"resolved": "https://registry.npmjs.org/@expo/metro/-/metro-54.2.0.tgz",
"integrity": "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w==",
"license": "MIT",
"dependencies": {
"metro": "0.83.3",
"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-minify-terser": "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"
}
},
"node_modules/@expo/osascript": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.4.2.tgz",
"integrity": "sha512-/XP7PSYF2hzOZzqfjgkoWtllyeTN8dW3aM4P6YgKcmmPikKL5FdoyQhti4eh6RK5a5VrUXJTOlTNIpIHsfB5Iw==",
"license": "MIT",
"dependencies": {
"@expo/spawn-async": "^1.7.2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@expo/package-manager": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.10.3.tgz",
"integrity": "sha512-ZuXiK/9fCrIuLjPSe1VYmfp0Sa85kCMwd8QQpgyi5ufppYKRtLBg14QOgUqj8ZMbJTxE0xqzd0XR7kOs3vAK9A==",
"license": "MIT",
"dependencies": {
"@expo/json-file": "^10.0.12",
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.0.0",
"npm-package-arg": "^11.0.0",
"ora": "^3.4.0",
"resolve-workspace-root": "^2.0.0"
}
},
"node_modules/@expo/plist": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.5.2.tgz",
"integrity": "sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"base64-js": "^1.5.1",
"xmlbuilder": "^15.1.1"
}
},
"node_modules/@expo/require-utils": {
"version": "55.0.3",
"resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-55.0.3.tgz",
"integrity": "sha512-TS1m5tW45q4zoaTlt6DwmdYHxvFTIxoLrTHKOFrIirHIqIXnHCzpceg8wumiBi+ZXSaGY2gobTbfv+WVhJY6Fw==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.20.0",
"@babel/core": "^7.25.2",
"@babel/plugin-transform-modules-commonjs": "^7.24.8"
},
"peerDependencies": {
"typescript": "^5.0.0 || ^5.0.0-0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@expo/schema-utils": {
"version": "55.0.2",
"resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-55.0.2.tgz",
"integrity": "sha512-QZ5WKbJOWkCrMq0/kfhV9ry8te/OaS34YgLVpG8u9y2gix96TlpRTbxM/YATjNcUR2s4fiQmPCOxkGtog4i37g==",
"license": "MIT"
},
"node_modules/@expo/sdk-runtime-versions": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz",
"integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==",
"license": "MIT"
},
"node_modules/@expo/spawn-async": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.7.2.tgz",
"integrity": "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==",
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@expo/sudo-prompt": {
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz",
"integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==",
"license": "MIT"
},
"node_modules/@expo/ws-tunnel": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-1.0.6.tgz",
"integrity": "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==",
"license": "MIT"
},
"node_modules/@expo/xcpretty": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.1.tgz",
"integrity": "sha512-KZNxZvnGCtiM2aYYZ6Wz0Ix5r47dAvpNLApFtZWnSoERzAdOMzVBOPysBoM0JlF6FKWZ8GPqgn6qt3dV/8Zlpg==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/code-frame": "^7.20.0",
"chalk": "^4.1.0",
"js-yaml": "^4.1.0"
},
"bin": {
"excpretty": "build/cli.js"
}
},
"node_modules/@expo/xcpretty/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/@expo/xcpretty/node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@ide/backoff": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ide/backoff/-/backoff-1.0.0.tgz",
"integrity": "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@isaacs/ttlcache": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz",
"integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
"integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.3.1",
"find-up": "^4.1.0",
"get-package-type": "^0.1.0",
"js-yaml": "^3.13.1",
"resolve-from": "^5.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@jest/create-cache-key-function": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz",
"integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/environment": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
"integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
"license": "MIT",
"dependencies": {
"@jest/fake-timers": "^29.7.0",
"@jest/types": "^29.6.3",
"@types/node": "*",
"jest-mock": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/fake-timers": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
"integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"@sinonjs/fake-timers": "^10.0.2",
"@types/node": "*",
"jest-message-util": "^29.7.0",
"jest-mock": "^29.7.0",
"jest-util": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/schemas": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.27.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/transform": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
"integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.11.6",
"@jest/types": "^29.6.3",
"@jridgewell/trace-mapping": "^0.3.18",
"babel-plugin-istanbul": "^6.1.1",
"chalk": "^4.0.0",
"convert-source-map": "^2.0.0",
"fast-json-stable-stringify": "^2.1.0",
"graceful-fs": "^4.2.9",
"jest-haste-map": "^29.7.0",
"jest-regex-util": "^29.6.3",
"jest-util": "^29.7.0",
"micromatch": "^4.0.4",
"pirates": "^4.0.4",
"slash": "^3.0.0",
"write-file-atomic": "^4.0.2"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/types": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
"integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
"license": "MIT",
"dependencies": {
"@jest/schemas": "^29.6.3",
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
"@types/yargs": "^17.0.8",
"chalk": "^4.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@react-native/assets-registry": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.2.tgz",
"integrity": "sha512-9I5l3pGAKnlpQ15uVkeB9Mgjvt3cZEaEc8EDtdexvdtZvLSjtwBzgourrOW4yZUijbjJr8h3YO2Y0q+THwUHTA==",
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/babel-plugin-codegen": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.2.tgz",
"integrity": "sha512-XbcN/BEa64pVlb0Hb/E/Ph2SepjVN/FcNKrJcQvtaKZA6mBSO8pW8Eircdlr61/KBH94LihHbQoQDzkQFpeaTg==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.25.3",
"@react-native/codegen": "0.83.2"
},
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/babel-preset": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.2.tgz",
"integrity": "sha512-X/RAXDfe6W+om/Fw1i6htTxQXFhBJ2jgNOWx3WpI3KbjeIWbq7ib6vrpTeIAW2NUMg+K3mML1NzgD4dpZeqdjA==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/plugin-proposal-export-default-from": "^7.24.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-export-default-from": "^7.24.7",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
"@babel/plugin-transform-arrow-functions": "^7.24.7",
"@babel/plugin-transform-async-generator-functions": "^7.25.4",
"@babel/plugin-transform-async-to-generator": "^7.24.7",
"@babel/plugin-transform-block-scoping": "^7.25.0",
"@babel/plugin-transform-class-properties": "^7.25.4",
"@babel/plugin-transform-classes": "^7.25.4",
"@babel/plugin-transform-computed-properties": "^7.24.7",
"@babel/plugin-transform-destructuring": "^7.24.8",
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@babel/plugin-transform-for-of": "^7.24.7",
"@babel/plugin-transform-function-name": "^7.25.1",
"@babel/plugin-transform-literals": "^7.25.2",
"@babel/plugin-transform-logical-assignment-operators": "^7.24.7",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7",
"@babel/plugin-transform-numeric-separator": "^7.24.7",
"@babel/plugin-transform-object-rest-spread": "^7.24.7",
"@babel/plugin-transform-optional-catch-binding": "^7.24.7",
"@babel/plugin-transform-optional-chaining": "^7.24.8",
"@babel/plugin-transform-parameters": "^7.24.7",
"@babel/plugin-transform-private-methods": "^7.24.7",
"@babel/plugin-transform-private-property-in-object": "^7.24.7",
"@babel/plugin-transform-react-display-name": "^7.24.7",
"@babel/plugin-transform-react-jsx": "^7.25.2",
"@babel/plugin-transform-react-jsx-self": "^7.24.7",
"@babel/plugin-transform-react-jsx-source": "^7.24.7",
"@babel/plugin-transform-regenerator": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/plugin-transform-shorthand-properties": "^7.24.7",
"@babel/plugin-transform-spread": "^7.24.7",
"@babel/plugin-transform-sticky-regex": "^7.24.7",
"@babel/plugin-transform-typescript": "^7.25.2",
"@babel/plugin-transform-unicode-regex": "^7.24.7",
"@babel/template": "^7.25.0",
"@react-native/babel-plugin-codegen": "0.83.2",
"babel-plugin-syntax-hermes-parser": "0.32.0",
"babel-plugin-transform-flow-enums": "^0.0.2",
"react-refresh": "^0.14.0"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/@react-native/codegen": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.2.tgz",
"integrity": "sha512-9uK6X1miCXqtL4c759l74N/XbQeneWeQVjoV7SD2CGJuW7ZefxaoYenwGPs7rMoCdtS6wuIyR3hXQ+uWEBGYXA==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/parser": "^7.25.3",
"glob": "^7.1.1",
"hermes-parser": "0.32.0",
"invariant": "^2.2.4",
"nullthrows": "^1.1.1",
"yargs": "^17.6.2"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/@react-native/codegen/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/@react-native/codegen/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@react-native/codegen/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@react-native/codegen/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@react-native/community-cli-plugin": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.2.tgz",
"integrity": "sha512-sTEF0eiUKtmImEP07Qo5c3Khvm1LIVX1Qyb6zWUqPL6W3MqFiXutZvKBjqLz6p49Szx8cplQLoXfLHT0bcDXKg==",
"license": "MIT",
"dependencies": {
"@react-native/dev-middleware": "0.83.2",
"debug": "^4.4.0",
"invariant": "^2.2.4",
"metro": "^0.83.3",
"metro-config": "^0.83.3",
"metro-core": "^0.83.3",
"semver": "^7.1.3"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@react-native-community/cli": "*",
"@react-native/metro-config": "*"
},
"peerDependenciesMeta": {
"@react-native-community/cli": {
"optional": true
},
"@react-native/metro-config": {
"optional": true
}
}
},
"node_modules/@react-native/debugger-frontend": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.2.tgz",
"integrity": "sha512-t4fYfa7xopbUF5S4+ihNEwgaq4wLZLKLY0Ms8z72lkMteVd3bOX2Foxa8E2wTfRvdhPOkSpOsTeNDmD8ON4DoQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/debugger-shell": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.2.tgz",
"integrity": "sha512-z9go6NJMsLSDJT5MW6VGugRsZHjYvUTwxtsVc3uLt4U9W6T3J6FWI2wHpXIzd2dUkXRfAiRQ3Zi8ZQQ8fRFg9A==",
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.6",
"fb-dotslash": "0.5.8"
},
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/dev-middleware": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.2.tgz",
"integrity": "sha512-Zi4EVaAm28+icD19NN07Gh8Pqg/84QQu+jn4patfWKNkcToRFP5vPEbbp0eLOGWS+BVB1d1Fn5lvMrJsBbFcOg==",
"license": "MIT",
"dependencies": {
"@isaacs/ttlcache": "^1.4.1",
"@react-native/debugger-frontend": "0.83.2",
"@react-native/debugger-shell": "0.83.2",
"chrome-launcher": "^0.15.2",
"chromium-edge-launcher": "^0.2.0",
"connect": "^3.6.5",
"debug": "^4.4.0",
"invariant": "^2.2.4",
"nullthrows": "^1.1.1",
"open": "^7.0.3",
"serve-static": "^1.16.2",
"ws": "^7.5.10"
},
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/gradle-plugin": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.83.2.tgz",
"integrity": "sha512-PqN11fXRAU+uJ0inZY1HWYlwJOXHOhF4SPyeHBBxjajKpm2PGunmvFWwkmBjmmUkP/CNO0ezTUudV0oj+2wiHQ==",
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/js-polyfills": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.2.tgz",
"integrity": "sha512-dk6fIY2OrKW/2Nk2HydfYNrQau8g6LOtd7NVBrgaqa+lvuRyIML5iimShP5qPqQnx2ofHuzjFw+Ya0b5Q7nDbA==",
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/normalize-colors": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.2.tgz",
"integrity": "sha512-gkZAb9LoVVzNuYzzOviH7DiPTXQoZPHuiTH2+O2+VWNtOkiznjgvqpwYAhg58a5zfRq5GXlbBdf5mzRj5+3Y5Q==",
"license": "MIT"
},
"node_modules/@react-navigation/bottom-tabs": {
"version": "7.15.7",
"resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.15.7.tgz",
"integrity": "sha512-OXNvU0esvyZf//KpaIsFh2GD1otxqG+Lv48VfkNIh+3DEbOnqni8pOrKdICgoC4T0R8oVln7pnVeHl89Gipv8w==",
"license": "MIT",
"dependencies": {
"@react-navigation/elements": "^2.9.12",
"color": "^4.2.3",
"sf-symbols-typescript": "^2.1.0"
},
"peerDependencies": {
"@react-navigation/native": "^7.2.0",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0",
"react-native-screens": ">= 4.0.0"
}
},
"node_modules/@react-navigation/core": {
"version": "7.17.0",
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.17.0.tgz",
"integrity": "sha512-E4Kr1PRrhKiVn1RdMdPIG1rCfrKh+HiVJ2smdLsh9D95Q2z0a9dGE9yHpRQ2pAUiiwOfgloLqegkPb8g+TcCBA==",
"license": "MIT",
"dependencies": {
"@react-navigation/routers": "^7.5.3",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
"query-string": "^7.1.3",
"react-is": "^19.1.0",
"use-latest-callback": "^0.2.4",
"use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
"react": ">= 18.2.0"
}
},
"node_modules/@react-navigation/core/node_modules/react-is": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
"integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
"license": "MIT"
},
"node_modules/@react-navigation/elements": {
"version": "2.9.12",
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.12.tgz",
"integrity": "sha512-LSaQUj5SV9OXVRcxT8mqETDoM7BOKCveCvuLjdAr9NZnPDM5HW8uDnvW/sCa8oEFy+22+ojoXtHFKsfnesgBbw==",
"license": "MIT",
"dependencies": {
"color": "^4.2.3",
"use-latest-callback": "^0.2.4",
"use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
"@react-native-masked-view/masked-view": ">= 0.2.0",
"@react-navigation/native": "^7.2.0",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0"
},
"peerDependenciesMeta": {
"@react-native-masked-view/masked-view": {
"optional": true
}
}
},
"node_modules/@react-navigation/native": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.2.0.tgz",
"integrity": "sha512-kEuqIS1MkzzLD45Fp17CrxAchoB4W6tMfc541merUgtAeNNsg06gRrvmuLv6LAYvpLifGdXuSjpluPIu/VmbQw==",
"license": "MIT",
"dependencies": {
"@react-navigation/core": "^7.17.0",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
"use-latest-callback": "^0.2.4"
},
"peerDependencies": {
"react": ">= 18.2.0",
"react-native": "*"
}
},
"node_modules/@react-navigation/native-stack": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.14.7.tgz",
"integrity": "sha512-MxKdKS817YK7iirlyW+XZnXJ339eRE7aA3E55zHVDS+R+bqro+PwRwNGqL1Y9e3w0KjAKZVsOfn5erJRWrO4iQ==",
"license": "MIT",
"dependencies": {
"@react-navigation/elements": "^2.9.12",
"color": "^4.2.3",
"sf-symbols-typescript": "^2.1.0",
"warn-once": "^0.1.1"
},
"peerDependencies": {
"@react-navigation/native": "^7.2.0",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0",
"react-native-screens": ">= 4.0.0"
}
},
"node_modules/@react-navigation/routers": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz",
"integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==",
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.27.10",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz",
"integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==",
"license": "MIT"
},
"node_modules/@sinonjs/commons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
"integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
"license": "BSD-3-Clause",
"dependencies": {
"type-detect": "4.0.8"
}
},
"node_modules/@sinonjs/fake-timers": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
"integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
"license": "BSD-3-Clause",
"dependencies": {
"@sinonjs/commons": "^3.0.0"
}
},
"node_modules/@supabase/auth-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.100.0.tgz",
"integrity": "sha512-pdT3ye3UVRN1Cg0wom6BmyY+XTtp5DiJaYnPi6j8ht5i8Lq8kfqxJMJz9GI9YDKk3w1nhGOPnh6Qz5qpyYm+1w==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/functions-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.100.0.tgz",
"integrity": "sha512-keLg79RPwP+uiwHuxFPTFgDRxPV46LM4j/swjyR2GKJgWniTVSsgiBHfbIBDcrQwehLepy09b/9QSHUywtKRWQ==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/phoenix": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz",
"integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==",
"license": "MIT"
},
"node_modules/@supabase/postgrest-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.100.0.tgz",
"integrity": "sha512-xYNvNbBJaXOGcrZ44wxwp5830uo1okMHGS8h8dm3u4f0xcZ39yzbryUsubTJW41MG2gbL/6U57cA4Pi6YMZ9pA==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/realtime-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.100.0.tgz",
"integrity": "sha512-2AZs00zzEF0HuCKY8grz5eCYlwEfVi5HONLZFoNR6aDfxQivl8zdQYNjyFoqN2MZiVhQHD7u6XV/xHwM8mCEHw==",
"license": "MIT",
"dependencies": {
"@supabase/phoenix": "^0.4.0",
"@types/ws": "^8.18.1",
"tslib": "2.8.1",
"ws": "^8.18.2"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/realtime-js/node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@supabase/storage-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.100.0.tgz",
"integrity": "sha512-d4EeuK6RNIgYNA2MU9kj8lQrLm5AzZ+WwpWjGkii6SADQNIGTC/uiaTRu02XJ5AmFALQfo8fLl9xuCkO6Xw+iQ==",
"license": "MIT",
"dependencies": {
"iceberg-js": "^0.8.1",
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.100.0.tgz",
"integrity": "sha512-r0tlcukejJXJ1m/2eG/Ya5eYs4W8AC7oZfShpG3+SIo/eIU9uIt76ZeYI1SoUwUmcmzlAbgch+HDZDR/toVQPQ==",
"license": "MIT",
"dependencies": {
"@supabase/auth-js": "2.100.0",
"@supabase/functions-js": "2.100.0",
"@supabase/postgrest-js": "2.100.0",
"@supabase/realtime-js": "2.100.0",
"@supabase/storage-js": "2.100.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.95.2",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.95.2.tgz",
"integrity": "sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.95.2",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.95.2.tgz",
"integrity": "sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.95.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7",
"@types/babel__generator": "*",
"@types/babel__template": "*",
"@types/babel__traverse": "*"
}
},
"node_modules/@types/babel__generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__template": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__traverse": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/emscripten": {
"version": "1.41.5",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz",
"integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==",
"license": "MIT"
},
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"license": "MIT"
},
"node_modules/@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
"integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/hammerjs": {
"version": "2.0.46",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz",
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==",
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"license": "MIT"
},
"node_modules/@types/istanbul-lib-report": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
"license": "MIT",
"dependencies": {
"@types/istanbul-lib-coverage": "*"
}
},
"node_modules/@types/istanbul-reports": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
"license": "MIT",
"dependencies": {
"@types/istanbul-lib-report": "*"
}
},
"node_modules/@types/node": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/stack-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/yargs": {
"version": "17.0.35",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
"integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
"license": "MIT",
"dependencies": {
"@types/yargs-parser": "*"
}
},
"node_modules/@types/yargs-parser": {
"version": "21.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
"license": "MIT"
},
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"license": "ISC"
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.11",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/anser": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz",
"integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==",
"license": "MIT"
},
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
"license": "MIT",
"dependencies": {
"type-fest": "^0.21.3"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-escapes/node_modules/type-fest": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"license": "MIT"
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"license": "MIT"
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"license": "MIT"
},
"node_modules/assert": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
"integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
"is-nan": "^1.3.2",
"object-is": "^1.1.5",
"object.assign": "^4.1.4",
"util": "^0.12.5"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
"license": "MIT",
"dependencies": {
"possible-typed-array-names": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
"integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
"license": "MIT",
"dependencies": {
"@jest/transform": "^29.7.0",
"@types/babel__core": "^7.1.14",
"babel-plugin-istanbul": "^6.1.1",
"babel-preset-jest": "^29.6.3",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"slash": "^3.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
},
"peerDependencies": {
"@babel/core": "^7.8.0"
}
},
"node_modules/babel-plugin-istanbul": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
"integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
"@istanbuljs/load-nyc-config": "^1.0.0",
"@istanbuljs/schema": "^0.1.2",
"istanbul-lib-instrument": "^5.0.4",
"test-exclude": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/babel-plugin-jest-hoist": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
"integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.3.3",
"@babel/types": "^7.3.3",
"@types/babel__core": "^7.1.14",
"@types/babel__traverse": "^7.0.6"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.17",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz",
"integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.28.6",
"@babel/helper-define-polyfill-provider": "^0.6.8",
"semver": "^6.3.1"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz",
"integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==",
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.5",
"core-js-compat": "^3.43.0"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/babel-plugin-polyfill-regenerator": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz",
"integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==",
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.8"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/babel-plugin-react-compiler": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz",
"integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.0"
}
},
"node_modules/babel-plugin-react-native-web": {
"version": "0.21.2",
"resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz",
"integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==",
"license": "MIT"
},
"node_modules/babel-plugin-syntax-hermes-parser": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz",
"integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==",
"license": "MIT",
"dependencies": {
"hermes-parser": "0.32.0"
}
},
"node_modules/babel-plugin-transform-flow-enums": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz",
"integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==",
"license": "MIT",
"dependencies": {
"@babel/plugin-syntax-flow": "^7.12.1"
}
},
"node_modules/babel-preset-current-node-syntax": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
"integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
"license": "MIT",
"dependencies": {
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/plugin-syntax-bigint": "^7.8.3",
"@babel/plugin-syntax-class-properties": "^7.12.13",
"@babel/plugin-syntax-class-static-block": "^7.14.5",
"@babel/plugin-syntax-import-attributes": "^7.24.7",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/plugin-syntax-json-strings": "^7.8.3",
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-syntax-numeric-separator": "^7.10.4",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5",
"@babel/plugin-syntax-top-level-await": "^7.14.5"
},
"peerDependencies": {
"@babel/core": "^7.0.0 || ^8.0.0-0"
}
},
"node_modules/babel-preset-jest": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
"integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
"license": "MIT",
"dependencies": {
"babel-plugin-jest-hoist": "^29.6.3",
"babel-preset-current-node-syntax": "^1.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/badgin": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.3.tgz",
"integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==",
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/barcode-detector": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-3.1.1.tgz",
"integrity": "sha512-ghWlEAV93ZCUniO7Co3ih/01XPm+U30CV+NoPbO6Chj5lZzHydDAqKlrBEd+37TkoR+QTH3tnnwd8k8epGTfIg==",
"license": "MIT",
"dependencies": {
"zxing-wasm": "3.0.1"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.10",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz",
"integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/better-opn": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz",
"integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==",
"license": "MIT",
"dependencies": {
"open": "^8.0.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/better-opn/node_modules/open": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
"license": "MIT",
"dependencies": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
"is-wsl": "^2.2.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
"integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
"license": "Unlicense",
"engines": {
"node": ">=0.6"
}
},
"node_modules/bplist-creator": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
"integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==",
"license": "MIT",
"dependencies": {
"stream-buffers": "2.2.x"
}
},
"node_modules/bplist-parser": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz",
"integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==",
"license": "MIT",
"dependencies": {
"big-integer": "1.6.x"
},
"engines": {
"node": ">= 5.10.0"
}
},
"node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/browserslist": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
"electron-to-chromium": "^1.5.263",
"node-releases": "^2.0.27",
"update-browserslist-db": "^1.2.0"
},
"bin": {
"browserslist": "cli.js"
},
"engines": {
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/bser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
"integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
"license": "Apache-2.0",
"dependencies": {
"node-int64": "^0.4.0"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
"integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"get-intrinsic": "^1.3.0",
"set-function-length": "^1.2.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001781",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz",
"integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chrome-launcher": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz",
"integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "*",
"escape-string-regexp": "^4.0.0",
"is-wsl": "^2.2.0",
"lighthouse-logger": "^1.0.0"
},
"bin": {
"print-chrome-path": "bin/print-chrome-path.js"
},
"engines": {
"node": ">=12.13.0"
}
},
"node_modules/chromium-edge-launcher": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz",
"integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "*",
"escape-string-regexp": "^4.0.0",
"is-wsl": "^2.2.0",
"lighthouse-logger": "^1.0.0",
"mkdirp": "^1.0.4",
"rimraf": "^3.0.2"
}
},
"node_modules/ci-info": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"license": "MIT"
},
"node_modules/cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
"license": "MIT",
"dependencies": {
"restore-cursor": "^2.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/cli-spinners": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
"integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
"license": "MIT",
"dependencies": {
"mime-db": ">= 1.43.0 < 2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/compression/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/compression/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/compression/node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
"node_modules/connect": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
"integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"finalhandler": "1.1.2",
"parseurl": "~1.3.3",
"utils-merge": "1.0.1"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/connect/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/connect/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"license": "MIT"
},
"node_modules/core-js-compat": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz",
"integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.28.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decode-uri-component": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"license": "MIT",
"engines": {
"node": ">=0.10"
}
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
"license": "MIT",
"dependencies": {
"clone": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/dnssd-advertise": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.3.tgz",
"integrity": "sha512-XENsHi3MBzWOCAXif3yZvU1Ah0l+nhJj1sjWL6TnOAYKvGiFhbTx32xHN7+wLMLUOCj7Nr0evADWG4R8JtqCDA==",
"license": "MIT"
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dotenv-expand": {
"version": "11.0.7",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
"integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
"license": "BSD-2-Clause",
"dependencies": {
"dotenv": "^16.4.5"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.321",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz",
"integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==",
"license": "ISC"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/error-stack-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
"license": "MIT",
"dependencies": {
"stackframe": "^1.3.4"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/expo": {
"version": "55.0.8",
"resolved": "https://registry.npmjs.org/expo/-/expo-55.0.8.tgz",
"integrity": "sha512-sziDGiDmeRmaSpFwMuSxFhr4vfWrQS1UgVXSTovsUDY0ximABzYdnF5L2OwtD8zjtIww8x2oJGmD6mKS+AoVsw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.0",
"@expo/cli": "55.0.18",
"@expo/config": "~55.0.10",
"@expo/config-plugins": "~55.0.7",
"@expo/devtools": "55.0.2",
"@expo/fingerprint": "0.16.6",
"@expo/local-build-cache-provider": "55.0.7",
"@expo/log-box": "55.0.7",
"@expo/metro": "~54.2.0",
"@expo/metro-config": "55.0.11",
"@expo/vector-icons": "^15.0.2",
"@ungap/structured-clone": "^1.3.0",
"babel-preset-expo": "~55.0.12",
"expo-asset": "~55.0.10",
"expo-constants": "~55.0.9",
"expo-file-system": "~55.0.11",
"expo-font": "~55.0.4",
"expo-keep-awake": "~55.0.4",
"expo-modules-autolinking": "55.0.11",
"expo-modules-core": "55.0.17",
"pretty-format": "^29.7.0",
"react-refresh": "^0.14.2",
"whatwg-url-minimum": "^0.1.1"
},
"bin": {
"expo": "bin/cli",
"expo-modules-autolinking": "bin/autolinking",
"fingerprint": "bin/fingerprint"
},
"peerDependencies": {
"@expo/dom-webview": "*",
"@expo/metro-runtime": "*",
"react": "*",
"react-native": "*",
"react-native-webview": "*"
},
"peerDependenciesMeta": {
"@expo/dom-webview": {
"optional": true
},
"@expo/metro-runtime": {
"optional": true
},
"react-native-webview": {
"optional": true
}
}
},
"node_modules/expo-application": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/expo-application/-/expo-application-6.1.5.tgz",
"integrity": "sha512-ToImFmzw8luY043pWFJhh2ZMm4IwxXoHXxNoGdlhD4Ym6+CCmkAvCglg0FK8dMLzAb+/XabmOE7Rbm8KZb6NZg==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-camera": {
"version": "55.0.10",
"resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-55.0.10.tgz",
"integrity": "sha512-ftDNJbGsAPNJ/QrM3j6g8/rQAOqTwZpqtvmzF7V9VX0movaCznZFdYsLi/Fff9WeEk1KzcnLIlmSz4Tj+BCrJA==",
"license": "MIT",
"dependencies": {
"barcode-detector": "^3.0.0"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*",
"react-native-web": "*"
},
"peerDependenciesMeta": {
"react-native-web": {
"optional": true
}
}
},
"node_modules/expo-constants": {
"version": "17.1.8",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.8.tgz",
"integrity": "sha512-sOCeMN/BWLA7hBP6lMwoEQzFNgTopk6YY03sBAmwT216IHyL54TjNseg8CRU1IQQ/+qinJ2fYWCl7blx2TiNcA==",
"license": "MIT",
"dependencies": {
"@expo/config": "~11.0.13",
"@expo/env": "~1.0.7"
},
"peerDependencies": {
"expo": "*",
"react-native": "*"
}
},
"node_modules/expo-constants/node_modules/@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
"license": "MIT",
"dependencies": {
"@babel/highlight": "^7.10.4"
}
},
"node_modules/expo-constants/node_modules/@expo/config": {
"version": "11.0.13",
"resolved": "https://registry.npmjs.org/@expo/config/-/config-11.0.13.tgz",
"integrity": "sha512-TnGb4u/zUZetpav9sx/3fWK71oCPaOjZHoVED9NaEncktAd0Eonhq5NUghiJmkUGt3gGSjRAEBXiBbbY9/B1LA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "~7.10.4",
"@expo/config-plugins": "~10.1.2",
"@expo/config-types": "^53.0.5",
"@expo/json-file": "^9.1.5",
"deepmerge": "^4.3.1",
"getenv": "^2.0.0",
"glob": "^10.4.2",
"require-from-string": "^2.0.2",
"resolve-from": "^5.0.0",
"resolve-workspace-root": "^2.0.0",
"semver": "^7.6.0",
"slugify": "^1.3.4",
"sucrase": "3.35.0"
}
},
"node_modules/expo-constants/node_modules/@expo/config-plugins": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.1.2.tgz",
"integrity": "sha512-IMYCxBOcnuFStuK0Ay+FzEIBKrwW8OVUMc65+v0+i7YFIIe8aL342l7T4F8lR4oCfhXn7d6M5QPgXvjtc/gAcw==",
"license": "MIT",
"dependencies": {
"@expo/config-types": "^53.0.5",
"@expo/json-file": "~9.1.5",
"@expo/plist": "^0.3.5",
"@expo/sdk-runtime-versions": "^1.0.0",
"chalk": "^4.1.2",
"debug": "^4.3.5",
"getenv": "^2.0.0",
"glob": "^10.4.2",
"resolve-from": "^5.0.0",
"semver": "^7.5.4",
"slash": "^3.0.0",
"slugify": "^1.6.6",
"xcode": "^3.0.1",
"xml2js": "0.6.0"
}
},
"node_modules/expo-constants/node_modules/@expo/config-types": {
"version": "53.0.5",
"resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-53.0.5.tgz",
"integrity": "sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g==",
"license": "MIT"
},
"node_modules/expo-constants/node_modules/@expo/env": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@expo/env/-/env-1.0.7.tgz",
"integrity": "sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow==",
"license": "MIT",
"dependencies": {
"chalk": "^4.0.0",
"debug": "^4.3.4",
"dotenv": "~16.4.5",
"dotenv-expand": "~11.0.6",
"getenv": "^2.0.0"
}
},
"node_modules/expo-constants/node_modules/@expo/json-file": {
"version": "9.1.5",
"resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.1.5.tgz",
"integrity": "sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "~7.10.4",
"json5": "^2.2.3"
}
},
"node_modules/expo-constants/node_modules/@expo/plist": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.3.5.tgz",
"integrity": "sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"base64-js": "^1.2.3",
"xmlbuilder": "^15.1.1"
}
},
"node_modules/expo-constants/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/expo-constants/node_modules/brace-expansion": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/expo-constants/node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/expo-constants/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/expo-constants/node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/expo-constants/node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/expo-image-loader": {
"version": "55.0.0",
"resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-55.0.0.tgz",
"integrity": "sha512-NOjp56wDrfuA5aiNAybBIjqIn1IxKeGJ8CECWZncQ/GzjZfyTYAHTCyeApYkdKkMBLHINzI4BbTGSlbCa0fXXQ==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-image-picker": {
"version": "55.0.13",
"resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-55.0.13.tgz",
"integrity": "sha512-G+W11rcoUi3rK+6cnKWkTfZilMkGVZnYe90TiM3R98nPSlzGBoto3a/TkGGTJXedz/dmMzr49L+STlWhuKKIFw==",
"license": "MIT",
"dependencies": {
"expo-image-loader": "~55.0.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-linking": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.7.tgz",
"integrity": "sha512-ZJaH1RIch2G/M3hx2QJdlrKbYFUTOjVVW4g39hfxrE5bPX9xhZUYXqxqQtzMNl1ylAevw9JkgEfWbBWddbZ3UA==",
"license": "MIT",
"dependencies": {
"expo-constants": "~17.1.7",
"invariant": "^2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-location": {
"version": "55.1.4",
"resolved": "https://registry.npmjs.org/expo-location/-/expo-location-55.1.4.tgz",
"integrity": "sha512-0QWQ4QP8I6svtGUL895Y8bvKM3nGUWNp/MdCoCQNH3uAuwvLf2TQ6ZDVD3kdlkl2wGGdw6ziHQIYDHL96OlfnA==",
"license": "MIT",
"dependencies": {
"@expo/image-utils": "^0.8.12"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-modules-autolinking": {
"version": "55.0.11",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-55.0.11.tgz",
"integrity": "sha512-9dqnPzQoIl1dIvEctMWpQ8eaiXDeBTgAwebCc1WF0BbEo+pcdKjZWoCSqlLj+d7IX+OnTgM+k6cY2kPDGIu4sg==",
"license": "MIT",
"dependencies": {
"@expo/require-utils": "^55.0.3",
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.1.0",
"commander": "^7.2.0"
},
"bin": {
"expo-modules-autolinking": "bin/expo-modules-autolinking.js"
}
},
"node_modules/expo-modules-core": {
"version": "55.0.17",
"resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-55.0.17.tgz",
"integrity": "sha512-pw3cZiaSlBrqRJUD/pHuMnKGsRTW6XJ255FrjDd3HC4QrqErCnfSQPmz+Sv4Qkelcvd9UGdAewyTqZdFwjLwOw==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-notifications": {
"version": "0.30.7",
"resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.30.7.tgz",
"integrity": "sha512-ExVxLIyD0SuoeO2icCbwtbqseblDP0/QK5fxCp4q2dk37DYd8yHoyIyxFBtqGE5eijuBOfIJ9nPs3bo9YW4b5g==",
"license": "MIT",
"dependencies": {
"@expo/image-utils": "^0.7.2",
"@ide/backoff": "^1.0.0",
"abort-controller": "^3.0.0",
"assert": "^2.0.0",
"badgin": "^1.1.5",
"expo-application": "~6.1.2",
"expo-constants": "~17.1.2"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-notifications/node_modules/@expo/image-utils": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.7.6.tgz",
"integrity": "sha512-GKnMqC79+mo/1AFrmAcUcGfbsXXTRqOMNS1umebuevl3aaw+ztsYEFEiuNhHZW7PQ3Xs3URNT513ZxKhznDscw==",
"license": "MIT",
"dependencies": {
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.0.0",
"getenv": "^2.0.0",
"jimp-compact": "0.16.1",
"parse-png": "^2.1.0",
"resolve-from": "^5.0.0",
"semver": "^7.6.0",
"temp-dir": "~2.0.0",
"unique-string": "~2.0.0"
}
},
"node_modules/expo-secure-store": {
"version": "55.0.9",
"resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-55.0.9.tgz",
"integrity": "sha512-TIPGjM73LKlebpXwgAu/yL7lNWr6RQYmFw3vgYHOqLFYQMpsBqkQmopovbNX3c/0+RCE9KZlLAkcz8r6detILQ==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-server": {
"version": "55.0.6",
"resolved": "https://registry.npmjs.org/expo-server/-/expo-server-55.0.6.tgz",
"integrity": "sha512-xI72FTm469FfuuBL2R5aNtthgH+GR7ygOpsx/KcPS0K8AZaZd7VjtEExbzn9/qyyYkWW3T+3dAmCDKOMX8gdmQ==",
"license": "MIT",
"engines": {
"node": ">=20.16.0"
}
},
"node_modules/expo-status-bar": {
"version": "55.0.4",
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-55.0.4.tgz",
"integrity": "sha512-BPDjUXKqv1F9j2YNGLRZfkBEZXIEEpqj+t81y4c+4fdSN3Pos7goIHXgcl2ozbKQLgKRZQyNZQtbUgh5UjHYUQ==",
"license": "MIT",
"dependencies": {
"react-native-is-edge-to-edge": "^1.2.1"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/@expo/cli": {
"version": "55.0.18",
"resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.18.tgz",
"integrity": "sha512-3sJwu8KvCvQIXBnhUlHgLBZBe+ZK4Da9R5rgI4znaowJavYWMqzRClLzyE6Kri66WVoMX7Q4HUVIh8prRlO0XA==",
"license": "MIT",
"dependencies": {
"@expo/code-signing-certificates": "^0.0.6",
"@expo/config": "~55.0.10",
"@expo/config-plugins": "~55.0.7",
"@expo/devcert": "^1.2.1",
"@expo/env": "~2.1.1",
"@expo/image-utils": "^0.8.12",
"@expo/json-file": "^10.0.12",
"@expo/log-box": "55.0.7",
"@expo/metro": "~54.2.0",
"@expo/metro-config": "~55.0.11",
"@expo/osascript": "^2.4.2",
"@expo/package-manager": "^1.10.3",
"@expo/plist": "^0.5.2",
"@expo/prebuild-config": "^55.0.10",
"@expo/require-utils": "^55.0.3",
"@expo/router-server": "^55.0.11",
"@expo/schema-utils": "^55.0.2",
"@expo/spawn-async": "^1.7.2",
"@expo/ws-tunnel": "^1.0.1",
"@expo/xcpretty": "^4.4.0",
"@react-native/dev-middleware": "0.83.2",
"accepts": "^1.3.8",
"arg": "^5.0.2",
"better-opn": "~3.0.2",
"bplist-creator": "0.1.0",
"bplist-parser": "^0.3.1",
"chalk": "^4.0.0",
"ci-info": "^3.3.0",
"compression": "^1.7.4",
"connect": "^3.7.0",
"debug": "^4.3.4",
"dnssd-advertise": "^1.1.3",
"expo-server": "^55.0.6",
"fetch-nodeshim": "^0.4.6",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"lan-network": "^0.2.0",
"multitars": "^0.2.3",
"node-forge": "^1.3.3",
"npm-package-arg": "^11.0.0",
"ora": "^3.4.0",
"picomatch": "^4.0.3",
"pretty-format": "^29.7.0",
"progress": "^2.0.3",
"prompts": "^2.3.2",
"resolve-from": "^5.0.0",
"semver": "^7.6.0",
"send": "^0.19.0",
"slugify": "^1.3.4",
"source-map-support": "~0.5.21",
"stacktrace-parser": "^0.1.10",
"structured-headers": "^0.4.1",
"terminal-link": "^2.1.1",
"toqr": "^0.1.1",
"wrap-ansi": "^7.0.0",
"ws": "^8.12.1",
"zod": "^3.25.76"
},
"bin": {
"expo-internal": "build/bin/cli"
},
"peerDependencies": {
"expo": "*",
"expo-router": "*",
"react-native": "*"
},
"peerDependenciesMeta": {
"expo-router": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/expo/node_modules/@expo/cli/node_modules/@expo/prebuild-config": {
"version": "55.0.10",
"resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-55.0.10.tgz",
"integrity": "sha512-AMylDld5G7YJGfEhEyXtgWRuBB83802QBoewF1vJ6NMDtufukuPhMJzOs9E4UXNsjLTaQcgT4yTWhsAWl7o1AQ==",
"license": "MIT",
"dependencies": {
"@expo/config": "~55.0.10",
"@expo/config-plugins": "~55.0.7",
"@expo/config-types": "^55.0.5",
"@expo/image-utils": "^0.8.12",
"@expo/json-file": "^10.0.12",
"@react-native/normalize-colors": "0.83.2",
"debug": "^4.3.1",
"resolve-from": "^5.0.0",
"semver": "^7.6.0",
"xml2js": "0.6.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": {
"version": "55.0.11",
"resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-55.0.11.tgz",
"integrity": "sha512-Kd8J1OOlFR00DZxn+1KfiQiXZtRut6cj8+ynqHJa7dtt/lTL4tGkYistqmVhpKJ6w886eRY5WivKy7o0ZBFkJA==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4"
},
"peerDependencies": {
"@expo/metro-runtime": "^55.0.6",
"expo": "*",
"expo-constants": "^55.0.9",
"expo-font": "^55.0.4",
"expo-router": "*",
"expo-server": "^55.0.6",
"react": "*",
"react-dom": "*",
"react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1"
},
"peerDependenciesMeta": {
"@expo/metro-runtime": {
"optional": true
},
"expo-router": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-server-dom-webpack": {
"optional": true
}
}
},
"node_modules/expo/node_modules/@expo/log-box": {
"version": "55.0.7",
"resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-55.0.7.tgz",
"integrity": "sha512-m7V1k2vlMp4NOj3fopjOg4zl/ANXyTRF3HMTMep2GZAKsPiDzgOQ41nm8CaU50/HlDIGXlCObss07gOn20UpHQ==",
"license": "MIT",
"dependencies": {
"@expo/dom-webview": "^55.0.3",
"anser": "^1.4.9",
"stacktrace-parser": "^0.1.10"
},
"peerDependencies": {
"@expo/dom-webview": "^55.0.3",
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/@expo/log-box/node_modules/@expo/dom-webview": {
"version": "55.0.3",
"resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-55.0.3.tgz",
"integrity": "sha512-bY4/rfcZ0f43DvOtMn8/kmPlmo01tex5hRoc5hKbwBwQjqWQuQt0ACwu7akR9IHI4j0WNG48eL6cZB6dZUFrzg==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/@expo/metro-config": {
"version": "55.0.11",
"resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-55.0.11.tgz",
"integrity": "sha512-qGxq7RwWpj0zNvZO/e5aizKrOKYYBrVPShSbxPOVB1EXcexxTPTxnOe4pYFg/gKkLIJe0t3jSSF8IDWlGdaaOg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.20.0",
"@babel/core": "^7.20.0",
"@babel/generator": "^7.20.5",
"@expo/config": "~55.0.10",
"@expo/env": "~2.1.1",
"@expo/json-file": "~10.0.12",
"@expo/metro": "~54.2.0",
"@expo/spawn-async": "^1.7.2",
"browserslist": "^4.25.0",
"chalk": "^4.1.0",
"debug": "^4.3.2",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"hermes-parser": "^0.32.0",
"jsc-safe-url": "^0.2.4",
"lightningcss": "^1.30.1",
"picomatch": "^4.0.3",
"postcss": "~8.4.32",
"resolve-from": "^5.0.0"
},
"peerDependencies": {
"expo": "*"
},
"peerDependenciesMeta": {
"expo": {
"optional": true
}
}
},
"node_modules/expo/node_modules/@expo/vector-icons": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.1.1.tgz",
"integrity": "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw==",
"license": "MIT",
"peerDependencies": {
"expo-font": ">=14.0.4",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/babel-preset-expo": {
"version": "55.0.12",
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.12.tgz",
"integrity": "sha512-oR46ExGZpRijmPUsr0rFH5X4lR/mvwqJAFXJRLpynZcvyv2pHPTeGMNfd/p5oPMbdbaeMS6G+3k18p48u2Qjbw==",
"license": "MIT",
"dependencies": {
"@babel/generator": "^7.20.5",
"@babel/helper-module-imports": "^7.25.9",
"@babel/plugin-proposal-decorators": "^7.12.9",
"@babel/plugin-proposal-export-default-from": "^7.24.7",
"@babel/plugin-syntax-export-default-from": "^7.24.7",
"@babel/plugin-transform-class-static-block": "^7.27.1",
"@babel/plugin-transform-export-namespace-from": "^7.25.9",
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/plugin-transform-object-rest-spread": "^7.24.7",
"@babel/plugin-transform-parameters": "^7.24.7",
"@babel/plugin-transform-private-methods": "^7.24.7",
"@babel/plugin-transform-private-property-in-object": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.23.0",
"@react-native/babel-preset": "0.83.2",
"babel-plugin-react-compiler": "^1.0.0",
"babel-plugin-react-native-web": "~0.21.0",
"babel-plugin-syntax-hermes-parser": "^0.32.0",
"babel-plugin-transform-flow-enums": "^0.0.2",
"debug": "^4.3.4",
"resolve-from": "^5.0.0"
},
"peerDependencies": {
"@babel/runtime": "^7.20.0",
"expo": "*",
"expo-widgets": "^55.0.6",
"react-refresh": ">=0.14.0 <1.0.0"
},
"peerDependenciesMeta": {
"@babel/runtime": {
"optional": true
},
"expo": {
"optional": true
},
"expo-widgets": {
"optional": true
}
}
},
"node_modules/expo/node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/expo/node_modules/expo-asset": {
"version": "55.0.10",
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-55.0.10.tgz",
"integrity": "sha512-wxjNBKIaDyachq7oJgVlWVFzZ6SnNpJFJhkkcymXoTPt5O3XmDM+a6fT91xQQawCXTyZuCc1sNxKMetEofeYkg==",
"license": "MIT",
"dependencies": {
"@expo/image-utils": "^0.8.12",
"expo-constants": "~55.0.9"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-constants": {
"version": "55.0.9",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-55.0.9.tgz",
"integrity": "sha512-iBiXjZeuU5S/8docQeNzsVvtDy4w0zlmXBpFEi1ypwugceEpdQQab65TVRbusXAcwpNVxCPMpNlDssYp0Pli2g==",
"license": "MIT",
"dependencies": {
"@expo/config": "~55.0.10",
"@expo/env": "~2.1.1"
},
"peerDependencies": {
"expo": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-file-system": {
"version": "55.0.11",
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.11.tgz",
"integrity": "sha512-KMUd6OY375J9WD79ZvjvCDZMveT7YfgiGWdi58/gfuTBsr14TRuoPk8RRQHAtc4UquzWViKcHwna9aPY7/XPpw==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-font": {
"version": "55.0.4",
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.4.tgz",
"integrity": "sha512-ZKeGTFffPygvY5dM/9ATM2p7QDkhsaHopH7wFAWgP2lKzqUMS9B/RxCvw5CaObr9Ro7x9YptyeRKX2HmgmMfrg==",
"license": "MIT",
"dependencies": {
"fontfaceobserver": "^2.1.0"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-keep-awake": {
"version": "55.0.4",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-55.0.4.tgz",
"integrity": "sha512-vwfdMtMS5Fxaon8gC0AiE70SpxTsHJ+rjeoVJl8kdfdbxczF7OIaVmfjFJ5Gfigd/WZiLqxhfZk34VAkXF4PNg==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react": "*"
}
},
"node_modules/expo/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/expo/node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/exponential-backoff": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz",
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
"license": "Apache-2.0"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"license": "MIT"
},
"node_modules/fb-dotslash": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz",
"integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==",
"license": "(MIT OR Apache-2.0)",
"bin": {
"dotslash": "bin/dotslash"
},
"engines": {
"node": ">=20"
}
},
"node_modules/fb-watchman": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
"integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
"license": "Apache-2.0",
"dependencies": {
"bser": "2.1.1"
}
},
"node_modules/fetch-nodeshim": {
"version": "0.4.9",
"resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.9.tgz",
"integrity": "sha512-XIQWlB2A4RZ7NebXWGxS0uDMdvRHkiUDTghBVJKFg9yEOd45w/PP8cZANuPf2H08W6Cor3+2n7Q6TTZgAS3Fkw==",
"license": "MIT"
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/finalhandler/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/finalhandler/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/flow-enums-runtime": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz",
"integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==",
"license": "MIT"
},
"node_modules/fontfaceobserver": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz",
"integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==",
"license": "BSD-2-Clause"
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
"license": "MIT",
"dependencies": {
"is-callable": "^1.2.7"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/foreground-child/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"license": "ISC"
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
"integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/getenv": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz",
"integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.2.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hermes-compiler": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.14.1.tgz",
"integrity": "sha512-+RPPQlayoZ9n6/KXKt5SFILWXCGJ/LV5d24L5smXrvTDrPS4L6dSctPczXauuvzFP3QEJbD1YO7Z3Ra4a+4IhA==",
"license": "MIT"
},
"node_modules/hermes-estree": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz",
"integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==",
"license": "MIT"
},
"node_modules/hermes-parser": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz",
"integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==",
"license": "MIT",
"dependencies": {
"hermes-estree": "0.32.0"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"license": "BSD-3-Clause",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hoist-non-react-statics/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/hosted-git-info": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
"integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==",
"license": "ISC",
"dependencies": {
"lru-cache": "^10.0.1"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/hosted-git-info/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/http-errors/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iceberg-js": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
"integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/image-size": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
"integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=16.x"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"license": "MIT",
"engines": {
"node": ">=0.8.19"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"license": "MIT",
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-generator-function": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
"integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.4",
"generator-function": "^2.0.0",
"get-proto": "^1.0.1",
"has-tostringtag": "^1.0.2",
"safe-regex-test": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-nan": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
"integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-typed-array": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
"license": "MIT",
"dependencies": {
"which-typed-array": "^1.1.16"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-instrument": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
"integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/core": "^7.12.3",
"@babel/parser": "^7.14.7",
"@istanbuljs/schema": "^0.1.2",
"istanbul-lib-coverage": "^3.2.0",
"semver": "^6.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-instrument/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jest-environment-node": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
"integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
"license": "MIT",
"dependencies": {
"@jest/environment": "^29.7.0",
"@jest/fake-timers": "^29.7.0",
"@jest/types": "^29.6.3",
"@types/node": "*",
"jest-mock": "^29.7.0",
"jest-util": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-get-type": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
"integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
"license": "MIT",
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-haste-map": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
"integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"@types/graceful-fs": "^4.1.3",
"@types/node": "*",
"anymatch": "^3.0.3",
"fb-watchman": "^2.0.0",
"graceful-fs": "^4.2.9",
"jest-regex-util": "^29.6.3",
"jest-util": "^29.7.0",
"jest-worker": "^29.7.0",
"micromatch": "^4.0.4",
"walker": "^1.0.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
},
"optionalDependencies": {
"fsevents": "^2.3.2"
}
},
"node_modules/jest-message-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
"integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.12.13",
"@jest/types": "^29.6.3",
"@types/stack-utils": "^2.0.0",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"micromatch": "^4.0.4",
"pretty-format": "^29.7.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-mock": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
"integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"@types/node": "*",
"jest-util": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-regex-util": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
"integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
"license": "MIT",
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
"integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"@types/node": "*",
"chalk": "^4.0.0",
"ci-info": "^3.2.0",
"graceful-fs": "^4.2.9",
"picomatch": "^2.2.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-util/node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/jest-validate": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
"integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"camelcase": "^6.2.0",
"chalk": "^4.0.0",
"jest-get-type": "^29.6.3",
"leven": "^3.1.0",
"pretty-format": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-worker": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
"integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"jest-util": "^29.7.0",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-worker/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/jimp-compact": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/jimp-compact/-/jimp-compact-0.16.1.tgz",
"integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==",
"license": "MIT"
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsc-safe-url": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz",
"integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==",
"license": "0BSD"
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/lan-network": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.0.tgz",
"integrity": "sha512-EZgbsXMrGS+oK+Ta12mCjzBFse+SIewGdwrSTr5g+MSymnjpox2x05ceI20PQejJOFvOgzcXrfDk/SdY7dSCtw==",
"license": "MIT",
"bin": {
"lan-network": "dist/lan-network-cli.js"
}
},
"node_modules/leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
"integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/lighthouse-logger": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz",
"integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==",
"license": "Apache-2.0",
"dependencies": {
"debug": "^2.6.9",
"marky": "^1.2.2"
}
},
"node_modules/lighthouse-logger/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/lighthouse-logger/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.32.0",
"lightningcss-darwin-arm64": "1.32.0",
"lightningcss-darwin-x64": "1.32.0",
"lightningcss-freebsd-x64": "1.32.0",
"lightningcss-linux-arm-gnueabihf": "1.32.0",
"lightningcss-linux-arm64-gnu": "1.32.0",
"lightningcss-linux-arm64-musl": "1.32.0",
"lightningcss-linux-x64-gnu": "1.32.0",
"lightningcss-linux-x64-musl": "1.32.0",
"lightningcss-win32-arm64-msvc": "1.32.0",
"lightningcss-win32-x64-msvc": "1.32.0"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
"cpu": [
"arm"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
"license": "MIT"
},
"node_modules/log-symbols": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
"license": "MIT",
"dependencies": {
"chalk": "^2.0.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/log-symbols/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/log-symbols/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/log-symbols/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/log-symbols/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/log-symbols/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/log-symbols/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/log-symbols/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/makeerror": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
"integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
"license": "BSD-3-Clause",
"dependencies": {
"tmpl": "1.0.5"
}
},
"node_modules/marky": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz",
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
"license": "Apache-2.0"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"license": "MIT"
},
"node_modules/metro": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro/-/metro-0.83.3.tgz",
"integrity": "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/core": "^7.25.2",
"@babel/generator": "^7.25.0",
"@babel/parser": "^7.25.3",
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.3",
"@babel/types": "^7.25.2",
"accepts": "^1.3.7",
"chalk": "^4.0.0",
"ci-info": "^2.0.0",
"connect": "^3.6.5",
"debug": "^4.4.0",
"error-stack-parser": "^2.0.6",
"flow-enums-runtime": "^0.0.6",
"graceful-fs": "^4.2.4",
"hermes-parser": "0.32.0",
"image-size": "^1.0.2",
"invariant": "^2.2.4",
"jest-worker": "^29.7.0",
"jsc-safe-url": "^0.2.2",
"lodash.throttle": "^4.1.1",
"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",
"source-map": "^0.5.6",
"throat": "^5.0.0",
"ws": "^7.5.10",
"yargs": "^17.6.2"
},
"bin": {
"metro": "src/cli.js"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-babel-transformer": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.3.tgz",
"integrity": "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"flow-enums-runtime": "^0.0.6",
"hermes-parser": "0.32.0",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-cache": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.3.tgz",
"integrity": "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==",
"license": "MIT",
"dependencies": {
"exponential-backoff": "^3.1.1",
"flow-enums-runtime": "^0.0.6",
"https-proxy-agent": "^7.0.5",
"metro-core": "0.83.3"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-cache-key": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.3.tgz",
"integrity": "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-config": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.3.tgz",
"integrity": "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==",
"license": "MIT",
"dependencies": {
"connect": "^3.6.5",
"flow-enums-runtime": "^0.0.6",
"jest-validate": "^29.7.0",
"metro": "0.83.3",
"metro-cache": "0.83.3",
"metro-core": "0.83.3",
"metro-runtime": "0.83.3",
"yaml": "^2.6.1"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-core": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.3.tgz",
"integrity": "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"lodash.throttle": "^4.1.1",
"metro-resolver": "0.83.3"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-file-map": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.3.tgz",
"integrity": "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"fb-watchman": "^2.0.0",
"flow-enums-runtime": "^0.0.6",
"graceful-fs": "^4.2.4",
"invariant": "^2.2.4",
"jest-worker": "^29.7.0",
"micromatch": "^4.0.4",
"nullthrows": "^1.1.1",
"walker": "^1.0.7"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-minify-terser": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.3.tgz",
"integrity": "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"terser": "^5.15.0"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-resolver": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.3.tgz",
"integrity": "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-runtime": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.3.tgz",
"integrity": "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.0",
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-source-map": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.3.tgz",
"integrity": "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==",
"license": "MIT",
"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.3",
"nullthrows": "^1.1.1",
"ob1": "0.83.3",
"source-map": "^0.5.6",
"vlq": "^1.0.0"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-symbolicate": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.3.tgz",
"integrity": "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"invariant": "^2.2.4",
"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"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-transform-plugins": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.3.tgz",
"integrity": "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/generator": "^7.25.0",
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.3",
"flow-enums-runtime": "^0.0.6",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-transform-worker": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.3.tgz",
"integrity": "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==",
"license": "MIT",
"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.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"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/minimatch": {
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/multitars": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/multitars/-/multitars-0.2.4.tgz",
"integrity": "sha512-XgLbg1HHchFauMCQPRwMj6MSyDd5koPlTA1hM3rUFkeXzGpjU/I9fP3to7yrObE9jcN8ChIOQGrM0tV0kUZaKg==",
"license": "MIT"
},
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0",
"object-assign": "^4.0.1",
"thenify-all": "^1.0.0"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-forge": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz",
"integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
}
},
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
"license": "MIT"
},
"node_modules/node-releases": {
"version": "2.0.36",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
"integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
"license": "MIT"
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-package-arg": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz",
"integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==",
"license": "ISC",
"dependencies": {
"hosted-git-info": "^7.0.0",
"proc-log": "^4.0.0",
"semver": "^7.3.5",
"validate-npm-package-name": "^5.0.0"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/nullthrows": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==",
"license": "MIT"
},
"node_modules/ob1": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.3.tgz",
"integrity": "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/object.assign": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
"integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.8",
"call-bound": "^1.0.3",
"define-properties": "^1.2.1",
"es-object-atoms": "^1.0.0",
"has-symbols": "^1.1.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
"license": "MIT",
"dependencies": {
"mimic-fn": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/open": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0",
"is-wsl": "^2.1.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ora": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz",
"integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==",
"license": "MIT",
"dependencies": {
"chalk": "^2.4.2",
"cli-cursor": "^2.1.0",
"cli-spinners": "^2.0.0",
"log-symbols": "^2.2.0",
"strip-ansi": "^5.2.0",
"wcwidth": "^1.0.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/ora/node_modules/ansi-regex": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/ora/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ora/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ora/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/ora/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/ora/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/ora/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/ora/node_modules/strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^4.1.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/ora/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/parse-png": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz",
"integrity": "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==",
"license": "MIT",
"dependencies": {
"pngjs": "^3.3.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "11.2.7",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
"integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pirates": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/plist": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
"integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"base64-js": "^1.5.1",
"xmlbuilder": "^15.1.1"
},
"engines": {
"node": ">=10.4.0"
}
},
"node_modules/pngjs": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
"license": "MIT",
"dependencies": {
"@jest/schemas": "^29.6.3",
"ansi-styles": "^5.0.0",
"react-is": "^18.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/pretty-format/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/proc-log": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz",
"integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==",
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/promise": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz",
"integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==",
"license": "MIT",
"dependencies": {
"asap": "~2.0.6"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
"license": "MIT",
"dependencies": {
"kleur": "^3.0.3",
"sisteransi": "^1.0.5"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/query-string": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"license": "MIT",
"dependencies": {
"decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/react": {
"version": "19.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-devtools-core": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz",
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
"license": "MIT",
"dependencies": {
"shell-quote": "^1.6.1",
"ws": "^7"
}
},
"node_modules/react-freeze": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
"integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=17.0.0"
}
},
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/react-native": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.83.2.tgz",
"integrity": "sha512-ZDma3SLkRN2U2dg0/EZqxNBAx4of/oTnPjXAQi299VLq2gdnbZowGy9hzqv+O7sTA62g+lM1v+2FM5DUnJ/6hg==",
"license": "MIT",
"dependencies": {
"@jest/create-cache-key-function": "^29.7.0",
"@react-native/assets-registry": "0.83.2",
"@react-native/codegen": "0.83.2",
"@react-native/community-cli-plugin": "0.83.2",
"@react-native/gradle-plugin": "0.83.2",
"@react-native/js-polyfills": "0.83.2",
"@react-native/normalize-colors": "0.83.2",
"@react-native/virtualized-lists": "0.83.2",
"abort-controller": "^3.0.0",
"anser": "^1.4.9",
"ansi-regex": "^5.0.0",
"babel-jest": "^29.7.0",
"babel-plugin-syntax-hermes-parser": "0.32.0",
"base64-js": "^1.5.1",
"commander": "^12.0.0",
"flow-enums-runtime": "^0.0.6",
"glob": "^7.1.1",
"hermes-compiler": "0.14.1",
"invariant": "^2.2.4",
"jest-environment-node": "^29.7.0",
"memoize-one": "^5.0.0",
"metro-runtime": "^0.83.3",
"metro-source-map": "^0.83.3",
"nullthrows": "^1.1.1",
"pretty-format": "^29.7.0",
"promise": "^8.3.0",
"react-devtools-core": "^6.1.5",
"react-refresh": "^0.14.0",
"regenerator-runtime": "^0.13.2",
"scheduler": "0.27.0",
"semver": "^7.1.3",
"stacktrace-parser": "^0.1.10",
"whatwg-fetch": "^3.0.0",
"ws": "^7.5.10",
"yargs": "^17.6.2"
},
"bin": {
"react-native": "cli.js"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@types/react": "^19.1.1",
"react": "^19.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-native-gesture-handler": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.30.0.tgz",
"integrity": "sha512-5YsnKHGa0X9C8lb5oCnKm0fLUPM6CRduvUUw2Bav4RIj/C3HcFh4RIUnF8wgG6JQWCL1//gRx4v+LVWgcIQdGA==",
"license": "MIT",
"dependencies": {
"@egjs/hammerjs": "^2.0.17",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-is-edge-to-edge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz",
"integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-maps": {
"version": "1.27.2",
"resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.27.2.tgz",
"integrity": "sha512-VKr+xZ2RZGHHJlY6KhlafvGSmK0dq/tUu5uhfJ7K9rwN5pUdubdugzMKGDU/16lXmQSg7xbClKhRctj3Pm5F5g==",
"license": "MIT",
"dependencies": {
"@types/geojson": "^7946.0.13"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"react": ">= 18.3.1",
"react-native": ">= 0.76.0",
"react-native-web": ">= 0.11"
},
"peerDependenciesMeta": {
"react-native-web": {
"optional": true
}
}
},
"node_modules/react-native-safe-area-context": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz",
"integrity": "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-screens": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.23.0.tgz",
"integrity": "sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw==",
"license": "MIT",
"dependencies": {
"react-freeze": "^1.0.0",
"warn-once": "^0.1.0"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.2.tgz",
"integrity": "sha512-N7mRjHLW/+KWxMp9IHRWyE3VIkeG1m3PnZJAGEFLCN8VFb7e4VfI567o7tE/HYcdcXCylw+Eqhlciz8gDeQ71g==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@types/react": "^19.2.0",
"react": "*",
"react-native": "*"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-native/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/react-native/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/react-native/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/react-native/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/react-native/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
"integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
"license": "MIT"
},
"node_modules/regenerate-unicode-properties": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
"integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT"
},
"node_modules/regexpu-core": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz",
"integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2",
"regenerate-unicode-properties": "^10.2.2",
"regjsgen": "^0.8.0",
"regjsparser": "^0.13.0",
"unicode-match-property-ecmascript": "^2.0.0",
"unicode-match-property-value-ecmascript": "^2.2.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/regjsgen": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
"integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
"license": "MIT"
},
"node_modules/regjsparser": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz",
"integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
"license": "BSD-2-Clause",
"dependencies": {
"jsesc": "~3.1.0"
},
"bin": {
"regjsparser": "bin/parser"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/resolve-workspace-root": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.1.tgz",
"integrity": "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==",
"license": "MIT"
},
"node_modules/restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
"license": "MIT",
"dependencies": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/rimraf/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/rimraf/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safe-regex-test": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"is-regex": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sax": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/send": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.1",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.4.1",
"range-parser": "~1.2.1",
"statuses": "~2.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/send/node_modules/debug/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/send/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/serialize-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz",
"integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/serve-static": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "~0.19.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/serve-static/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/sf-symbols-typescript": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz",
"integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/simple-plist": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz",
"integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==",
"license": "MIT",
"dependencies": {
"bplist-creator": "0.1.0",
"bplist-parser": "0.3.1",
"plist": "^3.0.5"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/slugify": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.8.tgz",
"integrity": "sha512-HVk9X1E0gz3mSpoi60h/saazLKXKaZThMLU3u/aNwoYn8/xQyX2MGxL0ui2eaokkD7tF+Zo+cKTHUbe1mmmGzA==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/source-map-support/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"license": "BSD-3-Clause"
},
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/stack-utils/node_modules/escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/stackframe": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
"license": "MIT"
},
"node_modules/stacktrace-parser": {
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz",
"integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==",
"license": "MIT",
"dependencies": {
"type-fest": "^0.7.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/stream-buffers": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz",
"integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==",
"license": "Unlicense",
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/structured-headers": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz",
"integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==",
"license": "MIT"
},
"node_modules/sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
"commander": "^4.0.0",
"glob": "^10.3.10",
"lines-and-columns": "^1.1.6",
"mz": "^2.7.0",
"pirates": "^4.0.1",
"ts-interface-checker": "^0.1.9"
},
"bin": {
"sucrase": "bin/sucrase",
"sucrase-node": "bin/sucrase-node"
},
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/sucrase/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/sucrase/node_modules/brace-expansion": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/sucrase/node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/sucrase/node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sucrase/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/sucrase/node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sucrase/node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-hyperlinks": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
"integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0",
"supports-color": "^7.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tagged-tag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
"integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/temp-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
"integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/terminal-link": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz",
"integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==",
"license": "MIT",
"dependencies": {
"ansi-escapes": "^4.2.1",
"supports-hyperlinks": "^2.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/terser": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz",
"integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==",
"license": "BSD-2-Clause",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
"integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
"glob": "^7.1.4",
"minimatch": "^3.0.4"
},
"engines": {
"node": ">=8"
}
},
"node_modules/test-exclude/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/test-exclude/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/test-exclude/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/test-exclude/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0"
}
},
"node_modules/thenify-all": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"license": "MIT",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/throat": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
"license": "MIT"
},
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"license": "BSD-3-Clause"
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/toqr": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/toqr/-/toqr-0.1.1.tgz",
"integrity": "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==",
"license": "MIT"
},
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"license": "Apache-2.0"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/type-fest": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz",
"integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=8"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
"integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-match-property-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
"integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
"license": "MIT",
"dependencies": {
"unicode-canonical-property-names-ecmascript": "^2.0.0",
"unicode-property-aliases-ecmascript": "^2.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-match-property-value-ecmascript": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
"integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-property-aliases-ecmascript": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
"integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/unique-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
"integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
"license": "MIT",
"dependencies": {
"crypto-random-string": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
},
"peerDependencies": {
"browserslist": ">= 4.21.0"
}
},
"node_modules/use-latest-callback": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz",
"integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
"is-generator-function": "^1.0.7",
"is-typed-array": "^1.1.3",
"which-typed-array": "^1.1.2"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/validate-npm-package-name": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz",
"integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==",
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vlq": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz",
"integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==",
"license": "MIT"
},
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
"integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
"license": "Apache-2.0",
"dependencies": {
"makeerror": "1.0.12"
}
},
"node_modules/warn-once": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz",
"integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==",
"license": "MIT"
},
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
"license": "MIT",
"dependencies": {
"defaults": "^1.0.3"
}
},
"node_modules/whatwg-fetch": {
"version": "3.6.20",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
"license": "MIT"
},
"node_modules/whatwg-url-minimum": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/whatwg-url-minimum/-/whatwg-url-minimum-0.1.1.tgz",
"integrity": "sha512-u2FNVjFVFZhdjb502KzXy1gKn1mEisQRJssmSJT8CPhZdZa0AP6VCbWlXERKyGu0l09t0k50FiDiralpGhBxgA==",
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/which-typed-array": {
"version": "1.1.22",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz",
"integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==",
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.9",
"call-bound": "^1.0.4",
"for-each": "^0.3.5",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/write-file-atomic": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
"integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
"license": "ISC",
"dependencies": {
"imurmurhash": "^0.1.4",
"signal-exit": "^3.0.7"
},
"engines": {
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xcode": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz",
"integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==",
"license": "Apache-2.0",
"dependencies": {
"simple-plist": "^1.1.0",
"uuid": "^7.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/xml2js": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
"integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xml2js/node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
},
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
"license": "MIT",
"engines": {
"node": ">=8.0"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zustand": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
"integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
},
"node_modules/zxing-wasm": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-3.0.1.tgz",
"integrity": "sha512-3CLj6iaGkpqPWXAB4pIWkFOR63MwqGekpMzaROFKto4dFowiPmLlC56KoMoOSXzqOCOpI5DAvMdB8ku2va6fUg==",
"license": "MIT",
"dependencies": {
"@types/emscripten": "^1.41.5",
"type-fest": "^5.4.4"
},
"peerDependencies": {
"@types/emscripten": ">=1.39.6"
}
},
"node_modules/zxing-wasm/node_modules/type-fest": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz",
"integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==",
"license": "(MIT OR CC0-1.0)",
"dependencies": {
"tagged-tag": "^1.0.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}
}
}
+29 -59
View File
@@ -1,68 +1,38 @@
{
"name": "quixzoom-app",
"name": "quixzoom-mobile",
"version": "1.0.0",
"description": "quiXzoom Native App — Field data collection for iOS/Android",
"main": "index.js",
"main": "index.ts",
"scripts": {
"start": "react-native start",
"android": "react-native run-android",
"ios": "react-native run-ios",
"test": "jest",
"lint": "eslint ."
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"react": "18.2.0",
"react-native": "0.72.0",
"@react-native-async-storage/async-storage": "^1.19.0",
"@react-native-community/geolocation": "^3.0.0",
"react-native-vision-camera": "^3.0.0",
"react-native-maps": "^1.7.0",
"react-native-vector-icons": "^10.0.0",
"react-native-share": "^9.0.0",
"react-native-fs": "^2.20.0",
"react-native-image-resizer": "^3.0.0",
"react-native-background-upload": "^6.0.0",
"react-native-offline": "^6.0.0",
"react-native-permissions": "^3.8.0",
"react-native-device-info": "^10.0.0",
"react-native-sensors": "^7.3.0",
"react-native-keep-awake": "^4.0.0",
"react-native-haptic-feedback": "^2.0.0",
"react-native-localize": "^3.0.0",
"react-native-keychain": "^8.1.0",
"react-native-encrypted-storage": "^4.0.0",
"@react-navigation/native": "^6.1.0",
"@react-navigation/bottom-tabs": "^6.5.0",
"@react-navigation/stack": "^6.3.0",
"react-native-gesture-handler": "^2.12.0",
"react-native-reanimated": "^3.3.0",
"react-native-screens": "^3.22.0",
"react-native-safe-area-context": "^4.6.0",
"@reduxjs/toolkit": "^1.9.0",
"react-redux": "^8.1.0",
"redux-persist": "^6.0.0",
"axios": "^1.4.0",
"date-fns": "^2.30.0",
"uuid": "^9.0.0"
"@react-navigation/bottom-tabs": "^7.15.7",
"@react-navigation/native": "^7.2.0",
"@react-navigation/native-stack": "^7.14.7",
"@supabase/supabase-js": "^2.100.0",
"@tanstack/react-query": "^5.95.2",
"expo": "~55.0.8",
"expo-camera": "~55.0.10",
"expo-image-picker": "~55.0.13",
"expo-linking": "~7.1.4",
"expo-location": "~55.1.4",
"expo-notifications": "^0.30.7",
"expo-secure-store": "~55.0.9",
"expo-status-bar": "~55.0.4",
"react": "19.2.0",
"react-native": "0.83.2",
"react-native-gesture-handler": "~2.30.0",
"react-native-maps": "^1.27.2",
"react-native-safe-area-context": "^5.7.0",
"react-native-screens": "~4.23.0",
"zustand": "^5.0.12"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/eslint-config": "^0.72.0",
"@react-native/metro-config": "^0.72.0",
"@tsconfig/react-native": "^3.0.0",
"@types/react": "^18.0.24",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.2.1",
"eslint": "^8.19.0",
"jest": "^29.2.1",
"metro-react-native-babel-preset": "0.76.5",
"prettier": "^2.4.1",
"react-test-renderer": "18.2.0",
"typescript": "4.8.4"
"@types/react": "~19.2.2",
"typescript": "~5.9.2"
},
"jest": {
"preset": "react-native"
}
"private": true
}
+263
View File
@@ -0,0 +1,263 @@
/**
* EarningsScreen — saldo + utbetalningshistorik
*/
import { useCallback, useEffect, useState } from 'react'
import {
View, Text, ScrollView, TouchableOpacity,
StyleSheet, ActivityIndicator, RefreshControl, Alert,
} from 'react-native'
import { wallet, payouts, type WalletBalance, type Payout } from '../lib/api'
type TabKey = 'oversikt' | 'utbetalningar'
const PAYOUT_STATUS: Record<string, { label: string; color: string }> = {
pending: { label: 'Väntar', color: '#f59e0b' },
approved: { label: 'Godkänd', color: '#10b981' },
paid: { label: 'Utbetald', color: '#6366f1' },
rejected: { label: 'Nekad', color: '#ef4444' },
}
export function EarningsScreen() {
const [tab, setTab] = useState<TabKey>('oversikt')
const [balance, setBalance] = useState<WalletBalance | null>(null)
const [payoutList, setPayoutList] = useState<Payout[]>([])
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const [requesting, setRequesting] = useState(false)
const load = useCallback(async () => {
try {
const [b, p] = await Promise.all([
wallet.balance(),
payouts.history(),
])
setBalance(b)
setPayoutList(p)
} catch {
// silent
} finally {
setLoading(false)
setRefreshing(false)
}
}, [])
useEffect(() => { load() }, [])
const onRefresh = useCallback(() => { setRefreshing(true); load() }, [load])
const handleRequestPayout = () => {
if (!balance) return
const available = balance.balance - balance.pending_payouts
if (available < 10000) { // 100 SEK minimum
Alert.alert('Minsta utbetalning 100 kr', `Du har ${(available / 100).toFixed(0)} kr tillgängligt.`)
return
}
Alert.alert(
'Begär utbetalning',
`Vill du ta ut ${(available / 100).toFixed(0)} SEK till ditt bankkonto?`,
[
{ text: 'Avbryt', style: 'cancel' },
{
text: 'Ta ut',
onPress: async () => {
setRequesting(true)
try {
await payouts.request(available)
Alert.alert('✅ Utbetalning begärd!', 'Pengarna är hos dig inom 13 bankdagar.')
load()
} catch (e: any) {
Alert.alert('Fel', e?.message ?? 'Kunde inte begära utbetalning.')
} finally {
setRequesting(false)
}
},
},
],
)
}
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator color="#6366f1" size="large" />
</View>
)
}
return (
<ScrollView
style={styles.container}
contentContainerStyle={{ paddingBottom: 40 }}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#6366f1" />}
>
{/* Big balance card */}
<View style={styles.balanceCard}>
<Text style={styles.balanceLabel}>Tillgängligt</Text>
<Text style={styles.balanceAmount}>
{balance ? (balance.balance / 100).toFixed(0) : '0'} SEK
</Text>
{balance && balance.pending_payouts > 0 && (
<Text style={styles.pendingNote}>
+ {(balance.pending_payouts / 100).toFixed(0)} SEK under behandling
</Text>
)}
<TouchableOpacity
style={[styles.payoutBtn, requesting && styles.payoutBtnDisabled]}
onPress={handleRequestPayout}
disabled={requesting}
>
{requesting
? <ActivityIndicator color="#fff" size="small" />
: <Text style={styles.payoutBtnText}>💳 Begär utbetalning</Text>
}
</TouchableOpacity>
</View>
{/* Tab bar */}
<View style={styles.tabBar}>
{([['oversikt', 'Översikt'], ['utbetalningar', 'Utbetalningar']] as [TabKey, string][]).map(([key, label]) => (
<TouchableOpacity
key={key}
style={[styles.tab, tab === key && styles.tabActive]}
onPress={() => setTab(key)}
>
<Text style={[styles.tabText, tab === key && styles.tabTextActive]}>{label}</Text>
</TouchableOpacity>
))}
</View>
{/* Översikt */}
{tab === 'oversikt' && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Din ekonomi</Text>
<View style={styles.infoCard}>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>Valuta</Text>
<Text style={styles.infoValue}>{balance?.currency ?? 'SEK'}</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>Tillgängligt</Text>
<Text style={styles.infoValue}>{balance ? (balance.balance / 100).toFixed(0) : '0'} kr</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>Väntande utbetalningar</Text>
<Text style={styles.infoValue}>{balance ? (balance.pending_payouts / 100).toFixed(0) : '0'} kr</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>Totalt utbetalt</Text>
<Text style={styles.infoValue}>
{payoutList
.filter(p => p.status === 'paid')
.reduce((sum, p) => sum + p.amount, 0) / 100} kr
</Text>
</View>
</View>
</View>
)}
{/* Utbetalningar */}
{tab === 'utbetalningar' && (
<View style={styles.section}>
{payoutList.length === 0
? (
<View style={styles.emptyWrap}>
<Text style={styles.emptyEmoji}>💳</Text>
<Text style={styles.emptyTitle}>Inga utbetalningar ännu</Text>
<Text style={styles.empty}>Begär din första utbetalning ovan.</Text>
</View>
)
: payoutList.map(p => {
const s = PAYOUT_STATUS[p.status] ?? { label: p.status, color: '#fff' }
return (
<View key={p.id} style={styles.payoutRow}>
<View>
<Text style={styles.payoutDate}>
{new Date(p.created_at).toLocaleDateString('sv-SE', { month: 'short', day: 'numeric', year: 'numeric' })}
</Text>
{p.paid_at && (
<Text style={styles.payoutPaidAt}>
Betald {new Date(p.paid_at).toLocaleDateString('sv-SE')}
</Text>
)}
</View>
<Text style={styles.payoutAmount}>
{(p.amount / 100).toFixed(0)} {p.currency}
</Text>
<View style={[styles.payoutStatusBadge, { backgroundColor: s.color + '22' }]}>
<Text style={[styles.payoutStatusText, { color: s.color }]}>{s.label}</Text>
</View>
</View>
)
})
}
</View>
)}
</ScrollView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
center: { flex: 1, backgroundColor: '#0A0A1B', alignItems: 'center', justifyContent: 'center' },
balanceCard: {
margin: 16,
marginTop: 56,
backgroundColor: '#6366f1',
borderRadius: 24, padding: 28,
alignItems: 'center',
},
balanceLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 14, marginBottom: 6 },
balanceAmount: { color: '#fff', fontSize: 48, fontWeight: '900', letterSpacing: -2 },
pendingNote: { color: 'rgba(255,255,255,0.6)', fontSize: 13, marginTop: 4 },
payoutBtn: {
marginTop: 20,
backgroundColor: 'rgba(255,255,255,0.15)',
borderRadius: 14, paddingHorizontal: 28, paddingVertical: 12,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.25)',
},
payoutBtnDisabled: { opacity: 0.6 },
payoutBtnText: { color: '#fff', fontWeight: '700', fontSize: 15 },
tabBar: {
flexDirection: 'row', marginHorizontal: 16, marginTop: 16, marginBottom: 4,
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 14, padding: 4,
},
tab: { flex: 1, paddingVertical: 8, alignItems: 'center', borderRadius: 10 },
tabActive: { backgroundColor: '#6366f1' },
tabText: { color: 'rgba(255,255,255,0.4)', fontSize: 12, fontWeight: '600' },
tabTextActive: { color: '#fff' },
section: { paddingHorizontal: 16, paddingTop: 16 },
sectionTitle: { color: 'rgba(255,255,255,0.35)', fontSize: 12, fontWeight: '700', letterSpacing: 1, textTransform: 'uppercase', marginBottom: 16 },
infoCard: {
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 16, padding: 20,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
},
infoRow: {
flexDirection: 'row', justifyContent: 'space-between',
paddingVertical: 12,
borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.05)',
},
infoLabel: { color: 'rgba(255,255,255,0.5)', fontSize: 14 },
infoValue: { color: '#fff', fontSize: 14, fontWeight: '700' },
emptyWrap: { alignItems: 'center', paddingTop: 40 },
emptyEmoji: { fontSize: 40, marginBottom: 12 },
emptyTitle: { color: '#fff', fontSize: 16, fontWeight: '700', marginBottom: 6 },
empty: { color: 'rgba(255,255,255,0.3)', textAlign: 'center', fontSize: 14 },
payoutRow: {
flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between',
paddingVertical: 14,
borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.05)',
},
payoutDate: { color: '#fff', fontSize: 13, fontWeight: '500' },
payoutPaidAt: { color: 'rgba(255,255,255,0.3)', fontSize: 11 },
payoutAmount: { color: '#fff', fontWeight: '700', fontSize: 14 },
payoutStatusBadge: { borderRadius: 20, paddingHorizontal: 10, paddingVertical: 4 },
payoutStatusText: { fontSize: 12, fontWeight: '600' },
})
+332
View File
@@ -0,0 +1,332 @@
/**
* HomeScreen — karta med live-uppdrag + snabbstatistik (intjänat idag)
* Integrerad med useLiveLocation() för kontinuerlig GPS-tracking
*/
import { useCallback, useEffect, useRef, useState } from 'react'
import {
View, Text, StyleSheet, TouchableOpacity,
ActivityIndicator, Platform,
} from 'react-native'
import MapView, { Marker, Region, PROVIDER_DEFAULT } from 'react-native-maps'
import { useNavigation } from '@react-navigation/native'
import { missions, wallet, type Mission, type WalletBalance } from '../lib/api'
import { useLiveLocation } from '../src/hooks/useLiveLocation'
import { useHeading } from '../src/hooks/useHeading'
import { useLocationStore } from '../src/stores/locationStore'
import { UserLocationMarker } from '../src/components/gps/UserLocationMarker'
import { FollowUserButton } from '../src/components/gps/FollowUserButton'
import { GpsDiagnostics } from '../src/components/gps/GpsDiagnostics'
const STOCKHOLM_ARCHIPELAGO: Region = {
latitude: 59.45,
longitude: 18.5,
latitudeDelta: 0.8,
longitudeDelta: 0.8,
}
const CATEGORY_EMOJI: Record<string, string> = {
infrastruktur: '🏗️',
vägar: '🛤️',
hamnar: '⚓',
bryggor: '🚢',
fritidshus: '🏠',
miljö: '🌿',
övrigt: '📍',
}
export function HomeScreen() {
const navigation = useNavigation<any>()
const mapRef = useRef<MapView>(null)
// Initialize live location tracking
useLiveLocation()
useHeading()
const {
currentPosition,
followUser,
setUserPannedMap,
locationAuthStatus,
} = useLocationStore()
const [region, setRegion] = useState<Region>(STOCKHOLM_ARCHIPELAGO)
const [nearbyMissions, setNearbyMissions] = useState<Mission[]>([])
const [balance, setBalance] = useState<WalletBalance | null>(null)
const [loadingMissions, setLoadingMissions] = useState(false)
const [showDiagnostics, setShowDiagnostics] = useState(false)
const [initialLocationSet, setInitialLocationSet] = useState(false)
// Center map on first position fix
useEffect(() => {
if (currentPosition && !initialLocationSet) {
const newRegion: Region = {
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
latitudeDelta: 0.5,
longitudeDelta: 0.5,
}
setRegion(newRegion)
mapRef.current?.animateToRegion(newRegion, 800)
fetchNearby(currentPosition.coords.latitude, currentPosition.coords.longitude)
setInitialLocationSet(true)
}
}, [currentPosition, initialLocationSet])
// Auto-follow user when followUser is true
useEffect(() => {
if (followUser && currentPosition && mapRef.current) {
const newRegion: Region = {
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
latitudeDelta: region.latitudeDelta,
longitudeDelta: region.longitudeDelta,
}
mapRef.current.animateToRegion(newRegion, 300)
}
}, [currentPosition, followUser, region.latitudeDelta, region.longitudeDelta])
// Wallet balance
useEffect(() => {
wallet.balance().then(setBalance).catch(() => {})
}, [])
const fetchNearby = useCallback(async (lat: number, lng: number) => {
setLoadingMissions(true)
try {
const data = await missions.nearby(lat, lng, 60)
setNearbyMissions(data)
} catch {
// silent
} finally {
setLoadingMissions(false)
}
}, [])
const onRegionChangeComplete = useCallback((r: Region) => {
setRegion(r)
fetchNearby(r.latitude, r.longitude)
}, [fetchNearby])
const onPanDrag = useCallback(() => {
setUserPannedMap(true)
}, [setUserPannedMap])
const availableSEK = balance ? ((balance.balance - balance.pending_payouts) / 100).toFixed(0) : '—'
const pendingSEK = balance ? (balance.pending_payouts / 100).toFixed(0) : '—'
return (
<View style={styles.container}>
{/* Map */}
<MapView
ref={mapRef}
style={styles.map}
provider={PROVIDER_DEFAULT}
initialRegion={STOCKHOLM_ARCHIPELAGO}
region={region}
onRegionChangeComplete={onRegionChangeComplete}
onPanDrag={onPanDrag}
showsUserLocation={false} // We use custom UserLocationMarker
showsMyLocationButton={false}
rotateEnabled={false} // Map stays north-up, marker rotates
>
{/* Custom user location marker with heading */}
{currentPosition && (
<Marker
coordinate={{
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
}}
anchor={{ x: 0.5, y: 0.5 }}
flat={true}
>
<UserLocationMarker />
</Marker>
)}
{nearbyMissions.map(m => (
<Marker
key={m.id}
coordinate={{ latitude: m.latitude, longitude: m.longitude }}
title={m.title}
description={`${(m.reward_amount / 100).toFixed(0)} SEK`}
onCalloutPress={() => navigation.navigate('MissionDetail', { missionId: m.id })}
>
<View style={styles.markerBubble}>
<Text style={styles.markerEmoji}>
{CATEGORY_EMOJI[m.category] ?? '📍'}
</Text>
<Text style={styles.markerReward}>
{(m.reward_amount / 100).toFixed(0)} kr
</Text>
</View>
</Marker>
))}
</MapView>
{/* Loading indicator */}
{loadingMissions && (
<View style={styles.loadingOverlay}>
<ActivityIndicator color="#6366f1" size="small" />
</View>
)}
{/* GPS Diagnostics toggle */}
<TouchableOpacity
style={styles.diagnosticsToggle}
onPress={() => setShowDiagnostics(prev => !prev)}
>
<Text style={styles.diagnosticsToggleText}>
{showDiagnostics ? '🔧' : '🛠️'}
</Text>
</TouchableOpacity>
{/* GPS Diagnostics overlay */}
{showDiagnostics && <GpsDiagnostics />}
{/* Stats card */}
<View style={styles.statsCard}>
<View style={styles.statItem}>
<Text style={styles.statLabel}>Tillgängligt</Text>
<Text style={styles.statValue}>{availableSEK} kr</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statLabel}>Under behandling</Text>
<Text style={[styles.statValue, { color: '#f59e0b' }]}>{pendingSEK} kr</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statLabel}>Uppdrag nära</Text>
<Text style={[styles.statValue, { color: '#10b981' }]}>{nearbyMissions.length}</Text>
</View>
</View>
{/* Follow user button */}
<View style={styles.followBtnContainer}>
<FollowUserButton />
</View>
{/* Location status indicator */}
{locationAuthStatus !== 'granted' && locationAuthStatus !== null && (
<View style={styles.locationWarning}>
<Text style={styles.locationWarningText}>
GPS-behörighet: {locationAuthStatus}
</Text>
</View>
)}
{/* Missions list button */}
<TouchableOpacity
style={styles.missionsBtn}
onPress={() => navigation.navigate('Uppdrag')}
>
<Text style={styles.missionsBtnText}>
🎯 {nearbyMissions.length} uppdrag nära dig
</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
map: { flex: 1 },
markerBubble: {
backgroundColor: '#6366f1',
borderRadius: 20,
paddingHorizontal: 10,
paddingVertical: 6,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.4,
shadowRadius: 4,
elevation: 4,
},
markerEmoji: { fontSize: 14 },
markerReward: { color: '#fff', fontSize: 11, fontWeight: '700' },
loadingOverlay: {
position: 'absolute',
top: Platform.OS === 'ios' ? 60 : 16,
right: 16,
backgroundColor: 'rgba(0,0,0,0.7)',
borderRadius: 20,
padding: 8,
},
diagnosticsToggle: {
position: 'absolute',
top: Platform.OS === 'ios' ? 60 : 16,
right: 60,
width: 36,
height: 36,
backgroundColor: 'rgba(10,10,27,0.9)',
borderRadius: 18,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.15)',
alignItems: 'center',
justifyContent: 'center',
},
diagnosticsToggleText: { fontSize: 16 },
statsCard: {
position: 'absolute',
top: Platform.OS === 'ios' ? 60 : 16,
left: 16,
right: 104,
flexDirection: 'row',
backgroundColor: 'rgba(10,10,27,0.92)',
borderRadius: 16,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
padding: 12,
alignItems: 'center',
},
statItem: { flex: 1, alignItems: 'center' },
statLabel: { color: 'rgba(255,255,255,0.45)', fontSize: 10, marginBottom: 2 },
statValue: { color: '#fff', fontSize: 15, fontWeight: '800' },
statDivider: { width: 1, height: 28, backgroundColor: 'rgba(255,255,255,0.1)' },
followBtnContainer: {
position: 'absolute',
bottom: 100,
right: 16,
},
locationWarning: {
position: 'absolute',
top: Platform.OS === 'ios' ? 120 : 76,
left: 16,
right: 16,
backgroundColor: 'rgba(245,158,11,0.2)',
borderRadius: 12,
padding: 10,
borderWidth: 1,
borderColor: 'rgba(245,158,11,0.4)',
alignItems: 'center',
},
locationWarningText: {
color: '#f59e0b',
fontSize: 12,
fontWeight: '600',
},
missionsBtn: {
position: 'absolute',
bottom: 32,
left: 16,
right: 16,
backgroundColor: '#6366f1',
borderRadius: 16,
paddingVertical: 16,
alignItems: 'center',
shadowColor: '#6366f1',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.4,
shadowRadius: 12,
elevation: 8,
},
missionsBtnText: { color: '#fff', fontWeight: '700', fontSize: 16 },
})
@@ -0,0 +1,593 @@
/**
* MissionDetailScreen — uppdragsinfo, karta, acceptera-knapp, kamera-upload vid leverans
* Live markör, distance overlay, heading indicator
*/
import { useEffect, useState, useRef, useCallback } from 'react'
import {
View, Text, ScrollView, TouchableOpacity,
StyleSheet, ActivityIndicator, Alert,
Image, Platform,
} from 'react-native'
import MapView, { Marker, Region, PROVIDER_DEFAULT } from 'react-native-maps'
import * as ImagePicker from 'expo-image-picker'
import { useNavigation, useRoute, type RouteProp } from '@react-navigation/native'
import { missions, type MissionDetail } from '../lib/api'
import { useLocationStore } from '../src/stores/locationStore'
import { UserLocationMarker } from '../src/components/gps/UserLocationMarker'
import { FollowUserButton } from '../src/components/gps/FollowUserButton'
type RouteParams = { MissionDetail: { missionId: string } }
const CATEGORY_EMOJI: Record<string, string> = {
infrastruktur: '🏗️',
vägar: '🛤️',
hamnar: '⚓',
bryggor: '🚢',
fritidshus: '🏠',
miljö: '🌿',
övrigt: '📍',
}
/** Calculate distance between two coordinates in meters */
function haversineDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
const R = 6371e3 // Earth radius in meters
const φ1 = (lat1 * Math.PI) / 180
const φ2 = (lat2 * Math.PI) / 180
const Δφ = ((lat2 - lat1) * Math.PI) / 180
const Δλ = ((lng2 - lng1) * Math.PI) / 180
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return R * c
}
function formatDistance(meters: number): string {
if (meters < 1000) {
return `${Math.round(meters)} m`
}
return `${(meters / 1000).toFixed(1)} km`
}
/** Calculate bearing from point 1 to point 2 in degrees */
function calculateBearing(lat1: number, lng1: number, lat2: number, lng2: number): number {
const φ1 = (lat1 * Math.PI) / 180
const φ2 = (lat2 * Math.PI) / 180
const Δλ = ((lng2 - lng1) * Math.PI) / 180
const y = Math.sin(Δλ) * Math.cos(φ2)
const x =
Math.cos(φ1) * Math.sin(φ2) -
Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ)
const θ = Math.atan2(y, x)
return ((θ * 180) / Math.PI + 360) % 360
}
function getDirectionArrow(degrees: number): string {
if (degrees >= 337.5 || degrees < 22.5) return '↑'
if (degrees >= 22.5 && degrees < 67.5) return '↗'
if (degrees >= 67.5 && degrees < 112.5) return '→'
if (degrees >= 112.5 && degrees < 157.5) return '↘'
if (degrees >= 157.5 && degrees < 202.5) return '↓'
if (degrees >= 202.5 && degrees < 247.5) return '↙'
if (degrees >= 247.5 && degrees < 292.5) return '←'
return '↖'
}
export function MissionDetailScreen() {
const navigation = useNavigation<any>()
const route = useRoute<RouteProp<RouteParams, 'MissionDetail'>>()
const { missionId } = route.params
const mapRef = useRef<MapView>(null)
// Live location from store
const {
currentPosition,
heading,
followUser,
setUserPannedMap,
} = useLocationStore()
const [mission, setMission] = useState<MissionDetail | null>(null)
const [loading, setLoading] = useState(true)
const [accepting, setAccepting] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [accepted, setAccepted] = useState(false)
const [mediaUris, setMediaUris] = useState<string[]>([])
const [distance, setDistance] = useState<number | null>(null)
const [bearing, setBearing] = useState<number | null>(null)
useEffect(() => {
missions.get(missionId)
.then(m => { setMission(m); setLoading(false) })
.catch(() => { Alert.alert('Fel', 'Kunde inte hämta uppdraget.'); navigation.goBack() })
}, [missionId])
// Update distance and bearing when position or mission changes
useEffect(() => {
if (currentPosition && mission) {
const dist = haversineDistance(
currentPosition.coords.latitude,
currentPosition.coords.longitude,
mission.latitude,
mission.longitude
)
setDistance(dist)
const bear = calculateBearing(
currentPosition.coords.latitude,
currentPosition.coords.longitude,
mission.latitude,
mission.longitude
)
setBearing(bear)
}
}, [currentPosition, mission])
// Auto-follow user when followUser is true
useEffect(() => {
if (followUser && currentPosition && mapRef.current) {
const region: Region = {
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
}
mapRef.current.animateToRegion(region, 300)
}
}, [currentPosition, followUser])
const onPanDrag = useCallback(() => {
setUserPannedMap(true)
}, [setUserPannedMap])
const handleAccept = async () => {
if (!mission) return
setAccepting(true)
try {
await missions.accept(mission.id)
setAccepted(true)
Alert.alert('✅ Uppdraget accepterat!', 'Ta dig till platsen och leverera när du är framme.')
} catch (e: any) {
Alert.alert('Fel', e?.message ?? 'Kunde inte acceptera uppdraget.')
} finally {
setAccepting(false)
}
}
const handlePickMedia = async () => {
const { status } = await ImagePicker.requestCameraPermissionsAsync()
if (status !== 'granted') {
Alert.alert('Kamera-åtkomst krävs', 'Tillåt kameraåtkomst i telefonens inställningar.')
return
}
Alert.alert('Välj källa', '', [
{
text: 'Ta foto',
onPress: async () => {
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 0.85,
allowsEditing: false,
})
if (!result.canceled && result.assets[0]) {
setMediaUris(prev => [...prev, result.assets[0].uri])
}
},
},
{
text: 'Välj från bibliotek',
onPress: async () => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 0.85,
allowsMultipleSelection: true,
selectionLimit: 10,
})
if (!result.canceled) {
setMediaUris(prev => [...prev, ...result.assets.map(a => a.uri)])
}
},
},
{ text: 'Avbryt', style: 'cancel' },
])
}
const handleSubmit = async () => {
if (!mission) return
if (mediaUris.length === 0) {
Alert.alert('Inga bilder', 'Lägg till minst en bild innan du skickar in.')
return
}
Alert.alert(
'Skicka in uppdraget?',
`${mediaUris.length} bild${mediaUris.length > 1 ? 'er' : ''} kommer att skickas.`,
[
{ text: 'Avbryt', style: 'cancel' },
{
text: 'Skicka in',
onPress: async () => {
setSubmitting(true)
try {
await missions.submit(mission.id, mediaUris)
Alert.alert(
'🎉 Inlämnat!',
`Dina bilder granskas. Om allt godkänns får du ${(mission.reward_amount / 100).toFixed(0)} kr.`,
[{ text: 'Tillbaka', onPress: () => navigation.goBack() }],
)
} catch (e: any) {
Alert.alert('Fel', e?.message ?? 'Kunde inte skicka in uppdraget.')
} finally {
setSubmitting(false)
}
},
},
],
)
}
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator color="#6366f1" size="large" />
</View>
)
}
if (!mission) return null
const isExpired = mission.deadline_at ? new Date(mission.deadline_at) < new Date() : false
const spotsLeft = mission.max_submissions - mission.submission_count
// Calculate relative bearing for navigation arrow
const relativeBearing = bearing != null && heading != null
? ((bearing - heading + 360) % 360)
: null
return (
<View style={styles.container}>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 120 }}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity style={styles.backBtn} onPress={() => navigation.goBack()}>
<Text style={styles.backBtnText}> Tillbaka</Text>
</TouchableOpacity>
</View>
{/* Map with live tracking */}
<View style={styles.mapContainer}>
<MapView
ref={mapRef}
style={styles.map}
provider={PROVIDER_DEFAULT}
region={{
latitude: mission.latitude,
longitude: mission.longitude,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
}}
onPanDrag={onPanDrag}
showsUserLocation={false} // Custom marker
rotateEnabled={false}
>
{/* Mission marker */}
<Marker
coordinate={{ latitude: mission.latitude, longitude: mission.longitude }}
pinColor="#10b981"
title={mission.title}
/>
{/* Live user location marker */}
{currentPosition && (
<Marker
coordinate={{
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
}}
anchor={{ x: 0.5, y: 0.5 }}
flat={true}
>
<UserLocationMarker />
</Marker>
)}
</MapView>
{/* Follow user button */}
<View style={styles.followBtnContainer}>
<FollowUserButton />
</View>
{/* Distance overlay */}
{distance != null && (
<View style={styles.distanceOverlay}>
<View style={styles.distanceRow}>
<Text style={styles.distanceLabel}>Avstånd till uppdrag</Text>
<Text style={styles.distanceValue}>{formatDistance(distance)}</Text>
</View>
{relativeBearing != null && (
<View style={styles.distanceRow}>
<Text style={styles.distanceLabel}>Riktning</Text>
<Text style={styles.distanceValue}>
{getDirectionArrow(relativeBearing)} {relativeBearing.toFixed(0)}°
</Text>
</View>
)}
{heading != null && (
<View style={styles.distanceRow}>
<Text style={styles.distanceLabel}>Din kurs</Text>
<Text style={styles.distanceValue}>{heading.toFixed(0)}°</Text>
</View>
)}
{currentPosition?.coords.speed != null && (
<View style={styles.distanceRow}>
<Text style={styles.distanceLabel}>Hastighet</Text>
<Text style={styles.distanceValue}>
{(currentPosition.coords.speed * 3.6).toFixed(1)} km/h
</Text>
</View>
)}
</View>
)}
</View>
{/* Content */}
<View style={styles.content}>
{/* Category + status row */}
<View style={styles.metaRow}>
<View style={styles.categoryBadge}>
<Text style={styles.categoryEmoji}>{CATEGORY_EMOJI[mission.category] ?? '📍'}</Text>
<Text style={styles.categoryText}>{mission.category}</Text>
</View>
{isExpired && <View style={styles.expiredBadge}><Text style={styles.expiredText}>Utgången</Text></View>}
{spotsLeft <= 3 && !isExpired && (
<View style={styles.urgentBadge}>
<Text style={styles.urgentText}>Bara {spotsLeft} platser kvar</Text>
</View>
)}
</View>
{/* Title */}
<Text style={styles.title}>{mission.title}</Text>
{/* Reward + deadline */}
<View style={styles.rewardRow}>
<View style={styles.rewardBox}>
<Text style={styles.rewardLabel}>Ersättning</Text>
<Text style={styles.rewardValue}>{(mission.reward_amount / 100).toFixed(0)} SEK</Text>
</View>
{mission.deadline_at && (
<View style={styles.rewardBox}>
<Text style={styles.rewardLabel}>Deadline</Text>
<Text style={styles.rewardValue}>
{new Date(mission.deadline_at).toLocaleDateString('sv-SE', {
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
})}
</Text>
</View>
)}
<View style={styles.rewardBox}>
<Text style={styles.rewardLabel}>Plats</Text>
<Text style={styles.rewardValue}>{mission.city || 'Skärgården'}</Text>
</View>
</View>
{/* Description */}
<Text style={styles.sectionTitle}>Beskrivning</Text>
<Text style={styles.desc}>{mission.description}</Text>
{/* Instructions */}
{mission.instructions && (
<>
<Text style={styles.sectionTitle}>Instruktioner</Text>
<Text style={styles.desc}>{mission.instructions}</Text>
</>
)}
{/* Requirements */}
{mission.requirements?.length > 0 && (
<>
<Text style={styles.sectionTitle}>Krav</Text>
{mission.requirements.map((r, i) => (
<View key={i} style={styles.reqRow}>
<Text style={styles.reqBullet}></Text>
<Text style={styles.reqText}>{r}</Text>
</View>
))}
</>
)}
{/* Media picker (only when accepted) */}
{accepted && (
<>
<Text style={styles.sectionTitle}>Dina bilder</Text>
<View style={styles.mediaGrid}>
{mediaUris.map((uri, i) => (
<View key={i} style={styles.mediaThumbWrap}>
<Image source={{ uri }} style={styles.mediaThumb} />
<TouchableOpacity
style={styles.removeBtn}
onPress={() => setMediaUris(prev => prev.filter((_, j) => j !== i))}
>
<Text style={styles.removeBtnText}></Text>
</TouchableOpacity>
</View>
))}
<TouchableOpacity style={styles.addMediaBtn} onPress={handlePickMedia}>
<Text style={styles.addMediaIcon}>📸</Text>
<Text style={styles.addMediaText}>Lägg till bild</Text>
</TouchableOpacity>
</View>
</>
)}
</View>
</ScrollView>
{/* Bottom action bar */}
{!isExpired && (
<View style={styles.bottomBar}>
{!accepted ? (
<TouchableOpacity
style={[styles.actionBtn, accepting && styles.actionBtnDisabled]}
onPress={handleAccept}
disabled={accepting}
>
{accepting
? <ActivityIndicator color="#fff" />
: <Text style={styles.actionBtnText}>🎯 Acceptera uppdraget</Text>
}
</TouchableOpacity>
) : (
<TouchableOpacity
style={[styles.actionBtn, styles.submitBtn, (submitting || mediaUris.length === 0) && styles.actionBtnDisabled]}
onPress={handleSubmit}
disabled={submitting || mediaUris.length === 0}
>
{submitting
? <ActivityIndicator color="#fff" />
: <Text style={styles.actionBtnText}>
📤 Skicka in ({mediaUris.length} bild{mediaUris.length !== 1 ? 'er' : ''})
</Text>
}
</TouchableOpacity>
)}
</View>
)}
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
center: { flex: 1, backgroundColor: '#0A0A1B', alignItems: 'center', justifyContent: 'center' },
header: {
paddingTop: Platform.OS === 'ios' ? 56 : 16,
paddingHorizontal: 16,
paddingBottom: 8,
},
backBtn: { paddingVertical: 4 },
backBtnText: { color: '#6366f1', fontSize: 15, fontWeight: '600' },
mapContainer: {
height: 280,
position: 'relative',
},
map: {
flex: 1,
},
followBtnContainer: {
position: 'absolute',
bottom: 16,
right: 16,
},
distanceOverlay: {
position: 'absolute',
top: 16,
left: 16,
backgroundColor: 'rgba(10,10,27,0.85)',
borderRadius: 12,
padding: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
minWidth: 160,
},
distanceRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
distanceLabel: {
color: 'rgba(255,255,255,0.45)',
fontSize: 11,
marginRight: 12,
},
distanceValue: {
color: '#fff',
fontSize: 13,
fontWeight: '700',
},
content: { padding: 20 },
metaRow: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 12 },
categoryBadge: {
flexDirection: 'row', alignItems: 'center', gap: 6,
backgroundColor: 'rgba(99,102,241,0.15)',
borderRadius: 20, paddingHorizontal: 12, paddingVertical: 6,
},
categoryEmoji: { fontSize: 14 },
categoryText: { color: '#a5b4fc', fontSize: 12, fontWeight: '600', textTransform: 'capitalize' },
expiredBadge: {
backgroundColor: 'rgba(239,68,68,0.15)',
borderRadius: 20, paddingHorizontal: 12, paddingVertical: 6,
},
expiredText: { color: '#ef4444', fontSize: 12, fontWeight: '600' },
urgentBadge: {
backgroundColor: 'rgba(245,158,11,0.15)',
borderRadius: 20, paddingHorizontal: 12, paddingVertical: 6,
},
urgentText: { color: '#f59e0b', fontSize: 12, fontWeight: '600' },
title: { color: '#fff', fontSize: 24, fontWeight: '800', lineHeight: 30, marginBottom: 20, letterSpacing: -0.5 },
rewardRow: { flexDirection: 'row', gap: 12, marginBottom: 24 },
rewardBox: {
flex: 1,
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 14, padding: 14,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
},
rewardLabel: { color: 'rgba(255,255,255,0.4)', fontSize: 11, marginBottom: 4 },
rewardValue: { color: '#fff', fontSize: 15, fontWeight: '700' },
sectionTitle: { color: 'rgba(255,255,255,0.5)', fontSize: 12, fontWeight: '700', letterSpacing: 1, textTransform: 'uppercase', marginBottom: 8, marginTop: 4 },
desc: { color: 'rgba(255,255,255,0.7)', fontSize: 15, lineHeight: 24, marginBottom: 20 },
reqRow: { flexDirection: 'row', gap: 10, marginBottom: 8 },
reqBullet: { color: '#10b981', fontWeight: '700', fontSize: 14 },
reqText: { color: 'rgba(255,255,255,0.65)', fontSize: 14, lineHeight: 20, flex: 1 },
mediaGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 8 },
mediaThumbWrap: { position: 'relative' },
mediaThumb: { width: 90, height: 90, borderRadius: 12 },
removeBtn: {
position: 'absolute', top: -6, right: -6,
width: 22, height: 22, borderRadius: 11,
backgroundColor: '#ef4444', alignItems: 'center', justifyContent: 'center',
},
removeBtnText: { color: '#fff', fontSize: 10, fontWeight: '800' },
addMediaBtn: {
width: 90, height: 90, borderRadius: 12,
backgroundColor: 'rgba(255,255,255,0.04)',
borderWidth: 2, borderColor: 'rgba(255,255,255,0.12)',
borderStyle: 'dashed',
alignItems: 'center', justifyContent: 'center', gap: 4,
},
addMediaIcon: { fontSize: 24 },
addMediaText: { color: 'rgba(255,255,255,0.4)', fontSize: 11 },
bottomBar: {
position: 'absolute', bottom: 0, left: 0, right: 0,
padding: 16,
paddingBottom: Platform.OS === 'ios' ? 32 : 16,
backgroundColor: 'rgba(10,10,27,0.95)',
borderTopWidth: 1, borderTopColor: 'rgba(255,255,255,0.08)',
},
actionBtn: {
backgroundColor: '#6366f1',
borderRadius: 16, paddingVertical: 16,
alignItems: 'center', justifyContent: 'center',
},
submitBtn: { backgroundColor: '#10b981' },
actionBtnDisabled: { opacity: 0.5 },
actionBtnText: { color: '#fff', fontWeight: '700', fontSize: 16 },
})
+338
View File
@@ -0,0 +1,338 @@
/**
* MissionsScreen — lista aktiva uppdrag nära mig, filter, sortering
* Läser position från locationStore för live-avståndsuppdateringar
*/
import { useCallback, useEffect, useState } from 'react'
import {
View, Text, FlatList, TouchableOpacity,
StyleSheet, RefreshControl, ActivityIndicator,
ScrollView,
} from 'react-native'
import { useNavigation } from '@react-navigation/native'
import { missions, type Mission, type MissionCategory } from '../lib/api'
import { useLocationStore } from '../src/stores/locationStore'
type SortKey = 'distance' | 'reward' | 'deadline'
const CATEGORIES: { key: MissionCategory | 'alla'; label: string; emoji: string }[] = [
{ key: 'alla', label: 'Alla', emoji: '🗺️' },
{ key: 'hamnar', label: 'Hamnar', emoji: '⚓' },
{ key: 'bryggor', label: 'Bryggor', emoji: '🚢' },
{ key: 'vägar', label: 'Vägar', emoji: '🛤️' },
{ key: 'infrastruktur', label: 'Infrastruktur', emoji: '🏗️' },
{ key: 'fritidshus', label: 'Fritidshus', emoji: '🏠' },
{ key: 'miljö', label: 'Miljö', emoji: '🌿' },
]
const STATUS_COLOR: Record<string, string> = {
open: '#10b981',
active: '#f59e0b',
completed: '#6366f1',
cancelled: '#ef4444',
expired: '#6b7280',
}
function timeLeft(deadline: string | null): string {
if (!deadline) return ''
const diff = new Date(deadline).getTime() - Date.now()
if (diff < 0) return 'Utgången'
const h = Math.floor(diff / 3600000)
if (h < 24) return `${h}h kvar`
return `${Math.floor(h / 24)}d kvar`
}
/** Calculate distance between two coordinates in meters */
function haversineDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
const R = 6371e3 // Earth radius in meters
const φ1 = (lat1 * Math.PI) / 180
const φ2 = (lat2 * Math.PI) / 180
const Δφ = ((lat2 - lat1) * Math.PI) / 180
const Δλ = ((lng2 - lng1) * Math.PI) / 180
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return R * c
}
function formatDistance(meters: number): string {
if (meters < 1000) {
return `${Math.round(meters)} m`
}
return `${(meters / 1000).toFixed(1)} km`
}
export function MissionsScreen() {
const navigation = useNavigation<any>()
// Read live position from store
const { currentPosition } = useLocationStore()
const [allMissions, setAllMissions] = useState<Mission[]>([])
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const [category, setCategory] = useState<MissionCategory | 'alla'>('alla')
const [sort, setSort] = useState<SortKey>('distance')
const [userLat, setUserLat] = useState<number | null>(null)
const [userLng, setUserLng] = useState<number | null>(null)
// Initialize with store position or fallback
useEffect(() => {
if (currentPosition) {
setUserLat(currentPosition.coords.latitude)
setUserLng(currentPosition.coords.longitude)
loadMissions(currentPosition.coords.latitude, currentPosition.coords.longitude)
} else {
// Fallback to Stockholm archipelago if no GPS yet
setUserLat(59.45)
setUserLng(18.5)
loadMissions(59.45, 18.5)
}
}, []) // Only on mount — subsequent updates come from currentPosition
// Update user position when store updates
useEffect(() => {
if (currentPosition) {
setUserLat(currentPosition.coords.latitude)
setUserLng(currentPosition.coords.longitude)
}
}, [currentPosition])
const loadMissions = async (lat: number, lng: number) => {
try {
const data = await missions.nearby(lat, lng, 80)
setAllMissions(data)
} catch {
// silent — show empty
} finally {
setLoading(false)
setRefreshing(false)
}
}
const onRefresh = useCallback(() => {
setRefreshing(true)
const lat = userLat ?? 59.45
const lng = userLng ?? 18.5
loadMissions(lat, lng)
}, [userLat, userLng])
// Compute live distances using current position
const missionsWithLiveDistance = allMissions.map(m => {
if (userLat != null && userLng != null) {
const liveDistance = haversineDistance(userLat, userLng, m.latitude, m.longitude)
return { ...m, distance_meters: liveDistance }
}
return m
})
const filtered = missionsWithLiveDistance
.filter(m => category === 'alla' || m.category === category)
.sort((a, b) => {
if (sort === 'reward') return b.reward_amount - a.reward_amount
if (sort === 'deadline') {
if (!a.deadline_at) return 1
if (!b.deadline_at) return -1
return new Date(a.deadline_at).getTime() - new Date(b.deadline_at).getTime()
}
// distance — use live-calculated distance
return (a.distance_meters ?? Infinity) - (b.distance_meters ?? Infinity)
})
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator color="#6366f1" size="large" />
<Text style={styles.loadingText}>Hämtar uppdrag nära dig</Text>
</View>
)
}
return (
<View style={styles.container}>
<Text style={styles.heading}>Uppdrag</Text>
{/* Live position indicator */}
{currentPosition && (
<View style={styles.locationBar}>
<Text style={styles.locationText}>
📍 {currentPosition.coords.latitude.toFixed(4)}, {currentPosition.coords.longitude.toFixed(4)}
{currentPosition.coords.accuracy && `${Math.round(currentPosition.coords.accuracy)}m)`}
</Text>
</View>
)}
{/* Category filter */}
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filterRow}
>
{CATEGORIES.map(c => (
<TouchableOpacity
key={c.key}
style={[styles.chip, category === c.key && styles.chipActive]}
onPress={() => setCategory(c.key)}
>
<Text style={styles.chipEmoji}>{c.emoji}</Text>
<Text style={[styles.chipText, category === c.key && styles.chipTextActive]}>
{c.label}
</Text>
</TouchableOpacity>
))}
</ScrollView>
{/* Sort bar */}
<View style={styles.sortBar}>
<Text style={styles.sortLabel}>Sortera:</Text>
{(['distance', 'reward', 'deadline'] as SortKey[]).map(s => (
<TouchableOpacity
key={s}
style={[styles.sortBtn, sort === s && styles.sortBtnActive]}
onPress={() => setSort(s)}
>
<Text style={[styles.sortBtnText, sort === s && styles.sortBtnTextActive]}>
{s === 'distance' ? 'Avstånd' : s === 'reward' ? 'Ersättning' : 'Deadline'}
</Text>
</TouchableOpacity>
))}
</View>
{/* Count */}
<Text style={styles.count}>
{filtered.length} uppdrag {category !== 'alla' ? `i "${category}"` : 'totalt'}
</Text>
<FlatList
data={filtered}
keyExtractor={m => m.id}
contentContainerStyle={{ paddingBottom: 32 }}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#6366f1" />
}
renderItem={({ item: m }) => (
<TouchableOpacity
style={styles.card}
onPress={() => navigation.navigate('MissionDetail', { missionId: m.id })}
activeOpacity={0.75}
>
<View style={styles.cardTop}>
<View style={styles.cardMeta}>
<View style={[styles.statusDot, { backgroundColor: STATUS_COLOR[m.status] ?? '#6b7280' }]} />
<Text style={styles.category}>{m.category}</Text>
</View>
<Text style={styles.reward}>
{(m.reward_amount / 100).toFixed(0)} SEK
</Text>
</View>
<Text style={styles.title} numberOfLines={2}>{m.title}</Text>
<Text style={styles.desc} numberOfLines={2}>{m.description}</Text>
<View style={styles.cardFooter}>
<Text style={styles.location}>
📍 {m.city || 'Stockholms skärgård'}
{m.distance_meters != null
? ` · ${formatDistance(m.distance_meters)}`
: ''}
</Text>
{m.deadline_at && (
<Text style={[
styles.deadline,
timeLeft(m.deadline_at) === 'Utgången' && { color: '#ef4444' },
]}>
{timeLeft(m.deadline_at)}
</Text>
)}
</View>
</TouchableOpacity>
)}
ListEmptyComponent={
<View style={styles.emptyWrap}>
<Text style={styles.emptyEmoji}>🎯</Text>
<Text style={styles.emptyTitle}>Inga uppdrag just nu</Text>
<Text style={styles.emptyText}>Prova en annan kategori eller dra för att uppdatera.</Text>
</View>
}
/>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B', paddingTop: 56 },
center: { flex: 1, backgroundColor: '#0A0A1B', alignItems: 'center', justifyContent: 'center', gap: 12 },
loadingText: { color: 'rgba(255,255,255,0.4)', fontSize: 14 },
heading: {
color: '#fff', fontSize: 28, fontWeight: '800',
marginHorizontal: 16, marginBottom: 8, letterSpacing: -0.5,
},
locationBar: {
marginHorizontal: 16,
marginBottom: 8,
backgroundColor: 'rgba(99,102,241,0.1)',
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 6,
borderWidth: 1,
borderColor: 'rgba(99,102,241,0.2)',
},
locationText: {
color: '#a5b4fc',
fontSize: 11,
fontWeight: '500',
},
filterRow: { paddingHorizontal: 16, gap: 8, paddingBottom: 4 },
chip: {
flexDirection: 'row', alignItems: 'center', gap: 6,
backgroundColor: 'rgba(255,255,255,0.05)',
borderWidth: 1, borderColor: 'rgba(255,255,255,0.08)',
borderRadius: 20, paddingHorizontal: 14, paddingVertical: 8,
},
chipActive: { backgroundColor: 'rgba(99,102,241,0.2)', borderColor: '#6366f1' },
chipEmoji: { fontSize: 14 },
chipText: { color: 'rgba(255,255,255,0.5)', fontSize: 13, fontWeight: '500' },
chipTextActive: { color: '#a5b4fc', fontWeight: '700' },
sortBar: {
flexDirection: 'row', alignItems: 'center', gap: 8,
paddingHorizontal: 16, marginTop: 12, marginBottom: 4,
},
sortLabel: { color: 'rgba(255,255,255,0.3)', fontSize: 12, marginRight: 4 },
sortBtn: {
paddingHorizontal: 10, paddingVertical: 5,
borderRadius: 8, backgroundColor: 'rgba(255,255,255,0.04)',
},
sortBtnActive: { backgroundColor: 'rgba(99,102,241,0.15)' },
sortBtnText: { color: 'rgba(255,255,255,0.4)', fontSize: 12 },
sortBtnTextActive: { color: '#a5b4fc', fontWeight: '600' },
count: { color: 'rgba(255,255,255,0.25)', fontSize: 12, marginHorizontal: 16, marginBottom: 12 },
card: {
marginHorizontal: 16, marginBottom: 12,
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 18, padding: 16,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
},
cardTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 },
cardMeta: { flexDirection: 'row', alignItems: 'center', gap: 6 },
statusDot: { width: 7, height: 7, borderRadius: 4 },
category: { color: 'rgba(255,255,255,0.35)', fontSize: 12, textTransform: 'capitalize' },
reward: { color: '#10b981', fontWeight: '800', fontSize: 16 },
title: { color: '#fff', fontWeight: '700', fontSize: 16, marginBottom: 6, lineHeight: 22 },
desc: { color: 'rgba(255,255,255,0.45)', fontSize: 14, lineHeight: 20, marginBottom: 12 },
cardFooter: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
location: { color: 'rgba(255,255,255,0.3)', fontSize: 12 },
deadline: { color: '#f59e0b', fontSize: 12, fontWeight: '600' },
emptyWrap: { alignItems: 'center', marginTop: 60, paddingHorizontal: 32 },
emptyEmoji: { fontSize: 48, marginBottom: 16 },
emptyTitle: { color: '#fff', fontSize: 18, fontWeight: '700', marginBottom: 8 },
emptyText: { color: 'rgba(255,255,255,0.35)', fontSize: 14, textAlign: 'center', lineHeight: 20 },
})
+269
View File
@@ -0,0 +1,269 @@
/**
* ProfileScreen — zoomer-profil, stats, badges
*/
import { useCallback, useEffect, useState } from 'react'
import {
View, Text, ScrollView, TouchableOpacity,
StyleSheet, ActivityIndicator, Alert, RefreshControl,
} from 'react-native'
import { auth, profile, type Zoomer } from '../lib/api'
const LEVEL_LABELS: Record<number, string> = {
1: 'Rookie Zoomer',
2: 'Junior Zoomer',
3: 'Zoomer',
4: 'Senior Zoomer',
5: 'Elite Zoomer',
6: 'Legend Zoomer',
}
const LEVEL_COLOR: Record<number, string> = {
1: '#6b7280',
2: '#10b981',
3: '#3b82f6',
4: '#8b5cf6',
5: '#f59e0b',
6: '#ef4444',
}
function levelProgress(level: number, totalEarned: number): number {
// Simple XP curve: each level needs level*500 kr more
const levelThreshold = level * 50000 // in öre = level * 500 SEK
const prevThreshold = (level - 1) * 50000
const progress = (totalEarned - prevThreshold) / (levelThreshold - prevThreshold)
return Math.min(Math.max(progress, 0), 1)
}
export function ProfileScreen() {
const [zoomer, setZoomer] = useState<Zoomer | null>(null)
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const load = useCallback(async () => {
try {
const data = await profile.get()
setZoomer(data)
} catch {
// silent
} finally {
setLoading(false)
setRefreshing(false)
}
}, [])
useEffect(() => { load() }, [])
const onRefresh = useCallback(() => { setRefreshing(true); load() }, [load])
const handleSignOut = () => {
Alert.alert(
'Logga ut',
'Är du säker på att du vill logga ut?',
[
{ text: 'Avbryt', style: 'cancel' },
{
text: 'Logga ut',
style: 'destructive',
onPress: async () => {
await auth.logout()
// AppNavigator will detect missing token and show AuthScreen
},
},
],
)
}
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator color="#6366f1" size="large" />
</View>
)
}
const level = zoomer?.level ?? 1
const levelLabel = LEVEL_LABELS[level] ?? `Nivå ${level}`
const levelColor = LEVEL_COLOR[level] ?? '#6366f1'
const progress = zoomer ? levelProgress(level, zoomer.total_earned) : 0
const initial = (zoomer?.display_name ?? zoomer?.email ?? '?')[0].toUpperCase()
return (
<ScrollView
style={styles.container}
contentContainerStyle={{ paddingBottom: 40 }}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#6366f1" />}
>
{/* Avatar + name */}
<View style={styles.headerSection}>
<View style={[styles.avatar, { borderColor: levelColor }]}>
<Text style={styles.avatarText}>{initial}</Text>
</View>
<Text style={styles.displayName}>
{zoomer?.display_name || 'Namnlös zoomer'}
</Text>
<Text style={styles.email}>{zoomer?.email}</Text>
{/* Level badge */}
<View style={[styles.levelBadge, { backgroundColor: levelColor + '22', borderColor: levelColor + '55' }]}>
<Text style={[styles.levelBadgeText, { color: levelColor }]}>
{levelLabel}
</Text>
</View>
{/* XP bar */}
<View style={styles.xpBarWrap}>
<View style={styles.xpBarBg}>
<View style={[styles.xpBarFill, { width: `${(progress * 100).toFixed(0)}%` as any, backgroundColor: levelColor }]} />
</View>
<Text style={styles.xpLabel}>
Nivå {level} {level + 1} ({(progress * 100).toFixed(0)}%)
</Text>
</View>
</View>
{/* Stats */}
<View style={styles.statsGrid}>
<View style={styles.statCard}>
<Text style={styles.statValue}>
{zoomer ? (zoomer.total_earned / 100).toFixed(0) : '0'} kr
</Text>
<Text style={styles.statLabel}>Totalt intjänat</Text>
</View>
<View style={styles.statCard}>
<Text style={[styles.statValue, { color: '#10b981' }]}>
{zoomer?.missions_completed ?? 0}
</Text>
<Text style={styles.statLabel}>Uppdrag klara</Text>
</View>
<View style={styles.statCard}>
<Text style={[styles.statValue, { color: levelColor }]}>
{level}
</Text>
<Text style={styles.statLabel}>Nivå</Text>
</View>
<View style={styles.statCard}>
<Text style={[styles.statValue, { color: '#f59e0b' }]}>
{zoomer?.missions_completed ?? 0}
</Text>
<Text style={styles.statLabel}>Uppdrag</Text>
</View>
</View>
{/* Badges — kommer när backend har stöd */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Badges</Text>
<View style={styles.emptyBadges}>
<Text style={styles.emptyEmoji}>🏅</Text>
<Text style={styles.emptyText}>
Slutför ditt första uppdrag för att låsa upp din första badge!
</Text>
</View>
</View>
{/* Member since */}
{zoomer?.joined_at && (
<Text style={styles.memberSince}>
Zoomer sedan {new Date(zoomer.joined_at).toLocaleDateString('sv-SE', { month: 'long', year: 'numeric' })}
</Text>
)}
{/* Sign out */}
<View style={styles.section}>
<TouchableOpacity style={styles.signOutBtn} onPress={handleSignOut}>
<Text style={styles.signOutText}>Logga ut</Text>
</TouchableOpacity>
</View>
</ScrollView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
center: { flex: 1, backgroundColor: '#0A0A1B', alignItems: 'center', justifyContent: 'center' },
headerSection: {
alignItems: 'center',
paddingTop: 64,
paddingHorizontal: 24,
paddingBottom: 24,
borderBottomWidth: 1,
borderBottomColor: 'rgba(255,255,255,0.06)',
},
avatar: {
width: 88, height: 88, borderRadius: 44,
backgroundColor: 'rgba(99,102,241,0.3)',
alignItems: 'center', justifyContent: 'center',
marginBottom: 16,
borderWidth: 3,
},
avatarText: { color: '#fff', fontSize: 36, fontWeight: '800' },
displayName: { color: '#fff', fontSize: 22, fontWeight: '800', marginBottom: 4 },
email: { color: 'rgba(255,255,255,0.4)', fontSize: 14, marginBottom: 16 },
levelBadge: {
borderRadius: 20, paddingHorizontal: 16, paddingVertical: 8,
borderWidth: 1, marginBottom: 16,
},
levelBadgeText: { fontWeight: '700', fontSize: 13 },
xpBarWrap: { width: '100%', gap: 6 },
xpBarBg: {
height: 6, backgroundColor: 'rgba(255,255,255,0.08)',
borderRadius: 3, overflow: 'hidden',
},
xpBarFill: { height: '100%', borderRadius: 3 },
xpLabel: { color: 'rgba(255,255,255,0.3)', fontSize: 11, textAlign: 'center' },
statsGrid: {
flexDirection: 'row', flexWrap: 'wrap',
padding: 16, gap: 10,
},
statCard: {
width: '47%',
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 16, padding: 16,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
alignItems: 'center',
},
statValue: { color: '#fff', fontSize: 24, fontWeight: '800', marginBottom: 4 },
statLabel: { color: 'rgba(255,255,255,0.35)', fontSize: 12 },
section: { paddingHorizontal: 16, marginTop: 8 },
sectionTitle: {
color: 'rgba(255,255,255,0.35)', fontSize: 12, fontWeight: '700',
letterSpacing: 1, textTransform: 'uppercase', marginBottom: 12,
},
badgesGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
badgeCard: {
width: '30%',
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 14, padding: 12,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
alignItems: 'center', gap: 4,
},
badgeEmoji: { fontSize: 28 },
badgeLabel: { color: '#fff', fontSize: 11, fontWeight: '600', textAlign: 'center' },
badgeDate: { color: 'rgba(255,255,255,0.3)', fontSize: 9 },
emptyBadges: {
backgroundColor: 'rgba(255,255,255,0.03)',
borderRadius: 16, padding: 24,
alignItems: 'center', gap: 8,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.06)',
},
emptyEmoji: { fontSize: 36 },
emptyText: { color: 'rgba(255,255,255,0.4)', fontSize: 13, textAlign: 'center', lineHeight: 20 },
memberSince: {
color: 'rgba(255,255,255,0.2)', fontSize: 12, textAlign: 'center',
marginTop: 16, marginBottom: 8,
},
signOutBtn: {
borderWidth: 1, borderColor: 'rgba(239,68,68,0.35)',
borderRadius: 14, paddingVertical: 14,
alignItems: 'center', marginTop: 8,
},
signOutText: { color: '#ef4444', fontWeight: '700', fontSize: 15 },
})
@@ -0,0 +1,340 @@
/**
* seed-sverige-launch.ts
*
* Skapar 50 seed-uppdrag i Stockholms skärgård för quiXzoom Sverige-launch (juni 2026).
* Koordinater runt Vaxholm, Sandhamn, Grinda, Möja och angränsande öar.
*
* Usage:
* QUIXZOOM_API_URL=https://api.quixzoom.com \
* QUIXZOOM_ADMIN_TOKEN=<admin-jwt> \
* npx ts-node scripts/seed-sverige-launch.ts
*/
const API_BASE = process.env.QUIXZOOM_API_URL ?? 'https://api.quixzoom.com'
const ADMIN_TOKEN = process.env.QUIXZOOM_ADMIN_TOKEN ?? ''
// ─── Geografiska ankarpunkter ──────────────────────────────────────────────
interface AnchorPoint {
name: string
lat: number
lng: number
}
const ANCHORS: AnchorPoint[] = [
{ name: 'Vaxholm', lat: 59.4028, lng: 18.3512 },
{ name: 'Sandhamn', lat: 59.2939, lng: 18.9127 },
{ name: 'Grinda', lat: 59.3544, lng: 18.6884 },
{ name: 'Möja', lat: 59.4456, lng: 18.7989 },
{ name: 'Utö', lat: 58.9697, lng: 17.9901 },
{ name: 'Fjäderholmarna', lat: 59.3295, lng: 18.1778 },
{ name: 'Värmdö', lat: 59.3241, lng: 18.5301 },
{ name: 'Nämdö', lat: 59.1936, lng: 18.8001 },
{ name: 'Runmarö', lat: 59.2581, lng: 18.7890 },
{ name: 'Ingarö', lat: 59.3102, lng: 18.4623 },
]
// ─── Kategorier & mallar ──────────────────────────────────────────────────
type Category = 'infrastruktur' | 'vägar' | 'hamnar' | 'bryggor' | 'fritidshus'
interface MissionTemplate {
category: Category
titles: string[]
descriptions: string[]
instructions: string
requirements: string[]
rewardRange: [number, number] // SEK
}
const TEMPLATES: MissionTemplate[] = [
{
category: 'hamnar',
titles: [
'Dokumentera hamninloppet',
'Hamnens parkeringsytor',
'Biljettbox och infoskyltning',
'Hamnkapacitet vid högsäsong',
'Gästhamns facilitetsstatus',
],
descriptions: [
'Fotografera hamninloppet från kajen och visa djupmarkeringar, bojar och eventuella hinder.',
'Dokumentera tillgängliga parkeringsplatser för bilar och båptrailers vid hamnen.',
'Fota biljettautomater, informationsskyltar och eventuella QR-koder i hamnen.',
'Visa antal båtplatser, beläggningsgrad och kösituation under högsäsong.',
'Dokumentera toaletter, duschar, sophantering och laddstationer för båtar.',
],
instructions: 'Ta minst 3 bilder från olika vinklar. Inkludera en vid bild och en närbild av skyltar/utrustning.',
requirements: [
'Minst 3 bilder krävs',
'Inkludera en översiktsbild',
'Tydliga bilder utan oskärpa',
],
rewardRange: [200, 350],
},
{
category: 'bryggor',
titles: [
'Brygga skick och bärighet',
'Förtöjningsutrustning',
'Färjebrygga dokumentation',
'Privat brygga tillståndskoll',
'Brygga efter vinterlagring',
],
descriptions: [
'Dokumentera bryggornas skick — räcken, plankor, belysning och eventuella skador.',
'Fotografera förtöjningspollare, ringar och eluttag längs bryggan.',
'Fota färjebryggan, rampen, väntutrymmen och informationsskyltar.',
'Dokumentera privat brygga vid angiven fastighet för försäkringsändamål.',
'Kontrollera bryggas skick efter vintersäsongen — lös plankor, rostiga delar, sättningar.',
],
instructions: 'Gå ut på bryggan om det är säkert. Ta bilder rakt ned för att visa plankors skick.',
requirements: [
'Bilder från bryggans kant och mitt',
'Närbild på eventuella skador',
'Inga personuppgifter i bild',
],
rewardRange: [150, 280],
},
{
category: 'vägar',
titles: [
'Vägunderhåll kontroll',
'Vägskyltning dokumentation',
'Grusväg efter tjällossning',
'Brovägsstatus',
'Vinterväghållning uppföljning',
],
descriptions: [
'Fotografera vägbeläggningens skick — sprickor, gropar, kantskador.',
'Dokumentera hastighetsskyltar, vägvisare och varningsmärken längs rutten.',
'Kontrollera och fotografera grusvägens skick efter vårens tjällossning.',
'Dokumentera brobanans skick, räcken och eventuella sättningar.',
'Fotografera vägens skick och snöröjning/sandning efter vinterperiod.',
],
instructions: 'Fotografera vägen i körfältets riktning. Inkludera referensobjekt för skala.',
requirements: [
'Minst 4 bilder längs sträckan',
'GPS-position ska matcha uppdraget',
'Inga bilar med läsbar registreringsskylt',
],
rewardRange: [180, 320],
},
{
category: 'infrastruktur',
titles: [
'Elnätsmast status',
'Avloppspumpstation',
'Vattenreservoar inspektion',
'Fiberkabel markering',
'Mobilmaster täckning',
],
descriptions: [
'Dokumentera elnätsmastens skick, eventuella skador och omgivande vegetation.',
'Fotografera avloppspumpstationen, larm och omgärdning.',
'Dokumentera vattenreservoarens yttre skick, lås och skyltning.',
'Markera och fotografera fiberkabelmarkeringar längs given sträcka.',
'Dokumentera mobilmastens placering, eventuella skador och omgivande byggnader.',
],
instructions: 'Fotografera enbart utsidan av anläggningar. Gå inte in i inhägnade områden.',
requirements: [
'Håll säkerhetsavstånd till elinstallationer',
'Fotografera ej inre av installationer',
'Inkludera omgivningsbild',
],
rewardRange: [250, 400],
},
{
category: 'fritidshus',
titles: [
'Fastighetsdokumentation exteriör',
'Stormskador efter höststormar',
'Renovering före/efter',
'Fastighetsläge och utsikt',
'Tomt och omgivning dokumentation',
],
descriptions: [
'Fotografera fastighetens exteriör från alla fyra sidor för försäkrings- och värderingsändamål.',
'Dokumentera eventuella stormskador på tak, fasad, fönster och omgivande träd.',
'Fotografera renoveringsarbeten på angiven fastighet för projektuppföljning.',
'Dokumentera fastighetens läge, utsikt, strandlinje och angränsande fastigheter.',
'Fotografera tomt, staket, sjöbod, brygga och angränsande skog/mark.',
],
instructions: 'Fotografera enbart utsidan. Inga interiörbilder. Respektera privatpersoners integritet.',
requirements: [
'Enbart exteriörbilder',
'Alla fyra fasader om möjligt',
'Inga privatpersoner i bild',
'GDPR-kompatibelt material',
],
rewardRange: [200, 380],
},
]
// ─── Hjälpfunktioner ───────────────────────────────────────────────────────
function randomBetween(min: number, max: number): number {
return Math.random() * (max - min) + min
}
function jitter(value: number, maxDelta: number): number {
return value + randomBetween(-maxDelta, maxDelta)
}
function pickRandom<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)]
}
function deadlineFromNow(hours: number): string {
return new Date(Date.now() + hours * 3600 * 1000).toISOString()
}
// ─── Uppdragsgenerator ────────────────────────────────────────────────────
interface SeedMission {
title: string
description: string
instructions: string
requirements: string[]
category: Category
latitude: number
longitude: number
city: string
reward_amount: number // öre
reward_currency: string
deadline_at: string
status: 'open'
max_submissions: number
tags: string[]
}
function generateMissions(count: number): SeedMission[] {
const missions: SeedMission[] = []
for (let i = 0; i < count; i++) {
const anchor = pickRandom(ANCHORS)
const template = pickRandom(TEMPLATES)
const titleIdx = Math.floor(Math.random() * template.titles.length)
const descIdx = Math.floor(Math.random() * template.descriptions.length)
// Sprid ut koordinaterna kring ankarpunkten (±0.05 grader ≈ ±5 km)
const lat = jitter(anchor.lat, 0.05)
const lng = jitter(anchor.lng, 0.08)
// Belöning i öre (SEK * 100), rundad till 50 kr steg
const rewardSEK = Math.round(randomBetween(
template.rewardRange[0],
template.rewardRange[1],
) / 50) * 50
const rewardOre = rewardSEK * 100
// Deadline: 2472 timmar
const deadlineHours = Math.floor(randomBetween(24, 72))
missions.push({
title: template.titles[titleIdx],
description: template.descriptions[descIdx],
instructions: template.instructions,
requirements: template.requirements,
category: template.category,
latitude: Math.round(lat * 100000) / 100000,
longitude: Math.round(lng * 100000) / 100000,
city: anchor.name,
reward_amount: rewardOre,
reward_currency: 'SEK',
deadline_at: deadlineFromNow(deadlineHours),
status: 'open',
max_submissions: Math.floor(randomBetween(1, 4)),
tags: ['sverige-launch-2026', 'stockholms-skärgård', anchor.name.toLowerCase()],
})
}
return missions
}
// ─── API POST ──────────────────────────────────────────────────────────────
async function postMission(mission: SeedMission): Promise<void> {
const res = await fetch(`${API_BASE}/missions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${ADMIN_TOKEN}`,
},
body: JSON.stringify(mission),
})
if (!res.ok) {
const body = await res.text()
throw new Error(`HTTP ${res.status}: ${body}`)
}
const created = await res.json() as { id: string }
console.log(`${mission.city.padEnd(20)} [${mission.category.padEnd(14)}] ${(mission.reward_amount / 100)} SEK → id=${created.id}`)
}
// ─── Main ──────────────────────────────────────────────────────────────────
async function main() {
console.log('🇸🇪 quiXzoom Sverige-launch seed — 50 uppdrag i Stockholms skärgård\n')
if (!ADMIN_TOKEN) {
console.warn('⚠️ QUIXZOOM_ADMIN_TOKEN inte satt — kör i DRY-RUN-läge (skriver ej till API)\n')
}
const missions = generateMissions(50)
// Visa fördelning
const byCat: Record<string, number> = {}
const byCity: Record<string, number> = {}
for (const m of missions) {
byCat[m.category] = (byCat[m.category] ?? 0) + 1
byCity[m.city] = (byCity[m.city] ?? 0) + 1
}
console.log('Kategorifördelning:')
for (const [cat, n] of Object.entries(byCat).sort((a, b) => b[1] - a[1])) {
console.log(` ${cat.padEnd(20)} ${n} uppdrag`)
}
console.log('\nPlatsfördelning:')
for (const [city, n] of Object.entries(byCity).sort((a, b) => b[1] - a[1])) {
console.log(` ${city.padEnd(20)} ${n} uppdrag`)
}
const totalSEK = missions.reduce((s, m) => s + m.reward_amount / 100, 0)
const avgSEK = totalSEK / missions.length
console.log(`\nTotalt utlagt: ${totalSEK.toFixed(0)} SEK | Snitt: ${avgSEK.toFixed(0)} SEK/uppdrag`)
console.log(`Deadline-spann: 2472 timmar\n`)
if (!ADMIN_TOKEN) {
console.log('DRY-RUN: Visar första 3 uppdrag:')
for (const m of missions.slice(0, 3)) {
console.log(JSON.stringify(m, null, 2))
}
console.log('\nSätt QUIXZOOM_ADMIN_TOKEN för att posta till API.')
return
}
// Post i batchar om 5 med fördröjning
const BATCH_SIZE = 5
const DELAY_MS = 300
let success = 0
let failed = 0
for (let i = 0; i < missions.length; i += BATCH_SIZE) {
const batch = missions.slice(i, i + BATCH_SIZE)
await Promise.allSettled(
batch.map(m =>
postMission(m)
.then(() => { success++ })
.catch(e => { console.error(`${m.title}: ${e.message}`); failed++ }),
),
)
if (i + BATCH_SIZE < missions.length) {
await new Promise(r => setTimeout(r, DELAY_MS))
}
}
console.log(`\n✅ Klar! ${success} uppdrag skapade, ${failed} misslyckades.`)
console.log('🚀 Sverige-launch redo för juni 2026!')
}
main().catch(console.error)
@@ -0,0 +1,210 @@
/**
* ActiveMissionMap.tsx — Mission map with live tracking, auto-follow, distance/heading overlay
* Wraps react-native-maps with live location integration
*/
import { useEffect, useRef, useCallback } from 'react'
import { View, Text, StyleSheet } from 'react-native'
import MapView, { Marker, Region, PROVIDER_DEFAULT } from 'react-native-maps'
import { useLocationStore } from '../../stores/locationStore'
import { UserLocationMarker } from './UserLocationMarker'
import { FollowUserButton } from './FollowUserButton'
import type { Mission } from '../../../lib/api'
interface ActiveMissionMapProps {
mission?: Mission
style?: object
}
export function ActiveMissionMap({ mission, style }: ActiveMissionMapProps) {
const mapRef = useRef<MapView>(null)
const {
currentPosition,
heading,
followUser,
setUserPannedMap,
} = useLocationStore()
// Auto-follow user when followUser is true
useEffect(() => {
if (followUser && currentPosition && mapRef.current) {
const region: Region = {
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}
mapRef.current.animateToRegion(region, 300)
}
}, [currentPosition, followUser])
const onPanDrag = useCallback(() => {
setUserPannedMap(true)
}, [setUserPannedMap])
// Calculate distance and bearing to mission
const getDistanceToMission = (): string => {
if (!currentPosition || !mission) return '—'
const R = 6371e3 // Earth radius in meters
const φ1 = (currentPosition.coords.latitude * Math.PI) / 180
const φ2 = (mission.latitude * Math.PI) / 180
const Δφ = ((mission.latitude - currentPosition.coords.latitude) * Math.PI) / 180
const Δλ = ((mission.longitude - currentPosition.coords.longitude) * Math.PI) / 180
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
const distance = R * c
if (distance < 1000) {
return `${Math.round(distance)} m`
}
return `${(distance / 1000).toFixed(1)} km`
}
const getBearingToMission = (): number | null => {
if (!currentPosition || !mission) return null
const φ1 = (currentPosition.coords.latitude * Math.PI) / 180
const φ2 = (mission.latitude * Math.PI) / 180
const Δλ = ((mission.longitude - currentPosition.coords.longitude) * Math.PI) / 180
const y = Math.sin(Δλ) * Math.cos(φ2)
const x =
Math.cos(φ1) * Math.sin(φ2) -
Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ)
const θ = Math.atan2(y, x)
const bearing = ((θ * 180) / Math.PI + 360) % 360
return bearing
}
const bearing = getBearingToMission()
const relativeBearing = bearing != null && heading != null
? ((bearing - heading + 360) % 360)
: null
return (
<View style={[styles.container, style]}>
<MapView
ref={mapRef}
style={styles.map}
provider={PROVIDER_DEFAULT}
showsUserLocation={false} // We use custom marker
showsMyLocationButton={false}
onPanDrag={onPanDrag}
rotateEnabled={false} // Map stays north-up, marker rotates
>
{/* Custom user location marker */}
{currentPosition && (
<Marker
coordinate={{
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
}}
anchor={{ x: 0.5, y: 0.5 }}
flat={true}
>
<UserLocationMarker />
</Marker>
)}
{/* Mission marker */}
{mission && (
<Marker
coordinate={{
latitude: mission.latitude,
longitude: mission.longitude,
}}
pinColor="#10b981"
title={mission.title}
/>
)}
</MapView>
{/* Distance / heading overlay */}
{mission && (
<View style={styles.overlay}>
<View style={styles.overlayRow}>
<Text style={styles.overlayLabel}>Avstånd</Text>
<Text style={styles.overlayValue}>{getDistanceToMission()}</Text>
</View>
{relativeBearing != null && (
<View style={styles.overlayRow}>
<Text style={styles.overlayLabel}>Riktning</Text>
<Text style={styles.overlayValue}>
{getDirectionArrow(relativeBearing)} {relativeBearing.toFixed(0)}°
</Text>
</View>
)}
{heading != null && (
<View style={styles.overlayRow}>
<Text style={styles.overlayLabel}>Din kurs</Text>
<Text style={styles.overlayValue}>{heading.toFixed(0)}°</Text>
</View>
)}
</View>
)}
{/* Follow user button */}
<View style={styles.followButtonContainer}>
<FollowUserButton />
</View>
</View>
)
}
function getDirectionArrow(degrees: number): string {
// Convert bearing to cardinal direction arrow
if (degrees >= 337.5 || degrees < 22.5) return '↑'
if (degrees >= 22.5 && degrees < 67.5) return '↗'
if (degrees >= 67.5 && degrees < 112.5) return '→'
if (degrees >= 112.5 && degrees < 157.5) return '↘'
if (degrees >= 157.5 && degrees < 202.5) return '↓'
if (degrees >= 202.5 && degrees < 247.5) return '↙'
if (degrees >= 247.5 && degrees < 292.5) return '←'
return '↖'
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
map: {
flex: 1,
},
overlay: {
position: 'absolute',
top: 16,
left: 16,
backgroundColor: 'rgba(10,10,27,0.85)',
borderRadius: 12,
padding: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
minWidth: 140,
},
overlayRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
overlayLabel: {
color: 'rgba(255,255,255,0.45)',
fontSize: 11,
marginRight: 12,
},
overlayValue: {
color: '#fff',
fontSize: 13,
fontWeight: '700',
},
followButtonContainer: {
position: 'absolute',
bottom: 100,
right: 16,
},
})
@@ -0,0 +1,61 @@
/**
* FollowUserButton.tsx — Toggle follow/nofollow, return to follow after pan
* State managed in locationStore
*/
import { TouchableOpacity, Text, StyleSheet } from 'react-native'
import { useLocationStore } from '../../stores/locationStore'
interface FollowUserButtonProps {
onPress?: () => void
}
export function FollowUserButton({ onPress }: FollowUserButtonProps) {
const { followUser, setFollowUser } = useLocationStore()
const handlePress = () => {
const newFollow = !followUser
setFollowUser(newFollow)
onPress?.()
}
return (
<TouchableOpacity
style={[styles.button, followUser && styles.buttonActive]}
onPress={handlePress}
activeOpacity={0.8}
>
<Text style={[styles.icon, followUser && styles.iconActive]}>
{followUser ? '📍' : '📌'}
</Text>
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
button: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: 'rgba(10,10,27,0.9)',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.15)',
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 4,
},
buttonActive: {
backgroundColor: 'rgba(99,102,241,0.3)',
borderColor: '#6366f1',
},
icon: {
fontSize: 20,
opacity: 0.6,
},
iconActive: {
opacity: 1,
},
})
@@ -0,0 +1,145 @@
/**
* GpsDiagnostics.tsx — Developer overlay for GPS metrics
* Semi-transparent overlay, toggle via settings
*/
import { View, Text, StyleSheet } from 'react-native'
import { useLocationStore } from '../../stores/locationStore'
export function GpsDiagnostics() {
const {
currentPosition,
heading,
headingAccuracy,
accuracy,
speed,
isTracking,
isStale,
lastUpdateTimestamp,
updateIntervalMs,
locationAuthStatus,
} = useLocationStore()
const locationAge = lastUpdateTimestamp
? ((Date.now() - lastUpdateTimestamp) / 1000).toFixed(1)
: '—'
const speedKmh = speed != null ? (speed * 3.6).toFixed(1) : '—'
return (
<View style={styles.container}>
<View style={styles.panel}>
<Text style={styles.title}>🛰 GPS Diagnostics</Text>
<View style={styles.row}>
<Text style={styles.label}>Tracking</Text>
<Text style={[styles.value, isTracking ? styles.good : styles.bad]}>
{isTracking ? '● LIVE' : '○ OFF'}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Auth Status</Text>
<Text style={styles.value}>{locationAuthStatus ?? '—'}</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>GPS Accuracy</Text>
<Text style={[styles.value, getAccuracyColor(accuracy)]}>
{accuracy != null ? `${accuracy.toFixed(1)} m` : '—'}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Heading</Text>
<Text style={styles.value}>
{heading != null ? `${heading.toFixed(1)}°` : '—'}
{headingAccuracy != null ? `${headingAccuracy.toFixed(0)}°)` : ''}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Speed</Text>
<Text style={styles.value}>
{speed != null ? `${speed.toFixed(1)} m/s (${speedKmh} km/h)` : '—'}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Update Interval</Text>
<Text style={styles.value}>
{updateIntervalMs != null ? `${updateIntervalMs} ms` : '—'}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Location Age</Text>
<Text style={[styles.value, isStale ? styles.bad : styles.good]}>
{locationAge}s {isStale ? '(STALE)' : ''}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Coords</Text>
<Text style={styles.value} numberOfLines={1}>
{currentPosition
? `${currentPosition.coords.latitude.toFixed(6)}, ${currentPosition.coords.longitude.toFixed(6)}`
: '—'}
</Text>
</View>
</View>
</View>
)
}
function getAccuracyColor(accuracy: number | null): object {
if (accuracy == null) return styles.neutral
if (accuracy <= 5) return styles.excellent
if (accuracy <= 10) return styles.good
if (accuracy <= 20) return styles.warning
return styles.bad
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 100,
right: 8,
zIndex: 999,
},
panel: {
backgroundColor: 'rgba(0,0,0,0.75)',
borderRadius: 12,
padding: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
minWidth: 220,
},
title: {
color: '#fff',
fontSize: 12,
fontWeight: '800',
marginBottom: 8,
letterSpacing: 0.5,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
label: {
color: 'rgba(255,255,255,0.45)',
fontSize: 10,
},
value: {
color: '#fff',
fontSize: 10,
fontWeight: '600',
maxWidth: 140,
},
excellent: { color: '#10b981' },
good: { color: '#34d399' },
warning: { color: '#f59e0b' },
bad: { color: '#ef4444' },
neutral: { color: 'rgba(255,255,255,0.5)' },
})
@@ -0,0 +1,154 @@
/**
* UserLocationMarker.tsx — Animated user location marker with heading rotation
* Includes accuracy circle (pulsing) and smooth heading animation
*/
import { useEffect, useRef } from 'react'
import { View, StyleSheet, Animated } from 'react-native'
import { useLocationStore } from '../../stores/locationStore'
export function UserLocationMarker() {
const { heading, accuracy } = useLocationStore()
const rotationAnim = useRef(new Animated.Value(0)).current
const pulseAnim = useRef(new Animated.Value(1)).current
// Smooth heading rotation
useEffect(() => {
if (heading != null) {
Animated.spring(rotationAnim, {
toValue: heading,
useNativeDriver: true,
friction: 8,
tension: 40,
}).start()
}
}, [heading, rotationAnim])
// Pulsing accuracy circle
useEffect(() => {
const pulse = Animated.loop(
Animated.sequence([
Animated.timing(pulseAnim, {
toValue: 1.3,
duration: 1500,
useNativeDriver: true,
}),
Animated.timing(pulseAnim, {
toValue: 1,
duration: 1500,
useNativeDriver: true,
}),
])
)
pulse.start()
return () => {
pulse.stop()
}
}, [pulseAnim])
const spin = rotationAnim.interpolate({
inputRange: [0, 360],
outputRange: ['0deg', '360deg'],
})
const accuracyRadius = accuracy ?? 10
return (
<View style={styles.container}>
{/* Accuracy circle (pulsing) */}
<Animated.View
style={[
styles.accuracyCircle,
{
width: accuracyRadius * 2,
height: accuracyRadius * 2,
borderRadius: accuracyRadius,
transform: [{ scale: pulseAnim }],
opacity: pulseAnim.interpolate({
inputRange: [1, 1.3],
outputRange: [0.3, 0.1],
}),
},
]}
/>
{/* Static accuracy ring */}
<View
style={[
styles.accuracyRing,
{
width: accuracyRadius * 2,
height: accuracyRadius * 2,
borderRadius: accuracyRadius,
},
]}
/>
{/* User dot */}
<View style={styles.dot}>
<View style={styles.dotInner} />
</View>
{/* Heading arrow */}
<Animated.View style={[styles.arrow, { transform: [{ rotate: spin }] }]}>
<View style={styles.arrowShape} />
</Animated.View>
</View>
)
}
const styles = StyleSheet.create({
container: {
width: 60,
height: 60,
alignItems: 'center',
justifyContent: 'center',
},
accuracyCircle: {
position: 'absolute',
backgroundColor: 'rgba(99,102,241,0.3)',
},
accuracyRing: {
position: 'absolute',
borderWidth: 1,
borderColor: 'rgba(99,102,241,0.4)',
backgroundColor: 'transparent',
},
dot: {
width: 18,
height: 18,
borderRadius: 9,
backgroundColor: '#fff',
borderWidth: 3,
borderColor: '#6366f1',
alignItems: 'center',
justifyContent: 'center',
zIndex: 2,
},
dotInner: {
width: 6,
height: 6,
borderRadius: 3,
backgroundColor: '#6366f1',
},
arrow: {
position: 'absolute',
width: 40,
height: 40,
alignItems: 'center',
justifyContent: 'flex-start',
zIndex: 1,
},
arrowShape: {
width: 0,
height: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: 6,
borderRightWidth: 6,
borderBottomWidth: 14,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: 'rgba(99,102,241,0.85)',
marginTop: -4,
},
})
@@ -0,0 +1,7 @@
/**
* GPS Components barrel export
*/
export { GpsDiagnostics } from './GpsDiagnostics'
export { UserLocationMarker } from './UserLocationMarker'
export { FollowUserButton } from './FollowUserButton'
export { ActiveMissionMap } from './ActiveMissionMap'
@@ -0,0 +1,204 @@
/**
* AuthScreen — JWT-baserad login/signup (identity-core, ej Supabase)
*/
import { useState } from 'react'
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
KeyboardAvoidingView,
Platform,
Alert,
ScrollView,
} from 'react-native'
import { auth } from '../../../lib/api'
interface Props {
onLoginSuccess: () => void
}
export function AuthScreen({ onLoginSuccess }: Props) {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [displayName, setDisplayName] = useState('')
const [loading, setLoading] = useState(false)
const [mode, setMode] = useState<'login' | 'signup'>('login')
const submit = async () => {
if (!email.trim() || !password) return
setLoading(true)
try {
if (mode === 'login') {
await auth.login(email.trim(), password)
} else {
if (password.length < 8) {
Alert.alert('Lösenord för kort', 'Minst 8 tecken krävs.')
return
}
await auth.register(email.trim(), password, displayName.trim() || undefined)
}
onLoginSuccess()
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : 'Okänt fel — försök igen.'
Alert.alert('Inloggning misslyckades', msg)
} finally {
setLoading(false)
}
}
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<ScrollView
contentContainerStyle={styles.inner}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
{/* Logo */}
<Text style={styles.logo}>quiXzoom</Text>
<Text style={styles.tagline}>Bli zoomer. Tjäna pengar. dina villkor.</Text>
{/* Mode toggle */}
<View style={styles.modeToggle}>
<TouchableOpacity
style={[styles.modeBtn, mode === 'login' && styles.modeBtnActive]}
onPress={() => setMode('login')}
>
<Text style={[styles.modeBtnText, mode === 'login' && styles.modeBtnTextActive]}>
Logga in
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modeBtn, mode === 'signup' && styles.modeBtnActive]}
onPress={() => setMode('signup')}
>
<Text style={[styles.modeBtnText, mode === 'signup' && styles.modeBtnTextActive]}>
Bli zoomer
</Text>
</TouchableOpacity>
</View>
{/* Form */}
<View style={styles.form}>
{mode === 'signup' && (
<View style={styles.inputWrap}>
<Text style={styles.inputLabel}>Namn (valfritt)</Text>
<TextInput
style={styles.input}
placeholder="Vad ska folk kalla dig?"
placeholderTextColor="rgba(255,255,255,0.25)"
value={displayName}
onChangeText={setDisplayName}
autoCapitalize="words"
returnKeyType="next"
/>
</View>
)}
<View style={styles.inputWrap}>
<Text style={styles.inputLabel}>E-post</Text>
<TextInput
style={styles.input}
placeholder="din@epost.se"
placeholderTextColor="rgba(255,255,255,0.25)"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
returnKeyType="next"
/>
</View>
<View style={styles.inputWrap}>
<Text style={styles.inputLabel}>Lösenord</Text>
<TextInput
style={styles.input}
placeholder={mode === 'signup' ? 'Minst 8 tecken' : '••••••••'}
placeholderTextColor="rgba(255,255,255,0.25)"
value={password}
onChangeText={setPassword}
secureTextEntry
returnKeyType="done"
onSubmitEditing={submit}
/>
</View>
<TouchableOpacity
style={[styles.btn, loading && styles.btnDisabled]}
onPress={submit}
disabled={loading}
>
<Text style={styles.btnText}>
{loading ? '…' : mode === 'login' ? 'Logga in' : 'Skapa zoomer-konto'}
</Text>
</TouchableOpacity>
</View>
{/* Sverige launch note */}
{mode === 'signup' && (
<View style={styles.launchNote}>
<Text style={styles.launchNoteText}>
🇸🇪 quiXzoom lanserar i Stockholms skärgård juni 2026.
Registrera dig nu och var bland de första zoomerarna.
</Text>
</View>
)}
</ScrollView>
</KeyboardAvoidingView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
inner: { flexGrow: 1, justifyContent: 'center', padding: 24, paddingBottom: 48 },
logo: {
fontSize: 40, fontWeight: '900', color: '#fff',
textAlign: 'center', marginBottom: 6, letterSpacing: -2,
},
tagline: {
color: 'rgba(255,255,255,0.35)',
textAlign: 'center', marginBottom: 40, fontSize: 14, lineHeight: 20,
},
modeToggle: {
flexDirection: 'row',
backgroundColor: 'rgba(255,255,255,0.05)',
borderRadius: 14, padding: 4,
marginBottom: 28,
},
modeBtn: { flex: 1, paddingVertical: 10, alignItems: 'center', borderRadius: 10 },
modeBtnActive: { backgroundColor: '#6366f1' },
modeBtnText: { color: 'rgba(255,255,255,0.4)', fontWeight: '600', fontSize: 14 },
modeBtnTextActive: { color: '#fff' },
form: { gap: 14 },
inputWrap: { gap: 6 },
inputLabel: { color: 'rgba(255,255,255,0.45)', fontSize: 12, fontWeight: '600', letterSpacing: 0.5 },
input: {
backgroundColor: 'rgba(255,255,255,0.06)',
borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)',
borderRadius: 14, paddingHorizontal: 16, paddingVertical: 14,
color: '#fff', fontSize: 16,
},
btn: {
backgroundColor: '#6366f1',
borderRadius: 14, paddingVertical: 16,
alignItems: 'center', marginTop: 4,
},
btnDisabled: { opacity: 0.6 },
btnText: { color: '#fff', fontWeight: '800', fontSize: 16 },
launchNote: {
marginTop: 24,
backgroundColor: 'rgba(99,102,241,0.1)',
borderRadius: 12, padding: 14,
borderWidth: 1, borderColor: 'rgba(99,102,241,0.25)',
},
launchNoteText: { color: 'rgba(255,255,255,0.55)', fontSize: 13, lineHeight: 20, textAlign: 'center' },
})
@@ -0,0 +1,259 @@
/**
* PasswordlessApprovalScreen — App visar godkännande för webb-inloggning
* Används när appen öppnas via deep link från webb
*/
import { useState, useEffect } from 'react'
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ActivityIndicator,
Alert,
} from 'react-native'
import { auth } from '../../../lib/api'
interface Props {
sessionId: string
requestToken: string
onComplete: () => void
onCancel: () => void
}
export function PasswordlessApprovalScreen({
sessionId,
requestToken,
onComplete,
onCancel,
}: Props) {
const [loading, setLoading] = useState(false)
const [sessionInfo, setSessionInfo] = useState<{
deviceName?: string
location?: string
browser?: string
}>({})
useEffect(() => {
// Hämta session info om möjligt
fetchSessionInfo()
}, [])
const fetchSessionInfo = async () => {
try {
// TODO: Implementera endpoint för att hämta session info
// För nu visar vi generisk info
setSessionInfo({
deviceName: 'Webbbläsare',
location: 'Sverige',
browser: 'Chrome/Safari',
})
} catch (e) {
console.error('Kunde inte hämta session info:', e)
}
}
const handleApprove = async () => {
setLoading(true)
try {
// Skapa signatur (förenklad för MVP)
const timestamp = new Date().toISOString()
const signature = await createSignature(sessionId, requestToken, timestamp)
const res = await fetch('https://api.quixzoom.com/v1/auth/passwordless/approve', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await getToken()}`,
},
body: JSON.stringify({
session_id: sessionId,
request_token: requestToken,
signature,
timestamp,
approving_device_id: await getDeviceId(),
}),
})
if (!res.ok) {
const err = await res.json()
throw new Error(err.detail || 'Kunde inte godkänna')
}
Alert.alert(
'✅ Inloggning godkänd',
'Du är nu inloggad på webben.',
[{ text: 'OK', onPress: onComplete }]
)
} catch (e: any) {
Alert.alert('Fel', e.message || 'Något gick fel')
} finally {
setLoading(false)
}
}
const handleReject = async () => {
try {
await fetch('https://api.quixzoom.com/v1/auth/passwordless/reject', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await getToken()}`,
},
body: JSON.stringify({
session_id: sessionId,
request_token: requestToken,
device_id: await getDeviceId(),
}),
})
} catch (e) {
// Ignorera fel vid nekad
}
onCancel()
}
return (
<View style={styles.container}>
<View style={styles.card}>
<Text style={styles.icon}>🔐</Text>
<Text style={styles.title}>Inloggningsförfrågan</Text>
<Text style={styles.subtitle}>
Försöker du logga in quixzoom.se?
</Text>
<View style={styles.infoBox}>
<Text style={styles.infoText}>📱 Enhet: {sessionInfo.deviceName || 'Okänd'}</Text>
<Text style={styles.infoText}>🌐 Webbläsare: {sessionInfo.browser || 'Okänd'}</Text>
<Text style={styles.infoText}>📍 Plats: {sessionInfo.location || 'Okänd'}</Text>
</View>
<Text style={styles.warning}>
Godkänn endast om du själv försöker logga in.
</Text>
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.button, styles.rejectButton]}
onPress={handleReject}
disabled={loading}
>
<Text style={styles.rejectButtonText}>Nej, neka</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.approveButton]}
onPress={handleApprove}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.approveButtonText}>Ja, godkänn</Text>
)}
</TouchableOpacity>
</View>
</View>
</View>
)
}
// Hjälpfunktioner
async function getToken(): Promise<string | null> {
// Använd samma token-hantering som i api.ts
const { getToken } = await import('../../../lib/api')
return getToken()
}
async function getDeviceId(): Promise<string> {
// TODO: Implementera enhets-ID-hantering
return 'device_unknown'
}
async function createSignature(
sessionId: string,
requestToken: string,
timestamp: string
): Promise<string> {
// TODO: Implementera riktig kryptografisk signatur
// För MVP använder vi en enkel hash
const data = `${sessionId}:${requestToken}:${timestamp}`
// I produktion: HMAC-SHA256 med enhets-privat-nyckel
return btoa(data)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#0A0A1B',
justifyContent: 'center',
padding: 24,
},
card: {
backgroundColor: 'rgba(255,255,255,0.05)',
borderRadius: 16,
padding: 32,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
},
icon: {
fontSize: 48,
textAlign: 'center',
marginBottom: 16,
},
title: {
fontSize: 24,
fontWeight: '800',
color: '#fff',
textAlign: 'center',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: 'rgba(255,255,255,0.7)',
textAlign: 'center',
marginBottom: 24,
},
infoBox: {
backgroundColor: 'rgba(255,255,255,0.03)',
borderRadius: 12,
padding: 16,
marginBottom: 24,
},
infoText: {
fontSize: 14,
color: 'rgba(255,255,255,0.6)',
marginBottom: 8,
},
warning: {
fontSize: 13,
color: 'rgba(255,200,100,0.8)',
textAlign: 'center',
marginBottom: 24,
fontStyle: 'italic',
},
buttonRow: {
flexDirection: 'row',
gap: 12,
},
button: {
flex: 1,
padding: 16,
borderRadius: 12,
alignItems: 'center',
},
rejectButton: {
backgroundColor: 'rgba(255,255,255,0.1)',
},
rejectButtonText: {
color: 'rgba(255,255,255,0.8)',
fontSize: 16,
fontWeight: '600',
},
approveButton: {
backgroundColor: '#0066FF',
},
approveButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '700',
},
})
@@ -0,0 +1,7 @@
/**
* Auth features — exports all auth-related components and hooks
*/
export { AuthScreen } from './AuthScreen'
export { PasswordlessApprovalScreen } from './PasswordlessApprovalScreen'
export { usePasswordlessAuth } from './usePasswordlessAuth'
export type { AuthRequest } from './usePasswordlessAuth'
@@ -0,0 +1,178 @@
/**
* usePasswordlessAuth — hanterar push-notifieringar och deep links
* för passwordless cross-device authentication
*/
import { useState, useEffect, useCallback, useRef } from 'react'
import { Platform } from 'react-native'
import * as Notifications from 'expo-notifications'
import * as Linking from 'expo-linking'
import type { Subscription } from 'expo-notifications'
import type { EventType } from 'expo-linking'
import { passwordless } from '../../../lib/api'
export interface AuthRequest {
requestToken: string
deviceInfo?: {
name?: string
browser?: string
type?: string
}
}
export function usePasswordlessAuth(
onRequest: (request: AuthRequest) => void,
onApproved: () => void,
onDenied: () => void,
) {
const [pushToken, setPushToken] = useState<string | null>(null)
const notificationListener = useRef<Subscription | null>(null)
const responseListener = useRef<Subscription | null>(null)
const urlListener = useRef<{ remove: () => void } | null>(null)
// Register for push notifications
useEffect(() => {
registerForPushNotifications()
return () => {
if (notificationListener.current) {
Notifications.removeNotificationSubscription(notificationListener.current)
}
if (responseListener.current) {
Notifications.removeNotificationSubscription(responseListener.current)
}
if (urlListener.current) {
urlListener.current.remove()
}
}
}, [])
// Listen for incoming notifications
useEffect(() => {
// Foreground notification received
notificationListener.current = Notifications.addNotificationReceivedListener(
(notification: Notifications.Notification) => {
handleNotification(notification.request.content.data as Record<string, unknown>)
},
)
// Notification tapped
responseListener.current = Notifications.addNotificationResponseReceivedListener(
(response: Notifications.NotificationResponse) => {
handleNotification(response.notification.request.content.data as Record<string, unknown>)
},
)
// Deep links
urlListener.current = Linking.addEventListener('url', (event: EventType) => {
handleDeepLink(event.url)
})
// Check initial URL (app opened via deep link)
Linking.getInitialURL().then((url: string | null) => {
if (url) handleDeepLink(url)
})
return () => {
if (notificationListener.current) {
Notifications.removeNotificationSubscription(notificationListener.current)
}
if (responseListener.current) {
Notifications.removeNotificationSubscription(responseListener.current)
}
if (urlListener.current) {
urlListener.current.remove()
}
}
}, [onRequest, onApproved, onDenied])
const handleNotification = useCallback(
(data: Record<string, unknown> | undefined) => {
if (!data) return
if (data.type === 'passwordless_auth') {
const requestToken = data.request_token as string
const deviceInfo = data.device_info as AuthRequest['deviceInfo'] | undefined
if (requestToken) {
onRequest({ requestToken, deviceInfo })
}
}
if (data.type === 'passwordless_approved') {
onApproved()
}
if (data.type === 'passwordless_denied') {
onDenied()
}
},
[onRequest, onApproved, onDenied],
)
const handleDeepLink = useCallback(
(url: string) => {
const parsed = Linking.parse(url)
if (parsed.scheme === 'quixzoom' && parsed.path === 'auth/approve') {
const requestToken = parsed.queryParams?.token as string | undefined
const deviceName = parsed.queryParams?.device as string | undefined
if (requestToken) {
onRequest({
requestToken,
deviceInfo: {
name: deviceName || 'quixzoom.se',
type: 'web',
},
})
}
}
},
[onRequest],
)
const registerForPushNotifications = async () => {
try {
const { status: existingStatus } = await Notifications.getPermissionsAsync()
let finalStatus = existingStatus
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync()
finalStatus = status
}
if (finalStatus !== 'granted') {
console.log('[usePasswordlessAuth] Push permission not granted')
return
}
const token = await Notifications.getExpoPushTokenAsync({
projectId: 'quixzoom',
})
setPushToken(token.data)
// Register push token with backend
await registerPushToken(token.data)
} catch (error) {
console.error('[usePasswordlessAuth] Failed to register push:', error)
}
}
const registerPushToken = async (token: string) => {
try {
await fetch('https://api.quixzoom.com/auth/push-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token,
platform: Platform.OS,
}),
})
} catch (e) {
console.error('[usePasswordlessAuth] Failed to register push token:', e)
}
}
return { pushToken }
}
+5
View File
@@ -0,0 +1,5 @@
/**
* Hooks barrel export
*/
export { useLiveLocation } from './useLiveLocation'
export { useHeading } from './useHeading'
+82
View File
@@ -0,0 +1,82 @@
/**
* useHeading.ts — React Hook for compass heading updates
* Uses watchHeadingAsync with headingFilter=1°
* Handles calibration state
*/
import { useEffect, useRef, useCallback } from 'react'
import * as Location from 'expo-location'
import { useLocationStore } from '../stores/locationStore'
const HEADING_FILTER = 1 // Update on every degree change
export function useHeading() {
const watchSubscription = useRef<Location.LocationSubscription | null>(null)
const { setHeading } = useLocationStore()
const startWatching = useCallback(async () => {
if (watchSubscription.current) {
console.log('[useHeading] Already watching, skipping start')
return
}
try {
// Check if compass/heading is available
const hasServices = await Location.hasServicesEnabledAsync()
if (!hasServices) {
console.log('[useHeading] Location services not enabled')
return
}
const { status } = await Location.requestForegroundPermissionsAsync()
if (status !== 'granted') {
console.log('[useHeading] Location permission denied')
return
}
watchSubscription.current = await Location.watchHeadingAsync(
(headingData) => {
// Use trueHeading if available, fallback to magneticHeading
const degrees = headingData.trueHeading >= 0
? headingData.trueHeading
: headingData.magHeading
const accuracy = headingData.accuracy
setHeading(degrees, accuracy)
// Log calibration state
if (headingData.accuracy < 0) {
console.log('[useHeading] Compass calibration needed')
}
}
)
console.log('[useHeading] watchHeadingAsync started with filter=1°')
} catch (error) {
console.error('[useHeading] Error starting heading watch:', error)
}
}, [setHeading])
const stopWatching = useCallback(() => {
if (watchSubscription.current) {
watchSubscription.current.remove()
watchSubscription.current = null
console.log('[useHeading] watchHeadingAsync stopped')
}
}, [])
// Auto-start on mount, stop on unmount
useEffect(() => {
startWatching()
return () => {
stopWatching()
}
}, [startWatching, stopWatching])
return {
startWatching,
stopWatching,
}
}
@@ -0,0 +1,132 @@
/**
* useLiveLocation.ts — React Hook for continuous GPS tracking
* Mission-grade config: BestForNavigation, distanceFilter=0, no pausing
* Includes stale-data rejection (reject positions older than 5s, accuracy > 20m)
*/
import { useEffect, useRef, useCallback } from 'react'
import * as Location from 'expo-location'
import { useLocationStore } from '../stores/locationStore'
const STALE_AGE_MS = 5000 // Reject positions older than 5 seconds
const MAX_ACCURACY_M = 20 // Reject positions with accuracy > 20m during mission
const MIN_ACCURACY_M = 150 // Reject positions with accuracy > 150m always
export function useLiveLocation() {
const watchSubscription = useRef<Location.LocationSubscription | null>(null)
const staleTimer = useRef<ReturnType<typeof setInterval> | null>(null)
const { isTracking, startTracking, stopTracking, updatePosition, markStale } = useLocationStore()
const validatePosition = useCallback((location: Location.LocationObject): boolean => {
const now = Date.now()
const age = now - location.timestamp
// Reject stale positions
if (age > STALE_AGE_MS) {
console.log(`[useLiveLocation] Rejected stale position: age=${age}ms > ${STALE_AGE_MS}ms`)
return false
}
// Reject low accuracy positions
const accuracy = location.coords.accuracy
if (accuracy == null) {
console.log('[useLiveLocation] Rejected: no accuracy data')
return false
}
if (accuracy > MIN_ACCURACY_M) {
console.log(`[useLiveLocation] Rejected poor accuracy: ${accuracy}m > ${MIN_ACCURACY_M}m`)
return false
}
// Warn if accuracy is mission-suboptimal but still acceptable
if (accuracy > MAX_ACCURACY_M) {
console.log(`[useLiveLocation] Warning: accuracy ${accuracy}m > mission-grade ${MAX_ACCURACY_M}m`)
// Still accept it — don't reject, just warn
}
return true
}, [])
const startWatching = useCallback(async () => {
if (watchSubscription.current) {
console.log('[useLiveLocation] Already watching, skipping start')
return
}
try {
// Request permissions
const { status: foregroundStatus } = await Location.requestForegroundPermissionsAsync()
if (foregroundStatus !== 'granted') {
console.log('[useLiveLocation] Foreground location permission denied')
useLocationStore.getState().setLocationAuthStatus('denied')
return
}
// Request background permission for iOS
if (foregroundStatus === 'granted') {
const { status: backgroundStatus } = await Location.requestBackgroundPermissionsAsync()
useLocationStore.getState().setLocationAuthStatus(backgroundStatus)
}
// Mission-grade location config
const options: Location.LocationOptions = {
accuracy: Location.Accuracy.BestForNavigation,
distanceInterval: 0, // Update on every movement
timeInterval: 1000, // At least every 1 second
mayShowUserSettingsDialog: true,
}
watchSubscription.current = await Location.watchPositionAsync(
options,
(location) => {
if (validatePosition(location)) {
updatePosition(location)
}
}
)
startTracking()
console.log('[useLiveLocation] watchPositionAsync started with BestForNavigation')
// Start stale checker
staleTimer.current = setInterval(() => {
markStale()
}, 2000)
} catch (error) {
console.error('[useLiveLocation] Error starting location watch:', error)
useLocationStore.getState().setLocationAuthStatus('error')
}
}, [validatePosition, startTracking, updatePosition, markStale])
const stopWatching = useCallback(() => {
if (watchSubscription.current) {
watchSubscription.current.remove()
watchSubscription.current = null
console.log('[useLiveLocation] watchPositionAsync stopped')
}
if (staleTimer.current) {
clearInterval(staleTimer.current)
staleTimer.current = null
}
stopTracking()
}, [stopTracking])
// Auto-start on mount, stop on unmount
useEffect(() => {
startWatching()
return () => {
stopWatching()
}
}, [startWatching, stopWatching])
return {
isTracking,
startWatching,
stopWatching,
}
}
@@ -0,0 +1,244 @@
/**
* AppNavigator — Root navigation with location permission request at startup
* Starts location tracking when user is authenticated
* Includes passwordless cross-device auth support
*/
import { useEffect, useState, useCallback } from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { Text, View, ActivityIndicator, Alert } from 'react-native'
import * as Location from 'expo-location'
import * as Notifications from 'expo-notifications'
import { getToken, auth } from '../../lib/api'
import { HomeScreen } from '../../screens/HomeScreen'
import { MissionsScreen } from '../../screens/MissionsScreen'
import { MissionDetailScreen } from '../../screens/MissionDetailScreen'
import { EarningsScreen } from '../../screens/EarningsScreen'
import { ProfileScreen } from '../../screens/ProfileScreen'
import { AuthScreen } from '../features/auth/AuthScreen'
import { PasswordlessApprovalScreen } from '../features/auth/PasswordlessApprovalScreen'
import { usePasswordlessAuth } from '../features/auth/usePasswordlessAuth'
import { useLocationStore } from '../stores/locationStore'
const Tab = createBottomTabNavigator()
const Stack = createNativeStackNavigator()
function MissionsStack() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="MissionsList" component={MissionsScreen} />
<Stack.Screen name="MissionDetail" component={MissionDetailScreen} />
</Stack.Navigator>
)
}
function MainTabs() {
return (
<Tab.Navigator
screenOptions={{
tabBarStyle: {
backgroundColor: '#0A0A1B',
borderTopColor: 'rgba(255,255,255,0.08)',
},
tabBarActiveTintColor: '#6366f1',
tabBarInactiveTintColor: 'rgba(255,255,255,0.35)',
headerShown: false,
}}
>
<Tab.Screen
name="Karta"
component={HomeScreen}
options={{
tabBarIcon: ({ color }) => <Text style={{ fontSize: 20, color }}>🗺</Text>,
}}
/>
<Tab.Screen
name="Uppdrag"
component={MissionsStack}
options={{
tabBarIcon: ({ color }) => <Text style={{ fontSize: 20, color }}>🎯</Text>,
}}
/>
<Tab.Screen
name="Intjänat"
component={EarningsScreen}
options={{
tabBarIcon: ({ color }) => <Text style={{ fontSize: 20, color }}>💰</Text>,
}}
/>
<Tab.Screen
name="Profil"
component={ProfileScreen}
options={{
tabBarIcon: ({ color }) => <Text style={{ fontSize: 20, color }}>👤</Text>,
}}
/>
</Tab.Navigator>
)
}
// Configure notification handler
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
})
export function AppNavigator() {
const [isLoggedIn, setIsLoggedIn] = useState<boolean | null>(null)
const [authRequest, setAuthRequest] = useState<{
requestToken: string
deviceInfo?: { name?: string; browser?: string; type?: string }
} | null>(null)
const { setLocationAuthStatus } = useLocationStore()
const handleAuthRequest = useCallback(
(request: { requestToken: string; deviceInfo?: { name?: string; browser?: string; type?: string } }) => {
setAuthRequest(request)
},
[],
)
const handleApproved = useCallback(() => {
setAuthRequest(null)
Alert.alert('✅ Inloggning godkänd', 'Användaren kan nu logga in på webben.')
}, [])
const handleDenied = useCallback(() => {
setAuthRequest(null)
Alert.alert('❌ Inloggning nekad', 'Förfrågan avvisad.')
}, [])
const handleClose = useCallback(() => {
setAuthRequest(null)
}, [])
// Initialize passwordless auth hooks
usePasswordlessAuth(handleAuthRequest, handleApproved, handleDenied)
useEffect(() => {
checkAuth()
requestLocationPermissions()
configureNotifications()
}, [])
const checkAuth = async () => {
const token = await getToken()
if (token) {
// Validate token by calling /auth/me
try {
await auth.me()
setIsLoggedIn(true)
} catch {
setIsLoggedIn(false)
}
} else {
setIsLoggedIn(false)
}
}
const requestLocationPermissions = async () => {
try {
// Check if location services are enabled
const servicesEnabled = await Location.hasServicesEnabledAsync()
if (!servicesEnabled) {
console.log('[AppNavigator] Location services not enabled')
setLocationAuthStatus('services_disabled')
return
}
// Request foreground permission
const { status: foregroundStatus } = await Location.requestForegroundPermissionsAsync()
console.log(`[AppNavigator] Foreground permission: ${foregroundStatus}`)
setLocationAuthStatus(foregroundStatus)
if (foregroundStatus !== 'granted') {
Alert.alert(
'GPS-behörighet krävs',
'quiXzoom behöver åtkomst till din plats för att visa uppdrag nära dig och navigera till uppdragsplatser.',
[
{ text: 'OK', style: 'default' },
]
)
return
}
// Request background permission (iOS)
const { status: backgroundStatus } = await Location.requestBackgroundPermissionsAsync()
console.log(`[AppNavigator] Background permission: ${backgroundStatus}`)
if (backgroundStatus === 'granted') {
setLocationAuthStatus('granted')
}
} catch (error) {
console.error('[AppNavigator] Error requesting location permissions:', error)
setLocationAuthStatus('error')
}
}
const configureNotifications = async () => {
try {
// Set notification categories for auth requests
await Notifications.setNotificationCategoryAsync('passwordless_auth', [
{
identifier: 'approve',
buttonTitle: 'Godkänn',
options: { isDestructive: false },
},
{
identifier: 'deny',
buttonTitle: 'Neka',
options: { isDestructive: true },
},
])
} catch (error) {
console.error('[AppNavigator] Error configuring notifications:', error)
}
}
if (isLoggedIn === null) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#0A0A1B' }}>
<ActivityIndicator color="#6366f1" size="large" />
</View>
)
}
if (!isLoggedIn) {
return (
<View style={{ flex: 1 }}>
<AuthScreen onLoginSuccess={() => setIsLoggedIn(true)} />
{authRequest && (
<PasswordlessApprovalScreen
requestToken={authRequest.requestToken}
deviceInfo={authRequest.deviceInfo}
onApproved={handleApproved}
onDenied={handleDenied}
onClose={handleClose}
/>
)}
</View>
)
}
return (
<NavigationContainer>
<View style={{ flex: 1 }}>
<MainTabs />
{authRequest && (
<PasswordlessApprovalScreen
requestToken={authRequest.requestToken}
deviceInfo={authRequest.deviceInfo}
onApproved={handleApproved}
onDenied={handleDenied}
onClose={handleClose}
/>
)}
</View>
</NavigationContainer>
)
}
@@ -0,0 +1,8 @@
/**
* LEGACY: This file previously contained the Supabase client.
* Auth has been migrated to identity-core JWT via lib/api.ts
*
* Re-exports from lib/api.ts for backwards compatibility.
* Remove this file once all src/features/* screens have been updated.
*/
export { getToken, setToken, clearTokens, auth } from '../../lib/api'
+5
View File
@@ -0,0 +1,5 @@
/**
* Stores barrel export
*/
export { useLocationStore } from './locationStore'
export type { LocationState } from './locationStore'
@@ -0,0 +1,142 @@
/**
* locationStore.ts — Zustand store for live GPS location
* Holds current position, heading, accuracy, speed, tracking state
*/
import { create } from 'zustand'
import type { LocationObject } from 'expo-location'
export interface LocationState {
// Position data
currentPosition: LocationObject | null
heading: number | null
headingAccuracy: number | null
accuracy: number | null
speed: number | null
course: number | null
// Tracking state
isTracking: boolean
isStale: boolean
lastUpdateTimestamp: number | null
updateIntervalMs: number | null
// Camera / UI state
followUser: boolean
userPannedMap: boolean
// Auth status
locationAuthStatus: string | null
// Actions
startTracking: () => void
stopTracking: () => void
updatePosition: (position: LocationObject) => void
setHeading: (heading: number, accuracy?: number) => void
setFollowUser: (follow: boolean) => void
setUserPannedMap: (panned: boolean) => void
setLocationAuthStatus: (status: string) => void
markStale: () => void
}
const STALE_THRESHOLD_MS = 5000 // 5 seconds
export const useLocationStore = create<LocationState>((set, get) => ({
// Initial state
currentPosition: null,
heading: null,
headingAccuracy: null,
accuracy: null,
speed: null,
course: null,
isTracking: false,
isStale: false,
lastUpdateTimestamp: null,
updateIntervalMs: null,
followUser: true,
userPannedMap: false,
locationAuthStatus: null,
startTracking: () => {
set({ isTracking: true, isStale: false })
console.log('[LocationStore] Tracking started')
},
stopTracking: () => {
set({
isTracking: false,
isStale: true,
currentPosition: null,
heading: null,
speed: null,
course: null,
})
console.log('[LocationStore] Tracking stopped')
},
updatePosition: (position: LocationObject) => {
const now = Date.now()
const prevTimestamp = get().lastUpdateTimestamp
const interval = prevTimestamp ? now - prevTimestamp : null
set({
currentPosition: position,
accuracy: position.coords.accuracy ?? null,
speed: position.coords.speed ?? null,
course: position.coords.course ?? null,
lastUpdateTimestamp: now,
updateIntervalMs: interval,
isStale: false,
})
console.log(
`[LocationStore] Position updated: lat=${position.coords.latitude.toFixed(6)}, ` +
`lng=${position.coords.longitude.toFixed(6)}, accuracy=${position.coords.accuracy}m, ` +
`speed=${position.coords.speed ?? 'null'}m/s, interval=${interval ?? 'first'}ms`
)
},
setHeading: (heading: number, accuracy?: number) => {
set({
heading,
headingAccuracy: accuracy ?? null,
})
console.log(`[LocationStore] Heading updated: ${heading.toFixed(1)}° (accuracy: ${accuracy ?? 'unknown'})`)
},
setFollowUser: (follow: boolean) => {
set({ followUser: follow })
if (follow) {
set({ userPannedMap: false })
}
},
setUserPannedMap: (panned: boolean) => {
set({ userPannedMap: panned })
if (panned) {
set({ followUser: false })
}
},
setLocationAuthStatus: (status: string) => {
set({ locationAuthStatus: status })
console.log(`[LocationStore] Auth status: ${status}`)
},
markStale: () => {
const { lastUpdateTimestamp } = get()
if (lastUpdateTimestamp && Date.now() - lastUpdateTimestamp > STALE_THRESHOLD_MS) {
set({ isStale: true })
console.log('[LocationStore] Position marked stale')
}
},
}))
// Selector hooks for performance
export const selectCurrentPosition = (state: LocationState) => state.currentPosition
export const selectIsTracking = (state: LocationState) => state.isTracking
export const selectIsStale = (state: LocationState) => state.isStale
export const selectHeading = (state: LocationState) => state.heading
export const selectAccuracy = (state: LocationState) => state.accuracy
export const selectSpeed = (state: LocationState) => state.speed
export const selectFollowUser = (state: LocationState) => state.followUser
export const selectLocationAuthStatus = (state: LocationState) => state.locationAuthStatus
+16
View File
@@ -0,0 +1,16 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.d.ts",
"expo-env.d.ts"
]
}
+16
View File
@@ -0,0 +1,16 @@
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application
COPY . .
# Expose port
EXPOSE 8080
# Run
CMD ["python", "main.py"]
@@ -0,0 +1,45 @@
version: '3.8'
services:
passwordless-auth:
build: .
container_name: quixzoom-passwordless
ports:
- "8080:8080"
environment:
- PORT=8080
- HOST=0.0.0.0
- REDIS_HOST=redis
- REDIS_PORT=6379
- REDIS_DB=0
- JWT_SECRET=${JWT_SECRET}
depends_on:
- redis
networks:
- quixzoom-auth
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
redis:
image: redis:7-alpine
container_name: quixzoom-auth-redis
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- quixzoom-auth
restart: unless-stopped
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
networks:
quixzoom-auth:
driver: bridge
volumes:
redis-data:
+55
View File
@@ -0,0 +1,55 @@
"""
quiXzoom Passwordless Authentication Service
Cross-device login between app and web
"""
import os
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import uvicorn
from routes.passwordless import router as passwordless_router
app = FastAPI(
title="quiXzoom Passwordless Auth",
description="Passwordless cross-device authentication service",
version="1.0.0"
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://quixzoom.se",
"https://www.quixzoom.se",
"https://quixzoom.com",
"https://www.quixzoom.com",
"http://localhost:3000",
"http://localhost:8080",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Error handler
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
return JSONResponse(
status_code=500,
content={"error": "Internal server error", "detail": str(exc)}
)
# Health check
@app.get("/health")
async def health():
return {"status": "ok", "service": "passwordless-auth"}
# Include routes
app.include_router(passwordless_router, prefix="/v1")
if __name__ == "__main__":
port = int(os.getenv('PORT', 8080))
host = os.getenv('HOST', '0.0.0.0')
uvicorn.run(app, host=host, port=port)
@@ -0,0 +1,21 @@
"""
QuixZoom Auth Service - Database Models
"""
from .device import Device, DeviceCreate, DeviceResponse
from .auth_request import AuthRequest, AuthRequestCreate, AuthRequestResponse, AuthRequestStatus
from .web_session import WebSession, WebSessionCreate, WebSessionResponse, WebSessionStatus
__all__ = [
"Device",
"DeviceCreate",
"DeviceResponse",
"AuthRequest",
"AuthRequestCreate",
"AuthRequestResponse",
"AuthRequestStatus",
"WebSession",
"WebSessionCreate",
"WebSessionResponse",
"WebSessionStatus",
]
@@ -0,0 +1,129 @@
"""
Auth Request model for passwordless cross-device authentication
Represents an ongoing authentication request between web and device
"""
from datetime import datetime
from typing import Optional
from enum import Enum
from pydantic import BaseModel, Field
import uuid
class AuthRequestStatus(str, Enum):
"""Authentication request status"""
PENDING = "pending" # Waiting for device approval
APPROVED = "approved" # Device approved, ready to complete
REJECTED = "rejected" # Device rejected
EXPIRED = "expired" # Timed out
COMPLETED = "completed" # Successfully completed
CANCELLED = "cancelled" # Cancelled by user
class AuthRequest(BaseModel):
"""Authentication request database model"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
# Request identifiers
request_token: str = Field(..., description="Unique request token (nonce)")
qr_code_data: str = Field(..., description="Data encoded in QR code")
# User and device
user_id: Optional[str] = Field(None, description="User ID if known")
device_id: Optional[str] = Field(None, description="Device that approved/rejected")
# Status tracking
status: AuthRequestStatus = Field(default=AuthRequestStatus.PENDING)
# Security
nonce: str = Field(..., description="Cryptographic nonce for this request")
challenge: str = Field(..., description="Challenge to be signed by device")
challenge_response: Optional[str] = Field(None, description="Device's signed response")
# Device fingerprint at time of request
web_fingerprint: Optional[str] = Field(None, description="Browser fingerprint")
device_fingerprint: Optional[str] = Field(None, description="Device fingerprint at approval")
# IP and location info
web_ip: Optional[str] = Field(None, description="Web client IP")
device_ip: Optional[str] = Field(None, description="Device IP at approval")
# Metadata
web_user_agent: Optional[str] = Field(None)
device_user_agent: Optional[str] = Field(None)
# Timestamps
created_at: datetime = Field(default_factory=datetime.utcnow)
expires_at: datetime = Field(..., description="Request expiration time")
approved_at: Optional[datetime] = Field(None)
completed_at: Optional[datetime] = Field(None)
# Result
session_token: Optional[str] = Field(None, description="Session token after completion")
error_message: Optional[str] = Field(None)
class Config:
json_schema_extra = {
"example": {
"id": "auth_req_123",
"request_token": "tkn_abc123",
"qr_code_data": "quixzoom://auth?token=tkn_abc123&nonce=nonce123",
"status": "pending",
"nonce": "nonce123",
"challenge": "ch_abc123...",
"created_at": "2024-01-15T10:30:00Z",
"expires_at": "2024-01-15T10:35:00Z"
}
}
class AuthRequestCreate(BaseModel):
"""Request model for initiating authentication"""
user_id: Optional[str] = Field(None, description="User ID if pre-authenticated")
device_fingerprint: Optional[str] = Field(None, description="Browser fingerprint")
user_agent: Optional[str] = Field(None)
ip_address: Optional[str] = Field(None)
class AuthRequestApprove(BaseModel):
"""Request model for device approval"""
request_token: str = Field(..., description="Token from QR code")
device_id: str = Field(..., description="Registered device ID")
challenge_response: str = Field(..., description="Signed challenge response")
device_fingerprint: str = Field(..., description="Current device fingerprint")
user_agent: Optional[str] = Field(None)
ip_address: Optional[str] = Field(None)
approved: bool = Field(default=True, description="Approve or reject")
class AuthRequestResponse(BaseModel):
"""Response model for auth request"""
id: str
request_token: str
qr_code_data: str
status: AuthRequestStatus
user_id: Optional[str] = None
device_id: Optional[str] = None
created_at: datetime
expires_at: datetime
approved_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
class Config:
from_attributes = True
class AuthRequestStatusResponse(BaseModel):
"""Response model for polling auth status"""
status: AuthRequestStatus
request_token: str
device_name: Optional[str] = None
device_platform: Optional[str] = None
approved_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
session_token: Optional[str] = None
error_message: Optional[str] = None
expires_at: datetime
class Config:
from_attributes = True
+101
View File
@@ -0,0 +1,101 @@
"""
Device model for passwordless authentication
Represents a registered mobile device (app)
"""
from datetime import datetime
from typing import Optional
from enum import Enum
from pydantic import BaseModel, Field
import uuid
class DevicePlatform(str, Enum):
"""Supported device platforms"""
IOS = "ios"
ANDROID = "android"
WEB = "web"
OTHER = "other"
class DeviceStatus(str, Enum):
"""Device status"""
ACTIVE = "active"
REVOKED = "revoked"
SUSPENDED = "suspended"
class Device(BaseModel):
"""Device database model"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
user_id: str = Field(..., description="Associated user ID")
device_name: str = Field(..., description="Human-readable device name")
platform: DevicePlatform = Field(..., description="Device platform")
# Device fingerprinting
public_key: str = Field(..., description="Device public key for challenge signing")
device_fingerprint: str = Field(..., description="Unique device fingerprint hash")
# Metadata
push_token: Optional[str] = Field(None, description="Push notification token")
last_ip: Optional[str] = Field(None, description="Last known IP address")
last_user_agent: Optional[str] = Field(None, description="Last user agent")
# Status
status: DeviceStatus = Field(default=DeviceStatus.ACTIVE)
# Timestamps
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
last_used_at: Optional[datetime] = Field(None)
class Config:
json_schema_extra = {
"example": {
"id": "dev_123456789",
"user_id": "user_abc123",
"device_name": "iPhone 15 Pro",
"platform": "ios",
"public_key": "-----BEGIN PUBLIC KEY-----\n...",
"device_fingerprint": "sha256:abc123...",
"status": "active",
"created_at": "2024-01-15T10:30:00Z"
}
}
class DeviceCreate(BaseModel):
"""Request model for device registration"""
user_id: str = Field(..., description="User ID to associate with device")
device_name: str = Field(..., min_length=1, max_length=100)
platform: DevicePlatform
public_key: str = Field(..., min_length=100, description="Device public key")
device_fingerprint: str = Field(..., min_length=32, description="Device fingerprint")
push_token: Optional[str] = Field(None, max_length=500)
class DeviceUpdate(BaseModel):
"""Request model for device update"""
device_name: Optional[str] = Field(None, min_length=1, max_length=100)
push_token: Optional[str] = Field(None, max_length=500)
status: Optional[DeviceStatus] = None
class DeviceResponse(BaseModel):
"""Response model for device operations"""
id: str
user_id: str
device_name: str
platform: DevicePlatform
status: DeviceStatus
created_at: datetime
last_used_at: Optional[datetime] = None
class Config:
from_attributes = True
class DeviceListResponse(BaseModel):
"""Response model for listing devices"""
devices: list[DeviceResponse]
total: int
@@ -0,0 +1,120 @@
"""
Web Session model for passwordless authentication
Represents an authenticated web session after device approval
"""
from datetime import datetime
from typing import Optional, Dict, Any
from enum import Enum
from pydantic import BaseModel, Field
import uuid
class WebSessionStatus(str, Enum):
"""Web session status"""
ACTIVE = "active"
EXPIRED = "expired"
LOGGED_OUT = "logged_out"
REVOKED = "revoked"
class WebSession(BaseModel):
"""Web session database model"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
# Session identifiers
session_token: str = Field(..., description="JWT session token")
refresh_token: str = Field(..., description="Refresh token for session renewal")
# User info
user_id: str = Field(..., description="Authenticated user ID")
auth_request_id: str = Field(..., description="Reference to auth request")
device_id: str = Field(..., description="Device that approved the login")
# Status
status: WebSessionStatus = Field(default=WebSessionStatus.ACTIVE)
# Security context
ip_address: Optional[str] = Field(None)
user_agent: Optional[str] = Field(None)
browser_fingerprint: Optional[str] = Field(None)
device_fingerprint: Optional[str] = Field(None)
# Session metadata
metadata: Dict[str, Any] = Field(default_factory=dict)
# Timestamps
created_at: datetime = Field(default_factory=datetime.utcnow)
expires_at: datetime = Field(..., description="Session expiration")
last_activity_at: datetime = Field(default_factory=datetime.utcnow)
logged_out_at: Optional[datetime] = Field(None)
class Config:
json_schema_extra = {
"example": {
"id": "ws_123456",
"session_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "rt_abc123...",
"user_id": "user_abc123",
"auth_request_id": "auth_req_123",
"device_id": "dev_123456789",
"status": "active",
"created_at": "2024-01-15T10:30:00Z",
"expires_at": "2024-01-15T18:30:00Z",
"last_activity_at": "2024-01-15T10:35:00Z"
}
}
class WebSessionCreate(BaseModel):
"""Request model for creating web session"""
user_id: str
auth_request_id: str
device_id: str
ip_address: Optional[str] = None
user_agent: Optional[str] = None
browser_fingerprint: Optional[str] = None
device_fingerprint: Optional[str] = None
metadata: Dict[str, Any] = Field(default_factory=dict)
class WebSessionResponse(BaseModel):
"""Response model for web session"""
id: str
user_id: str
device_id: str
status: WebSessionStatus
created_at: datetime
expires_at: datetime
last_activity_at: datetime
class Config:
from_attributes = True
class WebSessionTokenResponse(BaseModel):
"""Response model for session tokens"""
access_token: str = Field(..., description="JWT access token")
refresh_token: str = Field(..., description="Refresh token")
token_type: str = Field(default="Bearer")
expires_in: int = Field(..., description="Token expiration in seconds")
session_id: str
class WebSessionCompleteRequest(BaseModel):
"""Request model for completing web session"""
request_token: str = Field(..., description="Auth request token")
browser_fingerprint: Optional[str] = Field(None)
user_agent: Optional[str] = Field(None)
ip_address: Optional[str] = Field(None)
class WebSessionRefreshRequest(BaseModel):
"""Request model for refreshing session"""
refresh_token: str = Field(..., description="Valid refresh token")
class WebSessionLogoutRequest(BaseModel):
"""Request model for logging out"""
session_token: str = Field(..., description="Session token to invalidate")
all_sessions: bool = Field(default=False, description="Logout all user sessions")
@@ -0,0 +1,5 @@
fastapi>=0.104.0
uvicorn[standard]>=0.24.0
redis>=5.0.0
pydantic>=2.5.0
python-multipart>=0.0.6
@@ -0,0 +1,393 @@
"""
Passwordless authentication routes
Handles cross-device login between app and web
"""
import os
import time
import hashlib
import secrets
from datetime import datetime, timedelta
from typing import Optional
from fastapi import APIRouter, HTTPException, Header, Request, Depends
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
import redis
router = APIRouter(prefix="/auth/passwordless", tags=["passwordless"])
# Redis connection
redis_client = redis.Redis(
host=os.getenv('REDIS_HOST', 'localhost'),
port=int(os.getenv('REDIS_PORT', 6379)),
db=int(os.getenv('REDIS_DB', 0)),
decode_responses=True
)
# Constants
SESSION_TTL_SECONDS = 900 # 15 minutes
POLL_INTERVAL_MS = 2000
RATE_LIMIT_PER_IP = 10 # per minute
RATE_LIMIT_PER_USER = 5 # per minute
# ─── Request/Response Models ────────────────────────────────────────────────
class InitiateRequest(BaseModel):
client_id: str = Field(default="web-dashboard")
redirect_url: Optional[str] = Field(default="https://quixzoom.se/dashboard")
device_info: Optional[dict] = Field(default=None)
class InitiateResponse(BaseModel):
session_id: str
request_token: str
qr_data: str
expires_at: str
poll_interval_ms: int = POLL_INTERVAL_MS
class ApproveRequest(BaseModel):
session_id: str
request_token: str
signature: str
timestamp: str
approving_device_id: str
class ApproveResponse(BaseModel):
status: str
session_id: str
approved_at: str
class StatusResponse(BaseModel):
status: str
session_id: str
request_token: str
expires_at: str
approved_at: Optional[str] = None
completed_at: Optional[str] = None
access_token: Optional[str] = None
refresh_token: Optional[str] = None
error_message: Optional[str] = None
class RejectRequest(BaseModel):
session_id: str
request_token: str
device_id: str
# ─── Helper Functions ───────────────────────────────────────────────────────
def generate_session_id() -> str:
"""Generate unique session ID"""
return f"pls_{secrets.token_urlsafe(16)}"
def generate_request_token() -> str:
"""Generate cryptographically secure request token"""
return secrets.token_urlsafe(32)
def generate_nonce() -> str:
"""Generate nonce for challenge"""
return secrets.token_hex(16)
def get_client_ip(request: Request) -> str:
"""Extract client IP from request"""
forwarded = request.headers.get('X-Forwarded-For')
if forwarded:
return forwarded.split(',')[0].strip()
return request.client.host if request.client else "unknown"
def check_rate_limit(key: str, limit: int, window: int = 60) -> bool:
"""Check if rate limit is exceeded"""
current = redis_client.get(f"ratelimit:{key}")
if not current:
redis_client.setex(f"ratelimit:{key}", window, 1)
return True
count = int(current)
if count >= limit:
return False
redis_client.incr(f"ratelimit:{key}")
return True
def create_qr_data(session_id: str, request_token: str) -> str:
"""Create QR code data string"""
return f"quixzoom://auth?sid={session_id}&token={request_token}"
def hash_fingerprint(user_agent: str, ip: str) -> str:
"""Create hash of device fingerprint"""
data = f"{user_agent}:{ip}"
return hashlib.sha256(data.encode()).hexdigest()
# ─── Routes ─────────────────────────────────────────────────────────────────
@router.post("/initiate", response_model=InitiateResponse)
async def initiate_passwordless(
request: Request,
body: InitiateRequest
):
"""
Initiate a new passwordless authentication session.
Returns session details including QR code data.
"""
client_ip = get_client_ip(request)
user_agent = request.headers.get('User-Agent', 'unknown')
# Rate limiting
if not check_rate_limit(f"ip:{client_ip}", RATE_LIMIT_PER_IP):
raise HTTPException(
status_code=429,
detail="Too many requests. Please try again later."
)
# Generate session
session_id = generate_session_id()
request_token = generate_request_token()
nonce = generate_nonce()
challenge = secrets.token_urlsafe(32)
expires_at = datetime.utcnow() + timedelta(seconds=SESSION_TTL_SECONDS)
# Store in Redis
session_data = {
"session_id": session_id,
"request_token": request_token,
"nonce": nonce,
"challenge": challenge,
"status": "pending",
"client_id": body.client_id,
"redirect_url": body.redirect_url or "https://quixzoom.se/dashboard",
"web_ip": client_ip,
"web_user_agent": user_agent,
"web_fingerprint": hash_fingerprint(user_agent, client_ip),
"created_at": datetime.utcnow().isoformat(),
"expires_at": expires_at.isoformat(),
}
redis_key = f"passwordless:{session_id}"
redis_client.hset(redis_key, mapping=session_data)
redis_client.expire(redis_key, SESSION_TTL_SECONDS)
# Also store by request_token for lookup
redis_client.setex(
f"passwordless_token:{request_token}",
SESSION_TTL_SECONDS,
session_id
)
return InitiateResponse(
session_id=session_id,
request_token=request_token,
qr_data=create_qr_data(session_id, request_token),
expires_at=expires_at.isoformat() + "Z",
poll_interval_ms=POLL_INTERVAL_MS
)
@router.post("/approve", response_model=ApproveResponse)
async def approve_passwordless(
request: Request,
body: ApproveRequest,
authorization: Optional[str] = Header(None)
):
"""
Approve a passwordless authentication request from the app.
Requires valid app JWT in Authorization header.
"""
# Verify app JWT (simplified - integrate with your JWT validation)
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing or invalid authorization")
app_token = authorization.replace("Bearer ", "")
# TODO: Validate app_token against your JWT service
# For now, we'll do basic validation
if len(app_token) < 10:
raise HTTPException(status_code=401, detail="Invalid token")
# Look up session
redis_key = f"passwordless:{body.session_id}"
session_data = redis_client.hgetall(redis_key)
if not session_data:
raise HTTPException(status_code=410, detail="Session not found or expired")
# Verify request_token matches
if session_data.get("request_token") != body.request_token:
raise HTTPException(status_code=403, detail="Invalid request token")
# Check status
if session_data.get("status") != "pending":
raise HTTPException(
status_code=409,
detail=f"Session already {session_data.get('status')}"
)
# Verify timestamp is recent (within 5 minutes)
try:
timestamp = datetime.fromisoformat(body.timestamp.replace('Z', '+00:00'))
if datetime.utcnow() - timestamp > timedelta(minutes=5):
raise HTTPException(status_code=403, detail="Timestamp too old")
except ValueError:
raise HTTPException(status_code=400, detail="Invalid timestamp format")
# TODO: Verify signature
# expected_signature = hmac_sha256(session_id + request_token + timestamp, device_secret)
# For MVP, we'll skip signature verification and rely on JWT + request_token
# Update session
approved_at = datetime.utcnow()
redis_client.hset(redis_key, mapping={
"status": "approved",
"approved_at": approved_at.isoformat(),
"approving_device_id": body.approving_device_id,
"device_ip": get_client_ip(request),
"device_user_agent": request.headers.get('User-Agent', 'unknown'),
})
# Keep TTL but extend slightly for completion
redis_client.expire(redis_key, 300) # 5 more minutes
return ApproveResponse(
status="approved",
session_id=body.session_id,
approved_at=approved_at.isoformat() + "Z"
)
@router.get("/status")
async def check_status(
request: Request,
session_id: str,
request_token: str
):
"""
Check the status of a passwordless authentication session.
Called by web client via polling.
"""
redis_key = f"passwordless:{session_id}"
session_data = redis_client.hgetall(redis_key)
if not session_data:
raise HTTPException(status_code=404, detail="Session not found")
# Verify request_token
if session_data.get("request_token") != request_token:
raise HTTPException(status_code=403, detail="Invalid request token")
status = session_data.get("status", "unknown")
response = {
"status": status,
"session_id": session_id,
"request_token": request_token,
"expires_at": session_data.get("expires_at"),
}
if status == "approved":
response["approved_at"] = session_data.get("approved_at")
# Generate web session tokens
# TODO: Integrate with your JWT service to create proper tokens
user_id = "user_from_app_token" # Extract from app JWT
# Create web session
web_session_token = secrets.token_urlsafe(32)
refresh_token = secrets.token_urlsafe(32)
# Store web session
web_session_key = f"web_session:{web_session_token}"
redis_client.hset(web_session_key, mapping={
"user_id": user_id,
"session_id": session_id,
"created_at": datetime.utcnow().isoformat(),
"ip": get_client_ip(request),
"user_agent": request.headers.get('User-Agent', 'unknown'),
})
redis_client.expire(web_session_key, 86400) # 24 hours
# Mark session as completed
redis_client.hset(redis_key, mapping={
"status": "completed",
"completed_at": datetime.utcnow().isoformat(),
"web_session_token": web_session_token,
})
response.update({
"status": "completed",
"completed_at": datetime.utcnow().isoformat() + "Z",
"access_token": web_session_token,
"refresh_token": refresh_token,
})
elif status == "rejected":
response["error_message"] = "Authentication rejected by user"
elif status == "expired":
response["error_message"] = "Session expired"
return StatusResponse(**response)
@router.post("/reject")
async def reject_passwordless(
request: Request,
body: RejectRequest,
authorization: Optional[str] = Header(None)
):
"""
Reject a passwordless authentication request from the app.
"""
# Verify app JWT
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing authorization")
redis_key = f"passwordless:{body.session_id}"
session_data = redis_client.hgetall(redis_key)
if not session_data:
raise HTTPException(status_code=404, detail="Session not found")
if session_data.get("request_token") != body.request_token:
raise HTTPException(status_code=403, detail="Invalid request token")
redis_client.hset(redis_key, mapping={
"status": "rejected",
"rejected_at": datetime.utcnow().isoformat(),
"rejecting_device_id": body.device_id,
})
return {"status": "rejected", "session_id": body.session_id}
@router.post("/cancel")
async def cancel_passwordless(
session_id: str,
request_token: str
):
"""
Cancel a pending passwordless authentication request.
Called by web client.
"""
redis_key = f"passwordless:{session_id}"
session_data = redis_client.hgetall(redis_key)
if not session_data:
raise HTTPException(status_code=404, detail="Session not found")
if session_data.get("request_token") != request_token:
raise HTTPException(status_code=403, detail="Invalid request token")
if session_data.get("status") != "pending":
raise HTTPException(status_code=409, detail="Session already processed")
redis_client.hset(redis_key, mapping={
"status": "cancelled",
"cancelled_at": datetime.utcnow().isoformat(),
})
return {"status": "cancelled", "session_id": session_id}
@@ -0,0 +1,13 @@
"""
QuixZoom Auth Service - Services
"""
from .device_service import DeviceService
from .auth_service import AuthService
from .session_service import SessionService
__all__ = [
"DeviceService",
"AuthService",
"SessionService",
]
@@ -0,0 +1,396 @@
"""
Auth Service - Core authentication logic for passwordless cross-device flow
"""
from typing import Optional
from datetime import datetime, timedelta
from models.auth_request import (
AuthRequest, AuthRequestCreate, AuthRequestApprove,
AuthRequestStatus, AuthRequestResponse, AuthRequestStatusResponse
)
from models.device import DeviceStatus
from utils.security import security_manager
from utils.tokens import TokenGenerator
from utils.device_fingerprint import DeviceFingerprint
from services.device_service import DeviceService
class AuthService:
"""
Authentication service for passwordless cross-device flow
Flow:
1. Web initiates login -> creates AuthRequest with QR code
2. App scans QR -> gets request_token
3. App approves with signed challenge
4. Web polls status -> gets approved
5. Web completes -> creates session
"""
# Auth request expiration time (5 minutes)
AUTH_REQUEST_EXPIRE_MINUTES = 5
def __init__(self, db_pool=None, device_service: Optional[DeviceService] = None):
self.db = db_pool
self.device_service = device_service or DeviceService(db_pool)
async def initiate_auth(self, request_data: AuthRequestCreate) -> AuthRequest:
"""
Initiate authentication from web
Creates a new auth request with QR code data
Args:
request_data: Initiation data
Returns:
AuthRequest: Created auth request
"""
# Generate secure tokens
request_token = TokenGenerator.generate_request_token()
nonce = TokenGenerator.generate_nonce()
# Generate challenge for device signing
challenge = security_manager.generate_challenge(request_token, nonce)
# Generate QR code data
expires_at = datetime.utcnow() + timedelta(minutes=self.AUTH_REQUEST_EXPIRE_MINUTES)
qr_code_data = TokenGenerator.generate_qr_payload(
request_token=request_token,
nonce=nonce,
expires_at=expires_at
)
# Generate browser fingerprint if data available
web_fingerprint = None
if request_data.device_fingerprint:
web_fingerprint = security_manager.hash_fingerprint(request_data.device_fingerprint)
auth_request = AuthRequest(
request_token=request_token,
qr_code_data=qr_code_data,
user_id=request_data.user_id,
status=AuthRequestStatus.PENDING,
nonce=nonce,
challenge=challenge,
web_fingerprint=web_fingerprint,
web_ip=request_data.ip_address,
web_user_agent=request_data.user_agent,
created_at=datetime.utcnow(),
expires_at=expires_at
)
# Store in database
if self.db:
await self._save_auth_request_to_db(auth_request)
return auth_request
async def approve_auth(self, approve_data: AuthRequestApprove) -> Optional[AuthRequest]:
"""
Approve authentication from device (app)
Args:
approve_data: Approval data from device
Returns:
AuthRequest: Updated auth request or None if invalid
"""
# Get auth request
auth_request = await self.get_auth_request_by_token(approve_data.request_token)
if not auth_request:
return None
# Check if request is still pending
if auth_request.status != AuthRequestStatus.PENDING:
return None
# Check if request has expired
if datetime.utcnow() > auth_request.expires_at:
auth_request.status = AuthRequestStatus.EXPIRED
if self.db:
await self._update_auth_request_status(auth_request)
return None
# Validate device
device = await self.device_service.get_device(approve_data.device_id)
if not device or device.status != DeviceStatus.ACTIVE:
return None
# Verify challenge response (device signature)
if not security_manager.verify_challenge_response(
challenge=auth_request.challenge,
response=approve_data.challenge_response,
public_key_pem=device.public_key
):
return None
# Validate device fingerprint
device_fingerprint_hash = security_manager.hash_fingerprint(approve_data.device_fingerprint)
if device.device_fingerprint != device_fingerprint_hash:
# Fingerprint mismatch - possible attack
return None
# Update auth request
auth_request.status = (
AuthRequestStatus.APPROVED if approve_data.approved
else AuthRequestStatus.REJECTED
)
auth_request.device_id = approve_data.device_id
auth_request.challenge_response = approve_data.challenge_response
auth_request.device_fingerprint = device_fingerprint_hash
auth_request.device_ip = approve_data.ip_address
auth_request.device_user_agent = approve_data.user_agent
auth_request.approved_at = datetime.utcnow()
# Set user_id from device if not already set
if not auth_request.user_id:
auth_request.user_id = device.user_id
# Update device last used
await self.device_service.update_last_used(
device_id=approve_data.device_id,
ip_address=approve_data.ip_address
)
# Save to database
if self.db:
await self._update_auth_request_approval(auth_request)
return auth_request
async def get_auth_status(self, request_token: str) -> Optional[AuthRequestStatusResponse]:
"""
Get authentication status for polling
Args:
request_token: Auth request token
Returns:
AuthRequestStatusResponse: Current status
"""
auth_request = await self.get_auth_request_by_token(request_token)
if not auth_request:
return None
# Check expiration
if (auth_request.status == AuthRequestStatus.PENDING and
datetime.utcnow() > auth_request.expires_at):
auth_request.status = AuthRequestStatus.EXPIRED
if self.db:
await self._update_auth_request_status(auth_request)
# Get device info if approved
device_name = None
device_platform = None
if auth_request.device_id:
device = await self.device_service.get_device(auth_request.device_id)
if device:
device_name = device.device_name
device_platform = device.platform.value
return AuthRequestStatusResponse(
status=auth_request.status,
request_token=auth_request.request_token,
device_name=device_name,
device_platform=device_platform,
approved_at=auth_request.approved_at,
completed_at=auth_request.completed_at,
session_token=auth_request.session_token,
error_message=auth_request.error_message,
expires_at=auth_request.expires_at
)
async def complete_auth(self, request_token: str) -> Optional[AuthRequest]:
"""
Complete authentication (mark as completed)
Called after web session is created
Args:
request_token: Auth request token
Returns:
AuthRequest: Updated auth request
"""
auth_request = await self.get_auth_request_by_token(request_token)
if not auth_request:
return None
if auth_request.status != AuthRequestStatus.APPROVED:
return None
auth_request.status = AuthRequestStatus.COMPLETED
auth_request.completed_at = datetime.utcnow()
if self.db:
await self._update_auth_request_completion(auth_request)
return auth_request
async def cancel_auth(self, request_token: str) -> bool:
"""
Cancel authentication request
Args:
request_token: Auth request token
Returns:
bool: True if cancelled
"""
auth_request = await self.get_auth_request_by_token(request_token)
if not auth_request:
return False
if auth_request.status not in [AuthRequestStatus.PENDING]:
return False
auth_request.status = AuthRequestStatus.CANCELLED
if self.db:
await self._update_auth_request_status(auth_request)
return True
async def get_auth_request(self, auth_request_id: str) -> Optional[AuthRequest]:
"""Get auth request by ID"""
if not self.db:
return None
row = await self.db.fetchrow(
"""
SELECT * FROM auth_requests WHERE id = $1
""",
auth_request_id
)
if row:
return AuthRequest(**dict(row))
return None
async def get_auth_request_by_token(self, request_token: str) -> Optional[AuthRequest]:
"""Get auth request by token"""
if not self.db:
return None
row = await self.db.fetchrow(
"""
SELECT * FROM auth_requests WHERE request_token = $1
""",
request_token
)
if row:
return AuthRequest(**dict(row))
return None
async def cleanup_expired_requests(self) -> int:
"""
Clean up expired pending requests
Returns:
int: Number of requests cleaned up
"""
if not self.db:
return 0
result = await self.db.execute(
"""
UPDATE auth_requests
SET status = 'expired'
WHERE status = 'pending' AND expires_at < NOW()
"""
)
# Parse result (e.g., "UPDATE 5")
try:
return int(result.split()[-1])
except (IndexError, ValueError):
return 0
async def _save_auth_request_to_db(self, auth_request: AuthRequest):
"""Save auth request to database"""
await self.db.execute(
"""
INSERT INTO auth_requests (
id, request_token, qr_code_data, user_id, device_id,
status, nonce, challenge, challenge_response,
web_fingerprint, device_fingerprint,
web_ip, device_ip, web_user_agent, device_user_agent,
created_at, expires_at, approved_at, completed_at,
session_token, error_message
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)
""",
auth_request.id,
auth_request.request_token,
auth_request.qr_code_data,
auth_request.user_id,
auth_request.device_id,
auth_request.status.value,
auth_request.nonce,
auth_request.challenge,
auth_request.challenge_response,
auth_request.web_fingerprint,
auth_request.device_fingerprint,
auth_request.web_ip,
auth_request.device_ip,
auth_request.web_user_agent,
auth_request.device_user_agent,
auth_request.created_at,
auth_request.expires_at,
auth_request.approved_at,
auth_request.completed_at,
auth_request.session_token,
auth_request.error_message
)
async def _update_auth_request_status(self, auth_request: AuthRequest):
"""Update auth request status"""
await self.db.execute(
"""
UPDATE auth_requests
SET status = $2
WHERE id = $1
""",
auth_request.id,
auth_request.status.value
)
async def _update_auth_request_approval(self, auth_request: AuthRequest):
"""Update auth request after approval"""
await self.db.execute(
"""
UPDATE auth_requests
SET status = $2, device_id = $3, challenge_response = $4,
device_fingerprint = $5, device_ip = $6, device_user_agent = $7,
approved_at = $8, user_id = $9
WHERE id = $1
""",
auth_request.id,
auth_request.status.value,
auth_request.device_id,
auth_request.challenge_response,
auth_request.device_fingerprint,
auth_request.device_ip,
auth_request.device_user_agent,
auth_request.approved_at,
auth_request.user_id
)
async def _update_auth_request_completion(self, auth_request: AuthRequest):
"""Update auth request after completion"""
await self.db.execute(
"""
UPDATE auth_requests
SET status = $2, completed_at = $3
WHERE id = $1
""",
auth_request.id,
auth_request.status.value,
auth_request.completed_at
)
# Global auth service instance
auth_service = AuthService()
@@ -0,0 +1,253 @@
"""
Device Service - Manages device registration and operations
"""
from typing import Optional, List
from datetime import datetime
from models.device import (
Device, DeviceCreate, DeviceUpdate, DeviceResponse,
DeviceStatus, DevicePlatform
)
from utils.security import security_manager
from utils.device_fingerprint import DeviceFingerprint
class DeviceService:
"""
Service for managing registered devices
Handles:
- Device registration
- Device lookup
- Device revocation
- Device fingerprint validation
"""
def __init__(self, db_pool=None):
self.db = db_pool
async def register_device(self, device_data: DeviceCreate) -> Device:
"""
Register a new device
Args:
device_data: Device registration data
Returns:
Device: Created device
"""
# Hash the device fingerprint for storage
fingerprint_hash = security_manager.hash_fingerprint(device_data.device_fingerprint)
device = Device(
user_id=device_data.user_id,
device_name=device_data.device_name,
platform=device_data.platform,
public_key=device_data.public_key,
device_fingerprint=fingerprint_hash,
push_token=device_data.push_token,
status=DeviceStatus.ACTIVE,
created_at=datetime.utcnow(),
updated_at=datetime.utcnow()
)
# Store in database
if self.db:
await self._save_device_to_db(device)
return device
async def get_device(self, device_id: str) -> Optional[Device]:
"""Get device by ID"""
if not self.db:
return None
row = await self.db.fetchrow(
"""
SELECT * FROM devices WHERE id = $1
""",
device_id
)
if row:
return Device(**dict(row))
return None
async def get_device_by_fingerprint(
self,
user_id: str,
fingerprint: str
) -> Optional[Device]:
"""Get device by user ID and fingerprint"""
if not self.db:
return None
fingerprint_hash = security_manager.hash_fingerprint(fingerprint)
row = await self.db.fetchrow(
"""
SELECT * FROM devices
WHERE user_id = $1 AND device_fingerprint = $2 AND status = 'active'
""",
user_id,
fingerprint_hash
)
if row:
return Device(**dict(row))
return None
async def get_user_devices(self, user_id: str) -> List[DeviceResponse]:
"""Get all devices for a user"""
if not self.db:
return []
rows = await self.db.fetch(
"""
SELECT id, user_id, device_name, platform, status, created_at, last_used_at
FROM devices
WHERE user_id = $1
ORDER BY last_used_at DESC NULLS LAST
""",
user_id
)
return [DeviceResponse(**dict(row)) for row in rows]
async def update_device(
self,
device_id: str,
update_data: DeviceUpdate
) -> Optional[Device]:
"""Update device information"""
device = await self.get_device(device_id)
if not device:
return None
# Update fields
if update_data.device_name:
device.device_name = update_data.device_name
if update_data.push_token is not None:
device.push_token = update_data.push_token
if update_data.status:
device.status = update_data.status
device.updated_at = datetime.utcnow()
if self.db:
await self._update_device_in_db(device)
return device
async def revoke_device(self, device_id: str, reason: str = "") -> bool:
"""
Revoke a device
Args:
device_id: Device to revoke
reason: Reason for revocation
Returns:
bool: True if revoked successfully
"""
device = await self.get_device(device_id)
if not device:
return False
device.status = DeviceStatus.REVOKED
device.updated_at = datetime.utcnow()
if self.db:
await self.db.execute(
"""
UPDATE devices
SET status = 'revoked', updated_at = NOW()
WHERE id = $1
""",
device_id
)
return True
async def validate_device(
self,
device_id: str,
fingerprint: Optional[str] = None
) -> bool:
"""
Validate device is active and optionally check fingerprint
Args:
device_id: Device ID
fingerprint: Optional fingerprint to validate
Returns:
bool: True if device is valid
"""
device = await self.get_device(device_id)
if not device:
return False
if device.status != DeviceStatus.ACTIVE:
return False
if fingerprint:
fingerprint_hash = security_manager.hash_fingerprint(fingerprint)
if device.device_fingerprint != fingerprint_hash:
return False
return True
async def update_last_used(self, device_id: str, ip_address: Optional[str] = None):
"""Update last used timestamp"""
if self.db:
await self.db.execute(
"""
UPDATE devices
SET last_used_at = NOW(), last_ip = $2
WHERE id = $1
""",
device_id,
ip_address
)
async def _save_device_to_db(self, device: Device):
"""Save device to database"""
await self.db.execute(
"""
INSERT INTO devices (
id, user_id, device_name, platform, public_key,
device_fingerprint, push_token, status, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
""",
device.id,
device.user_id,
device.device_name,
device.platform.value,
device.public_key,
device.device_fingerprint,
device.push_token,
device.status.value,
device.created_at,
device.updated_at
)
async def _update_device_in_db(self, device: Device):
"""Update device in database"""
await self.db.execute(
"""
UPDATE devices
SET device_name = $2, push_token = $3, status = $4, updated_at = $5
WHERE id = $1
""",
device.id,
device.device_name,
device.push_token,
device.status.value,
device.updated_at
)
# Global device service instance
device_service = DeviceService()
@@ -0,0 +1,13 @@
"""
QuixZoom Auth Service - Utilities
"""
from .security import SecurityManager
from .device_fingerprint import DeviceFingerprint
from .tokens import TokenGenerator
__all__ = [
"SecurityManager",
"DeviceFingerprint",
"TokenGenerator",
]
@@ -0,0 +1,156 @@
"""
Device fingerprinting utilities
Generates unique fingerprints for devices and browsers
"""
import hashlib
import json
from typing import Dict, Optional, Any
class DeviceFingerprint:
"""
Device fingerprinting for security
Creates unique fingerprints from device/browser characteristics
to detect anomalies and prevent replay attacks
"""
@staticmethod
def generate_browser_fingerprint(
user_agent: Optional[str] = None,
accept_language: Optional[str] = None,
screen_resolution: Optional[str] = None,
timezone: Optional[str] = None,
fonts: Optional[list] = None,
canvas_hash: Optional[str] = None,
webgl_hash: Optional[str] = None,
plugins: Optional[list] = None
) -> str:
"""
Generate browser fingerprint from available characteristics
Args:
user_agent: Browser user agent string
accept_language: Accept-Language header
screen_resolution: Screen resolution (e.g., "1920x1080")
timezone: Browser timezone
fonts: List of detected fonts
canvas_hash: Canvas fingerprinting hash
webgl_hash: WebGL fingerprinting hash
plugins: List of browser plugins
Returns:
str: SHA-256 hash of fingerprint components
"""
components = {
"user_agent": user_agent or "",
"accept_language": accept_language or "",
"screen_resolution": screen_resolution or "",
"timezone": timezone or "",
"fonts": sorted(fonts) if fonts else [],
"canvas_hash": canvas_hash or "",
"webgl_hash": webgl_hash or "",
"plugins": sorted(plugins) if plugins else []
}
# Create deterministic JSON string
fingerprint_json = json.dumps(components, sort_keys=True, separators=(',', ':'))
# Hash the fingerprint
return hashlib.sha256(fingerprint_json.encode()).hexdigest()
@staticmethod
def generate_device_fingerprint(
platform: str,
os_version: Optional[str] = None,
device_model: Optional[str] = None,
hardware_id: Optional[str] = None,
app_version: Optional[str] = None,
install_id: Optional[str] = None
) -> str:
"""
Generate mobile device fingerprint
Args:
platform: Device platform (ios/android)
os_version: OS version
device_model: Device model
hardware_id: Hardware identifier
app_version: App version
install_id: Unique installation ID
Returns:
str: SHA-256 hash of device components
"""
components = {
"platform": platform.lower(),
"os_version": os_version or "",
"device_model": device_model or "",
"hardware_id": hardware_id or "",
"app_version": app_version or "",
"install_id": install_id or ""
}
fingerprint_json = json.dumps(components, sort_keys=True, separators=(',', ':'))
return hashlib.sha256(fingerprint_json.encode()).hexdigest()
@staticmethod
def generate_simple_fingerprint(
user_agent: Optional[str] = None,
ip_address: Optional[str] = None,
accept_headers: Optional[Dict[str, str]] = None
) -> str:
"""
Generate simple fingerprint from request headers
Used as fallback when detailed fingerprinting is not available
"""
components = {
"user_agent": user_agent or "",
"ip_address": ip_address or "",
"accept": accept_headers.get("accept", "") if accept_headers else "",
"accept_language": accept_headers.get("accept-language", "") if accept_headers else "",
"accept_encoding": accept_headers.get("accept-encoding", "") if accept_headers else ""
}
fingerprint_json = json.dumps(components, sort_keys=True, separators=(',', ':'))
return hashlib.sha256(fingerprint_json.encode()).hexdigest()
@staticmethod
def compare_fingerprints(
fingerprint1: str,
fingerprint2: str,
tolerance: float = 0.0
) -> bool:
"""
Compare two fingerprints
Args:
fingerprint1: First fingerprint
fingerprint2: Second fingerprint
tolerance: Similarity threshold (0.0 = exact match)
Returns:
bool: True if fingerprints match within tolerance
"""
if tolerance == 0.0:
return fingerprint1 == fingerprint2
# For fuzzy matching, we could implement similarity algorithms
# For now, exact match only
return fingerprint1 == fingerprint2
@staticmethod
def extract_fingerprint_components(fingerprint_hash: str) -> Dict[str, Any]:
"""
Extract components from a fingerprint (if stored separately)
Note: This is a placeholder - in production, components would be
stored alongside the hash for analysis
"""
return {
"hash": fingerprint_hash,
"algorithm": "sha256",
"components_stored": False
}
+227
View File
@@ -0,0 +1,227 @@
"""
Security utilities for passwordless authentication
JWT signing, nonce generation, challenge/response
"""
import os
import secrets
import hashlib
import hmac
import base64
from datetime import datetime, timedelta
from typing import Dict, Optional, Tuple
import jwt
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.exceptions import InvalidSignature
class SecurityManager:
"""
Security manager for passwordless authentication
Features:
- JWT token creation/verification
- Cryptographic nonce generation
- Challenge/response for device signing
- Token expiration management
"""
def __init__(self):
self.secret_key = os.getenv("JWT_SECRET", "quixzoom_jwt_secret_change_in_production")
self.algorithm = os.getenv("JWT_ALGORITHM", "HS256")
self.access_token_expire = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))
self.refresh_token_expire = int(os.getenv("REFRESH_TOKEN_EXPIRE_DAYS", "7"))
self.auth_request_expire = int(os.getenv("AUTH_REQUEST_EXPIRE_MINUTES", "5"))
def generate_nonce(self, length: int = 32) -> str:
"""Generate cryptographically secure nonce"""
return secrets.token_urlsafe(length)
def generate_challenge(self, request_token: str, nonce: str) -> str:
"""
Generate a challenge for device signing
Combines request token and nonce with timestamp
"""
timestamp = datetime.utcnow().isoformat()
challenge_data = f"{request_token}:{nonce}:{timestamp}"
# Create HMAC of challenge data
challenge = hmac.new(
self.secret_key.encode(),
challenge_data.encode(),
hashlib.sha256
).hexdigest()
return challenge
def verify_challenge_response(
self,
challenge: str,
response: str,
public_key_pem: str
) -> bool:
"""
Verify device challenge response
Args:
challenge: Original challenge
response: Device's signed response (base64 encoded signature)
public_key_pem: Device's public key in PEM format
Returns:
bool: True if signature is valid
"""
try:
# Load public key
public_key = serialization.load_pem_public_key(
public_key_pem.encode()
)
# Decode signature
signature = base64.b64decode(response)
# Verify signature
public_key.verify(
signature,
challenge.encode(),
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except InvalidSignature:
return False
except Exception:
return False
def create_access_token(
self,
user_id: str,
device_id: str,
session_id: str,
additional_claims: Optional[Dict] = None
) -> str:
"""Create JWT access token"""
now = datetime.utcnow()
expires = now + timedelta(minutes=self.access_token_expire)
payload = {
"sub": user_id,
"device_id": device_id,
"session_id": session_id,
"type": "access",
"iat": now,
"exp": expires,
"jti": self.generate_nonce(16) # Unique token ID
}
if additional_claims:
payload.update(additional_claims)
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
def create_refresh_token(
self,
user_id: str,
session_id: str
) -> str:
"""Create JWT refresh token"""
now = datetime.utcnow()
expires = now + timedelta(days=self.refresh_token_expire)
payload = {
"sub": user_id,
"session_id": session_id,
"type": "refresh",
"iat": now,
"exp": expires,
"jti": self.generate_nonce(16)
}
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
def verify_token(self, token: str, expected_type: str = "access") -> Dict:
"""
Verify JWT token
Args:
token: JWT token string
expected_type: Expected token type ("access" or "refresh")
Returns:
Dict: Token payload
Raises:
jwt.ExpiredSignatureError: Token expired
jwt.InvalidTokenError: Invalid token
ValueError: Wrong token type
"""
payload = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
# Check token type
token_type = payload.get("type")
if token_type != expected_type:
raise ValueError(f"Expected {expected_type} token, got {token_type}")
return payload
def create_request_token(self) -> str:
"""Create unique request token for auth flow"""
return f"req_{secrets.token_urlsafe(24)}"
def create_qr_code_data(
self,
request_token: str,
nonce: str,
base_url: str = "quixzoom://auth"
) -> str:
"""
Create QR code data string
Format: quixzoom://auth?token=<token>&nonce=<nonce>
"""
return f"{base_url}?token={request_token}&nonce={nonce}"
def hash_fingerprint(self, fingerprint: str) -> str:
"""Hash device fingerprint for storage"""
return hashlib.sha256(fingerprint.encode()).hexdigest()
def generate_key_pair(self) -> Tuple[str, str]:
"""
Generate RSA key pair for device
Returns:
Tuple of (private_key_pem, public_key_pem)
"""
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
).decode()
public_pem = private_key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode()
return private_pem, public_pem
def is_token_expired(self, token: str) -> bool:
"""Check if token is expired without raising exception"""
try:
jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
return False
except jwt.ExpiredSignatureError:
return True
except jwt.InvalidTokenError:
return True
# Global security manager instance
security_manager = SecurityManager()
+133
View File
@@ -0,0 +1,133 @@
"""
Token generation and validation utilities
"""
import secrets
import string
from datetime import datetime, timedelta
from typing import Optional
class TokenGenerator:
"""
Secure token generator for authentication flows
Generates various types of tokens:
- Request tokens (for auth flow)
- Session tokens
- One-time tokens
- Recovery codes
"""
ALPHANUMERIC = string.ascii_letters + string.digits
URLSAFE = string.ascii_letters + string.digits + "-_"
@classmethod
def generate_request_token(cls, length: int = 32) -> str:
"""Generate secure request token"""
return f"req_{secrets.token_urlsafe(length)}"
@classmethod
def generate_session_token(cls, length: int = 32) -> str:
"""Generate secure session token"""
return f"sess_{secrets.token_urlsafe(length)}"
@classmethod
def generate_refresh_token(cls, length: int = 48) -> str:
"""Generate secure refresh token"""
return f"rt_{secrets.token_urlsafe(length)}"
@classmethod
def generate_nonce(cls, length: int = 32) -> str:
"""Generate cryptographic nonce"""
return secrets.token_urlsafe(length)
@classmethod
def generate_pin(cls, length: int = 6) -> str:
"""Generate numeric PIN code"""
return ''.join(secrets.choice(string.digits) for _ in range(length))
@classmethod
def generate_recovery_code(cls, segments: int = 4, segment_length: int = 4) -> str:
"""Generate human-readable recovery code"""
segments_list = [
''.join(secrets.choice(cls.ALPHANUMERIC) for _ in range(segment_length))
for _ in range(segments)
]
return '-'.join(segments_list).upper()
@classmethod
def generate_api_key(cls, prefix: str = "qk", length: int = 32) -> str:
"""Generate API key with prefix"""
return f"{prefix}_{secrets.token_urlsafe(length)}"
@classmethod
def generate_device_id(cls) -> str:
"""Generate unique device identifier"""
return f"dev_{secrets.token_urlsafe(16)}"
@classmethod
def generate_qr_payload(
cls,
request_token: str,
nonce: str,
expires_at: datetime,
base_url: str = "quixzoom://auth"
) -> str:
"""
Generate QR code payload
Format: quixzoom://auth?token=<token>&nonce=<nonce>&exp=<timestamp>
"""
exp_timestamp = int(expires_at.timestamp())
return f"{base_url}?token={request_token}&nonce={nonce}&exp={exp_timestamp}"
@classmethod
def parse_qr_payload(cls, payload: str) -> Optional[dict]:
"""
Parse QR code payload
Returns:
dict with token, nonce, exp or None if invalid
"""
try:
# Remove scheme
if '://' in payload:
payload = payload.split('://', 1)[1]
# Parse query parameters
if '?' not in payload:
return None
query_string = payload.split('?', 1)[1]
params = {}
for param in query_string.split('&'):
if '=' in param:
key, value = param.split('=', 1)
params[key] = value
return {
"token": params.get("token"),
"nonce": params.get("nonce"),
"exp": int(params.get("exp", 0)) if params.get("exp") else None
}
except Exception:
return None
@classmethod
def is_token_format_valid(cls, token: str, prefix: Optional[str] = None) -> bool:
"""Check if token has valid format"""
if not token or len(token) < 10:
return False
if prefix and not token.startswith(prefix):
return False
# Check for valid characters
valid_chars = set(cls.URLSAFE + '_')
return all(c in valid_chars for c in token.replace(prefix + '_', '') if prefix else token)
# Global token generator
token_generator = TokenGenerator()