From 4a5331b2e4af2009aa116f10543803c9a205a50c Mon Sep 17 00:00:00 2001 From: Sergei Date: Thu, 11 Dec 2025 13:25:14 -0800 Subject: [PATCH] [TEST] Initial setup - NOT PRODUCTION CODE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⚠️ This is test/experimental code for API integration testing. Do not use in production. Includes: - WellNuo API integration (dashboard, patient context) - Playwright tests for API verification - WebView component for dashboard embedding - API documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .env.example | 11 + .gitignore | 1 + API_DOCUMENTATION.md | 443 ++ App.tsx | 428 +- app.json | 37 +- babel.config.js | 14 + backend/deploy.sh | 22 + backend/server.js | 767 +++ eas.json | 27 + index.ts => index.tsx | 12 +- package-lock.json | 4906 ++++++++++++++++- package.json | 19 +- playwright.config.js | 23 + src/components/DashboardWebView.tsx | 245 + src/components/StatusIndicator.tsx | 62 + src/components/TranscriptView.tsx | 110 + src/components/VoiceButton.tsx | 281 + src/components/ZAIAnalysis.jsx | 137 + src/components/index.ts | 4 + src/hooks/useVoiceAssistant.ts | 1013 ++++ src/services/webhookService.ts | 322 ++ src/types/env.d.ts | 5 + src/types/index.ts | 23 + src/utils/audioConverter.ts | 131 + src/utils/index.ts | 1 + src/utils/zaiMCPClient.js | 87 + tests/api-check.spec.js | 164 + tests/api-discovery.spec.js | 185 + tests/check-repo.spec.js | 18 + tests/gitea-complete.spec.js | 76 + tests/gitea-final.spec.js | 75 + tests/gitea-register.spec.js | 107 + tests/gitea-reset-password.spec.js | 44 + tests/gitea-set-password.spec.js | 78 + tests/network-capture.spec.js | 104 + tests/screenshots/.last-run.json | 6 + .../error-context.md | 126 + .../test-failed-1.png | Bin 0 -> 85698 bytes tests/test-chat.js | 130 + 39 files changed, 10183 insertions(+), 61 deletions(-) create mode 100644 .env.example create mode 100644 API_DOCUMENTATION.md create mode 100644 babel.config.js create mode 100755 backend/deploy.sh create mode 100644 backend/server.js create mode 100644 eas.json rename index.ts => index.tsx (55%) create mode 100644 playwright.config.js create mode 100644 src/components/DashboardWebView.tsx create mode 100644 src/components/StatusIndicator.tsx create mode 100644 src/components/TranscriptView.tsx create mode 100644 src/components/VoiceButton.tsx create mode 100644 src/components/ZAIAnalysis.jsx create mode 100644 src/components/index.ts create mode 100644 src/hooks/useVoiceAssistant.ts create mode 100644 src/services/webhookService.ts create mode 100644 src/types/env.d.ts create mode 100644 src/types/index.ts create mode 100644 src/utils/audioConverter.ts create mode 100644 src/utils/index.ts create mode 100644 src/utils/zaiMCPClient.js create mode 100644 tests/api-check.spec.js create mode 100644 tests/api-discovery.spec.js create mode 100644 tests/check-repo.spec.js create mode 100644 tests/gitea-complete.spec.js create mode 100644 tests/gitea-final.spec.js create mode 100644 tests/gitea-register.spec.js create mode 100644 tests/gitea-reset-password.spec.js create mode 100644 tests/gitea-set-password.spec.js create mode 100644 tests/network-capture.spec.js create mode 100644 tests/screenshots/.last-run.json create mode 100644 tests/screenshots/check-repo-check-repo-content-chromium/error-context.md create mode 100644 tests/screenshots/check-repo-check-repo-content-chromium/test-failed-1.png create mode 100644 tests/test-chat.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..03e8c95 --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# OpenAI Configuration +# Get your API key from https://platform.openai.com/api-keys +OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxx + +# Webhook URL for fetching context/instructions (optional) +# The webhook should return JSON with: { systemPrompt, voiceSettings, userData } +WEBHOOK_URL=https://your-webhook-url.com/context + +# Voice Settings (optional) +# Available voices: alloy, echo, shimmer, ash, ballad, coral, sage, verse +DEFAULT_VOICE=shimmer diff --git a/.gitignore b/.gitignore index d914c32..de0ac0c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ yarn-error.* # local env files .env*.local +.env # typescript *.tsbuildinfo diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md new file mode 100644 index 0000000..7a799a0 --- /dev/null +++ b/API_DOCUMENTATION.md @@ -0,0 +1,443 @@ +# WellNuo API Documentation + +## Overview + +WellNuo - система мониторинга здоровья пожилых людей. API позволяет получать данные о пациентах, их активности, локации и состоянии здоровья. + +**Дата тестирования:** 2025-12-10 +**Статус:** Работает + +--- + +## Endpoints + +### Base URL +``` +https://eluxnetworks.net/function/well-api/api +``` + +### Web Dashboard +``` +https://react.eluxnetworks.net/dashboard +``` + +--- + +## Authentication + +### Credentials +``` +Username: anandk +Password: anandk_8 +Client ID: 001 +``` + +### Получение токена + +**Request:** +```bash +curl -X POST "https://eluxnetworks.net/function/well-api/api" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "function=credentials&clientId=001&user_name=anandk&ps=anandk_8&nonce=$(uuidgen)" +``` + +**Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| function | string | Yes | `credentials` | +| clientId | string | Yes | `001` | +| user_name | string | Yes | Username | +| ps | string | Yes | Password | +| nonce | string | Yes | Unique UUID for each request | + +**Response (200 OK):** +```json +{ + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "privileges": "21,38,29,41,42", + "user_id": 43, + "max_role": 2, + "status": "200 OK" +} +``` + +**Token Format:** JWT (JSON Web Token) +- Algorithm: HS256 +- Expiration: ~7 days (exp claim in payload) + +--- + +## API Endpoints + +### 1. Dashboard List (Список пациентов) + +**Request:** +```bash +curl -X POST "https://eluxnetworks.net/function/well-api/api" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "function=dashboard_list&user_name=anandk&token=&date=2025-12-10&nonce=" +``` + +**Parameters:** +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| function | string | Yes | `dashboard_list` | +| user_name | string | Yes | Username | +| token | string | Yes | JWT access token | +| date | string | Yes | Date in YYYY-MM-DD format | +| nonce | string | Yes | Unique UUID | + +**Response:** +```json +{ + "result_list": [ + { + "user_id": 25, + "name": "Ferdinand Zmrzli", + "address": "661 Encore Way", + "time_zone": "America/Los_Angeles", + "picture": "/", + "bathroom_at": "2025-12-10T13:52:00", + "kitchen_at": "2025-12-10T14:48:00", + "bedroom_at": "2025-12-10T09:14:00", + "temperature": 72.77, + "smell": "clean", + "bathroom_delayed": [6, 12], + "kitchen_delayed": [6, 12], + "bedroom_delayed": [13, 16], + "last_location": "Kitchen", + "last_detected_time": "2025-12-10T14:47:00", + "before_last_location": "Living Room", + "last_present_duration": 1, + "wellness_score_percent": 90, + "wellness_descriptor": "Great!", + "wellness_descriptor_color": "bg-green-100 text-green-700", + "bedroom_temperature": 15.22, + "sleep_bathroom_visit_count": 0, + "bedroom_co2": 500, + "shower_detected_time": "2025-12-10T13:52:00", + "breakfast_detected_time": 0, + "living_room_time_spent": 0, + "outside_hours": 0, + "most_time_spent_in": "Bedroom", + "sleep_hours": 11.12, + "units": "°F", + "deployment_id": 21, + "location_list": ["Living Room", "Bathroom Small", "Bedroom", "Dining Room", "Bathroom Main", "Kitchen", "Office"] + } + ], + "status": "200 OK" +} +``` + +--- + +## Available Patients (Тестовые данные) + +| User ID | Name | Address | Timezone | Deployment ID | +|---------|------|---------|----------|---------------| +| 25 | Ferdinand Zmrzli | 661 Encore Way | America/Los_Angeles | 21 | +| 34 | Helga Kleine | - | Europe/Berlin | 29 | +| 48 | Đurđica Božičević-Radić | A.T. Mimare 38 | Europe/Zagreb | 38 | +| 50 | Tarik Hubana | - | Europe/Sarajevo | 41 | +| 54 | Jamie Rivera-Vallestero | 2088 Trafalgar Ave | US/Pacific | 42 | + +--- + +## Data Structure + +### Patient Object Fields + +| Field | Type | Description | +|-------|------|-------------| +| user_id | int | Unique patient ID | +| name | string | Patient full name | +| address | string | Home address | +| time_zone | string | IANA timezone | +| temperature | float | Current room temperature | +| smell | string | Air quality (clean/etc) | +| last_location | string | Current room | +| last_detected_time | datetime | Last activity timestamp | +| wellness_score_percent | int | Health score 0-100 | +| wellness_descriptor | string | Status text | +| sleep_hours | float | Hours slept | +| bathroom_at | datetime | Last bathroom visit | +| kitchen_at | datetime | Last kitchen visit | +| bedroom_at | datetime | Last bedroom visit | +| location_list | array | Available rooms in home | +| units | string | Temperature units (°F/°C) | +| deployment_id | int | Sensor deployment ID | + +--- + +## Integration Notes + +### WebView Integration (React Native) + +Для встраивания dashboard в React Native приложение: + +```javascript +import { WebView } from 'react-native-webview'; + +// Inject credentials and auto-login +const injectedJS = ` + localStorage.setItem('auth2', JSON.stringify({ + username: 'anandk', + token: '${token}', + user_id: 43 + })); + window.location.reload(); + true; +`; + + +``` + +### Request Headers + +All requests use: +``` +Content-Type: application/x-www-form-urlencoded +``` + +### Nonce Generation + +Each request requires a unique nonce (UUID): +```javascript +const nonce = crypto.randomUUID(); +// or +const nonce = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { + const r = Math.random() * 16 | 0; + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); +}); +``` + +--- + +## Error Handling + +| Status | Description | +|--------|-------------| +| 200 OK | Success | +| 401 | Invalid token | +| 403 | Access denied | +| 404 | Resource not found | +| 405 | Method not allowed (use POST) | + +--- + +## Quick Test Commands + +### 1. Get Token +```bash +curl -s -X POST "https://eluxnetworks.net/function/well-api/api" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "function=credentials&clientId=001&user_name=anandk&ps=anandk_8&nonce=test-123" +``` + +### 2. Get Dashboard +```bash +TOKEN="" +curl -s -X POST "https://eluxnetworks.net/function/well-api/api" \ + -d "function=dashboard_list&user_name=anandk&token=$TOKEN&date=$(date +%Y-%m-%d)&nonce=test-456" +``` + +--- + +--- + +## Patient Context API (для AI чата) + +### Endpoint +``` +https://wellnuo.smartlaunchhub.com/api/patient/context +``` + +### Request +```bash +curl -s "https://wellnuo.smartlaunchhub.com/api/patient/context" +``` + +### Response Structure + +API возвращает детальные данные о пациенте для использования в AI чате: + +```json +{ + "patient": { + "id": "dad", + "name": "John Smith", + "age": 78, + "relationship": "Father", + "address": "123 Oak Street, San Francisco, CA", + "timezone": "America/Los_Angeles", + "emergency_contact": { "name": "Sarah Smith", "phone": "+1 (555) 123-4567" } + }, + "current_status": { + "location": "Living Room", + "presence_detected": true, + "is_moving": false, + "last_movement": "2025-12-10T22:51:43.316Z", + "estimated_activity": "resting", + "time_in_current_room_minutes": 45 + }, + "environment": { + "living_room": { + "temperature_c": 22.2, "temperature_f": 72, + "humidity_percent": 45, "co2_ppm": 650, + "voc_index": 85, "light_lux": 320, + "air_quality_status": "good", + "presence": true, "motion_level": "low" + } + }, + "sleep_analysis": { + "last_night": { + "bed_time_detected": "2025-11-30T22:18:00Z", + "wake_time_detected": "2025-12-01T05:45:00Z", + "total_hours": 7.45, + "quality_score": 78, + "bathroom_visits": 1 + } + }, + "activity_patterns": { + "today": { + "total_active_minutes": 185, + "sedentary_minutes": 240, + "rooms_visited": ["Bedroom", "Bathroom", "Kitchen", "Living Room"] + } + }, + "recent_events": [ + { + "time": "2025-12-01T10:45:00Z", + "type": "presence", + "event": "Continued presence in living room", + "room": "Living Room" + } + ], + "alerts": { + "active": [], + "recent": [ + { + "id": "alert_001", + "type": "possible_fall", + "severity": "high", + "timestamp": "2025-11-15T14:22:00Z", + "room": "Bathroom", + "resolved": true + } + ] + } +} +``` + +### Доступные данные для чата: + +| Раздел | Описание | +|--------|----------| +| patient | Информация о пациенте | +| current_status | Текущее местоположение и активность | +| environment | Данные сенсоров по комнатам | +| environment_history | История показателей за день | +| sleep_analysis | Анализ сна | +| activity_patterns | Паттерны активности | +| recent_events | Последние события (до 90 записей) | +| alerts | Активные и недавние алерты | + +--- + +## Files + +- `tests/api-check.spec.js` - Playwright tests for API +- `tests/network-capture.spec.js` - Network capture for debugging +- `tests/api-discovery.spec.js` - API endpoint discovery +- `tests/screenshots/` - Visual verification screenshots + +--- + +--- + +## React Native WebView Integration + +### Installation +```bash +npm install react-native-webview +``` + +### Usage Example +```tsx +import { DashboardWebView } from './src/components'; + +function DashboardScreen() { + const handlePatientSelect = (patientId: number, patientName: string) => { + console.log('Selected patient:', patientId, patientName); + // Navigate to patient details or start voice chat + }; + + return ( + console.error(error)} + /> + ); +} +``` + +### Component: `src/components/DashboardWebView.tsx` + +Компонент автоматически: +1. Получает JWT токен через API +2. Автоматически логинится в веб-интерфейс +3. Отображает dashboard внутри приложения +4. Перехватывает клики на пациентах + +### Credentials в коде: +```typescript +const CREDENTIALS = { + username: 'anandk', + password: 'anandk_8', + clientId: '001' +}; +``` + +--- + +--- + +## Gitea Repository Access + +### Credentials +``` +URL: https://gitea.wellnua.com +Username: sergei_t +Password: WellNuo2025!Secure +Email: serter2069@gmail.com +``` + +### Repository +``` +https://gitea.wellnua.com/robert/MobileApp_react_native +``` + +### Clone Command +```bash +git clone https://sergei_t:WellNuo2025%21Secure@gitea.wellnua.com/robert/MobileApp_react_native.git +``` + +### Local Clone +``` +~/Desktop/Wellnuo/wellnuo-mobile-repo/ +``` + +--- + +## Contact / Support + +- Dashboard: https://react.eluxnetworks.net/dashboard +- API Base: https://eluxnetworks.net/function/well-api/api +- Patient Context: https://wellnuo.smartlaunchhub.com/api/patient/context +- Gitea: https://gitea.wellnua.com diff --git a/App.tsx b/App.tsx index 0329d0c..de7c156 100644 --- a/App.tsx +++ b/App.tsx @@ -1,11 +1,295 @@ -import { StatusBar } from 'expo-status-bar'; -import { StyleSheet, Text, View } from 'react-native'; +import React, { useState, useEffect, useCallback } from 'react'; +import { + StyleSheet, + View, + Text, + StatusBar, + TouchableOpacity, + Alert, +} from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { Ionicons } from '@expo/vector-icons'; +import { OPENAI_API_KEY, WEBHOOK_URL } from '@env'; +import { VoiceButton, StatusIndicator, TranscriptView } from './src/components'; +import { useVoiceAssistant, PermissionStatus } from './src/hooks/useVoiceAssistant'; +import { fetchWebhookContext, getDefaultContext } from './src/services/webhookService'; +import { WebhookContext } from './src/types'; + +// Wellnuo brand colors +const COLORS = { + primary: '#0074be', + primaryDark: '#005a94', + teal: '#5db1a8', + purple: '#ab5b8d', + white: '#ffffff', + background: '#f4f6f8', + textDark: '#515b69', + textLight: '#7f8795', + error: '#dc3545', +}; + +interface Message { + role: 'user' | 'assistant'; + content: string; + timestamp: Date; +} export default function App() { + const [context, setContext] = useState(getDefaultContext()); + const [messages, setMessages] = useState([]); + const [currentTranscript, setCurrentTranscript] = useState(''); + const [assistantText, setAssistantText] = useState(''); + const [isInitialized, setIsInitialized] = useState(false); + const [isMuted, setIsMuted] = useState(false); + const [apiKey] = useState(OPENAI_API_KEY || ''); + + // Fetch context from webhook on mount + useEffect(() => { + async function initialize() { + if (WEBHOOK_URL) { + const webhookContext = await fetchWebhookContext(WEBHOOK_URL); + setContext(webhookContext); + } + setIsInitialized(true); + } + initialize(); + }, []); + + const handleTranscript = useCallback((text: string, isFinal: boolean) => { + if (isFinal) { + setMessages(prev => [ + ...prev, + { role: 'user', content: text, timestamp: new Date() }, + ]); + setCurrentTranscript(''); + } else { + setCurrentTranscript(text); + } + }, []); + + const handleAssistantResponse = useCallback((text: string) => { + setAssistantText(prev => prev + text); + }, []); + + const { + state, + connectionStatus, + permissionStatus, + isInConversation, + connect, + disconnect, + startContinuousListening, + stopContinuousListening, + interrupt, + openSettings, + } = useVoiceAssistant({ + apiKey, + context, + onTranscript: handleTranscript, + onAssistantResponse: handleAssistantResponse, + }); + + // Save assistant response when done + useEffect(() => { + if (!state.isSpeaking && !state.isProcessing && assistantText) { + setMessages(prev => [ + ...prev, + { role: 'assistant', content: assistantText, timestamp: new Date() }, + ]); + setAssistantText(''); + } + }, [state.isSpeaking, state.isProcessing, assistantText]); + + // Toggle mute + const handleToggleMute = useCallback(async () => { + if (isMuted) { + // Unmute - resume listening + setIsMuted(false); + if (connectionStatus === 'connected') { + await startContinuousListening(); + } + } else { + // Mute - stop listening but keep connection + setIsMuted(true); + await stopContinuousListening(); + } + }, [isMuted, connectionStatus, startContinuousListening, stopContinuousListening]); + + // Toggle conversation on/off with single tap + const handleToggleConversation = useCallback(async () => { + if (!apiKey) { + Alert.alert( + 'API Key Required', + 'Please add your OpenAI API key in the app configuration.', + [{ text: 'OK' }] + ); + return; + } + + // If in conversation, end it + if (isInConversation || connectionStatus === 'connected') { + await stopContinuousListening(); + disconnect(); + setIsMuted(false); + return; + } + + // Start new conversation + await connect(); + }, [apiKey, isInConversation, connectionStatus, connect, disconnect, stopContinuousListening]); + + // Start continuous listening after connected (if not muted) + useEffect(() => { + if (connectionStatus === 'connected' && !isInConversation && !isMuted) { + const timer = setTimeout(() => { + startContinuousListening(); + }, 500); + return () => clearTimeout(timer); + } + }, [connectionStatus, isInConversation, isMuted, startContinuousListening]); + + // Interrupt AI when tapping during speech + const handleInterrupt = useCallback(() => { + if (state.isSpeaking) { + interrupt(); + } + }, [state.isSpeaking, interrupt]); + + // Main button: Start/End conversation + const handleMainButtonPress = useCallback(() => { + handleToggleConversation(); + }, [handleToggleConversation]); + + // For legacy VoiceButton compatibility + const handlePressIn = useCallback(() => { + // Main button now only starts/stops conversation + handleMainButtonPress(); + }, [handleMainButtonPress]); + + const handlePressOut = useCallback(() => { + // No longer needed for toggle mode + }, []); + + const handleClearHistory = useCallback(() => { + Alert.alert( + 'Clear History', + 'Are you sure you want to clear the conversation history?', + [ + { text: 'Cancel', style: 'cancel' }, + { + text: 'Clear', + style: 'destructive', + onPress: () => setMessages([]), + }, + ] + ); + }, []); + + if (!isInitialized) { + return ( + + Initializing... + + ); + } + return ( - Open up App.tsx to start working on your app! - + + + {/* Header */} + + Wellnuo + Your AI Health Assistant + {messages.length > 0 && ( + + + + )} + + + {/* Transcript View */} + + + + + {/* Voice Controls */} + + {/* Permission Denied Message */} + {permissionStatus === 'denied' && ( + + + Microphone Access Required + + To use voice chat, please enable microphone access in Settings. + + + Open Settings + + + )} + + {permissionStatus !== 'denied' && ( + <> + + + + {/* Main Call Button - Start/End Call */} + + + + {/* Mute Button - only during call */} + {(isInConversation || connectionStatus === 'connected') && ( + + + + {isMuted ? 'Unmute' : 'Mute'} + + + )} + + {state.error && ( + {state.error} + )} + + + {connectionStatus !== 'connected' + ? 'Tap to call Julia' + : isInConversation + ? 'Tap again to end call' + : ''} + + + )} + + ); } @@ -13,8 +297,142 @@ export default function App() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: '#fff', + backgroundColor: COLORS.background, + }, + safeArea: { + flex: 1, + }, + loadingContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + backgroundColor: COLORS.background, + }, + loadingText: { + fontSize: 18, + color: COLORS.textDark, + }, + header: { + paddingHorizontal: 20, + paddingTop: 16, + paddingBottom: 16, + backgroundColor: COLORS.white, + alignItems: 'center', + borderBottomWidth: 1, + borderBottomColor: '#e5e7eb', + }, + title: { + fontSize: 28, + fontWeight: '600', + color: COLORS.primary, + letterSpacing: 0.5, + }, + subtitle: { + fontSize: 14, + color: COLORS.textLight, + marginTop: 4, + }, + clearButton: { + position: 'absolute', + right: 20, + top: 20, + padding: 8, + }, + transcriptContainer: { + flex: 1, + backgroundColor: COLORS.white, + marginHorizontal: 16, + marginVertical: 12, + borderRadius: 12, + overflow: 'hidden', + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.05, + shadowRadius: 8, + elevation: 2, + }, + controlsContainer: { + paddingVertical: 20, + paddingHorizontal: 20, + backgroundColor: COLORS.white, + alignItems: 'center', + borderTopWidth: 1, + borderTopColor: '#e5e7eb', + }, + buttonsRow: { + flexDirection: 'row', alignItems: 'center', justifyContent: 'center', }, + voiceButton: { + marginVertical: 8, + }, + muteButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: 16, + paddingVertical: 10, + borderRadius: 20, + backgroundColor: COLORS.background, + borderWidth: 1, + borderColor: '#e5e7eb', + marginTop: 12, + gap: 6, + }, + muteButtonActive: { + backgroundColor: COLORS.error, + borderColor: COLORS.error, + }, + muteButtonText: { + fontSize: 14, + fontWeight: '500', + color: COLORS.textDark, + }, + muteButtonTextActive: { + color: COLORS.white, + }, + errorText: { + color: COLORS.error, + fontSize: 14, + textAlign: 'center', + marginTop: 8, + }, + hint: { + color: COLORS.textLight, + fontSize: 13, + textAlign: 'center', + marginTop: 12, + }, + permissionDenied: { + alignItems: 'center', + paddingVertical: 20, + paddingHorizontal: 16, + }, + permissionTitle: { + color: COLORS.textDark, + fontSize: 18, + fontWeight: '600', + marginTop: 16, + marginBottom: 8, + textAlign: 'center', + }, + permissionText: { + color: COLORS.textLight, + fontSize: 14, + textAlign: 'center', + marginBottom: 20, + lineHeight: 20, + }, + settingsButton: { + backgroundColor: COLORS.primary, + paddingHorizontal: 24, + paddingVertical: 12, + borderRadius: 8, + }, + settingsButtonText: { + color: COLORS.white, + fontSize: 16, + fontWeight: '600', + }, }); diff --git a/app.json b/app.json index ca1c7c6..359da65 100644 --- a/app.json +++ b/app.json @@ -10,21 +10,46 @@ "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", - "backgroundColor": "#ffffff" + "backgroundColor": "#4A90A4" }, "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.kosyakorel1.wellnuo", + "infoPlist": { + "NSMicrophoneUsageDescription": "Wellnuo needs microphone access to enable voice conversations with your AI health assistant.", + "UIBackgroundModes": [ + "audio" + ], + "ITSAppUsesNonExemptEncryption": false + } }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" + "backgroundColor": "#4A90A4" }, - "edgeToEdgeEnabled": true, - "predictiveBackGestureEnabled": false + "permissions": [ + "android.permission.RECORD_AUDIO", + "android.permission.MODIFY_AUDIO_SETTINGS" + ], + "package": "com.wellnuo.app" }, "web": { "favicon": "./assets/favicon.png" - } + }, + "plugins": [ + [ + "expo-av", + { + "microphonePermission": "Wellnuo needs microphone access to enable voice conversations with your AI health assistant." + } + ] + ], + "extra": { + "eas": { + "projectId": "c9e68e27-5713-4b6d-a313-cb8e7a8866ee" + } + }, + "owner": "kosyakorel1" } } diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..46ebf26 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,14 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + plugins: [ + ['module:react-native-dotenv', { + moduleName: '@env', + path: '.env', + safe: false, + allowUndefined: true, + }], + ], + }; +}; diff --git a/backend/deploy.sh b/backend/deploy.sh new file mode 100755 index 0000000..72ce4c9 --- /dev/null +++ b/backend/deploy.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Deploy WellNuo backend to production + +SERVER="root@91.98.205.156" +REMOTE_PATH="/var/www/wellnuo" +LOCAL_PATH="$(dirname "$0")" + +echo "🚀 Deploying WellNuo backend..." + +# Sync files +rsync -avz --delete \ + --exclude 'node_modules' \ + --exclude '.DS_Store' \ + --exclude 'deploy.sh' \ + -e "ssh" \ + "$LOCAL_PATH/" "$SERVER:$REMOTE_PATH/" + +echo "📦 Restarting PM2..." +ssh $SERVER "pm2 restart wellnuo" + +echo "✅ Deploy complete!" +echo "🌐 https://wellnuo.smartlaunchhub.com" diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..0d0d999 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,767 @@ +const http = require("http"); + +// WellNuo Mock API - Based on REAL Wellplug sensor capabilities +// Sensors: Radar motion/presence, CO2, Smell (CO, VOC, VSC), Humidity, Light, Temperature, Pressure + +const patientData = { + patient: { + id: "dad", + name: "John Smith", + age: 78, + relationship: "Father", + photo: "https://randomuser.me/api/portraits/men/75.jpg", + address: "123 Oak Street, San Francisco, CA", + timezone: "America/Los_Angeles", + emergency_contact: { + name: "Sarah Smith", + phone: "+1 (555) 123-4567", + relationship: "Daughter" + }, + secondary_contact: { + name: "Michael Smith", + phone: "+1 (555) 987-6543", + relationship: "Son" + } + }, + + // Current real-time status from sensors + current_status: { + location: "Living Room", + presence_detected: true, + is_moving: false, + last_movement: "2025-12-01T10:28:00Z", + estimated_activity: "resting", // inferred from motion patterns + time_in_current_room_minutes: 45 + }, + + // Environment data from Wellplug sensors + environment: { + living_room: { + temperature_c: 22.2, + temperature_f: 72, + humidity_percent: 45, + co2_ppm: 650, + voc_index: 85, // Volatile Organic Compounds (0-500 scale) + co_detected: false, + pressure_hpa: 1013, + light_lux: 320, + air_quality_status: "good", + presence: true, + motion_level: "low" + }, + bedroom: { + temperature_c: 20.5, + temperature_f: 69, + humidity_percent: 48, + co2_ppm: 520, + voc_index: 45, + co_detected: false, + pressure_hpa: 1013, + light_lux: 15, + air_quality_status: "excellent", + presence: false, + motion_level: "none" + }, + kitchen: { + temperature_c: 23.8, + temperature_f: 75, + humidity_percent: 52, + co2_ppm: 580, + voc_index: 120, + co_detected: false, + pressure_hpa: 1013, + light_lux: 450, + air_quality_status: "good", + presence: false, + motion_level: "none" + }, + bathroom: { + temperature_c: 21.0, + temperature_f: 70, + humidity_percent: 58, + co2_ppm: 480, + voc_index: 65, + co_detected: false, + pressure_hpa: 1013, + light_lux: 0, + air_quality_status: "good", + presence: false, + motion_level: "none" + } + }, + + // Environment history (hourly readings) + environment_history: { + today: [ + { time: "00:00", room: "bedroom", temp_f: 68, humidity: 50, co2: 480, presence: true, motion: "minimal" }, + { time: "01:00", room: "bedroom", temp_f: 68, humidity: 51, co2: 520, presence: true, motion: "none" }, + { time: "02:00", room: "bedroom", temp_f: 67, humidity: 52, co2: 540, presence: true, motion: "none" }, + { time: "03:00", room: "bedroom", temp_f: 67, humidity: 52, co2: 560, presence: true, motion: "minimal" }, + { time: "03:15", room: "bathroom", temp_f: 68, humidity: 55, co2: 450, presence: true, motion: "detected" }, + { time: "03:20", room: "bedroom", temp_f: 67, humidity: 52, co2: 550, presence: true, motion: "minimal" }, + { time: "04:00", room: "bedroom", temp_f: 66, humidity: 53, co2: 580, presence: true, motion: "none" }, + { time: "05:00", room: "bedroom", temp_f: 66, humidity: 52, co2: 590, presence: true, motion: "none" }, + { time: "05:45", room: "bedroom", temp_f: 67, humidity: 51, co2: 600, presence: true, motion: "active" }, + { time: "06:00", room: "bathroom", temp_f: 68, humidity: 65, co2: 480, presence: true, motion: "active" }, + { time: "06:15", room: "bedroom", temp_f: 68, humidity: 50, co2: 550, presence: true, motion: "active" }, + { time: "07:00", room: "bathroom", temp_f: 70, humidity: 75, co2: 520, presence: true, motion: "active" }, + { time: "07:30", room: "kitchen", temp_f: 70, humidity: 48, co2: 580, presence: true, motion: "active" }, + { time: "08:00", room: "kitchen", temp_f: 72, humidity: 50, co2: 650, voc: 180, presence: true, motion: "active", note: "cooking detected" }, + { time: "08:30", room: "kitchen", temp_f: 74, humidity: 52, co2: 720, voc: 220, presence: true, motion: "moderate" }, + { time: "09:00", room: "kitchen", temp_f: 73, humidity: 50, co2: 680, presence: true, motion: "low" }, + { time: "09:30", room: "living_room", temp_f: 71, humidity: 46, co2: 620, presence: true, motion: "low" }, + { time: "10:00", room: "living_room", temp_f: 72, humidity: 45, co2: 640, presence: true, motion: "minimal" }, + { time: "10:30", room: "living_room", temp_f: 72, humidity: 45, co2: 650, presence: true, motion: "low" } + ] + }, + + // Sleep analysis (derived from motion/presence sensors) + sleep_analysis: { + last_night: { + bed_time_detected: "2025-11-30T22:18:00Z", + wake_time_detected: "2025-12-01T05:45:00Z", + total_hours: 7.45, + quality_score: 78, // based on movement patterns + night_movements: 3, + bathroom_visits: 1, + bathroom_visit_times: ["03:15"], + restlessness_periods: [ + { start: "01:30", end: "01:45", duration_minutes: 15 }, + { start: "04:20", end: "04:35", duration_minutes: 15 } + ], + time_to_fall_asleep_minutes: 18, + bedroom_co2_peak: 600, + bedroom_temp_avg_f: 67 + }, + weekly_average: { + avg_sleep_hours: 7.2, + avg_bathroom_visits: 1.3, + avg_quality_score: 75, + trend: "stable" + }, + patterns: { + typical_bedtime: "22:00-22:30", + typical_waketime: "05:30-06:00", + weekend_variation: "+45 minutes" + } + }, + + // Activity patterns (derived from motion sensors across rooms) + activity_patterns: { + today: { + total_active_minutes: 185, + sedentary_minutes: 240, + rooms_visited: ["Bedroom", "Bathroom", "Kitchen", "Living Room"], + room_time_distribution: { + bedroom: { minutes: 420, percent: 58 }, + living_room: { minutes: 180, percent: 25 }, + kitchen: { minutes: 85, percent: 12 }, + bathroom: { minutes: 35, percent: 5 } + }, + peak_activity_periods: ["07:00-08:30", "09:30-10:00"], + longest_sedentary_period_minutes: 95 + }, + weekly_comparison: { + avg_active_minutes: 195, + trend: "stable", + most_active_day: "Saturday", + least_active_day: "Sunday" + } + }, + + // Real-time events timeline (what sensors actually detect) + recent_events: [ + // December 1st - Today (detailed sensor-based events) + { time: "2025-12-01T10:45:00Z", type: "presence", event: "Continued presence in living room", room: "Living Room", motion_level: "low" }, + { time: "2025-12-01T10:30:00Z", type: "activity", event: "Settled in living room - minimal movement detected", room: "Living Room", duration_minutes: 45 }, + { time: "2025-12-01T10:15:00Z", type: "environment", event: "Kitchen VOC levels returning to normal after cooking", room: "Kitchen", voc_index: 95 }, + { time: "2025-12-01T10:00:00Z", type: "movement", event: "Moved from kitchen to living room", from_room: "Kitchen", to_room: "Living Room" }, + { time: "2025-12-01T09:45:00Z", type: "activity", event: "Active movement in kitchen - likely cleaning up", room: "Kitchen", motion_level: "moderate", duration_minutes: 15 }, + { time: "2025-12-01T09:30:00Z", type: "environment", event: "Kitchen temperature stabilizing after cooking", room: "Kitchen", temp_f: 73, co2: 680 }, + { time: "2025-12-01T09:00:00Z", type: "activity", event: "Reduced movement in kitchen - likely eating", room: "Kitchen", motion_level: "low", duration_minutes: 30 }, + { time: "2025-12-01T08:30:00Z", type: "environment", event: "Elevated VOC and temperature - cooking activity detected", room: "Kitchen", temp_f: 74, voc_index: 220, co2: 720 }, + { time: "2025-12-01T08:00:00Z", type: "activity", event: "Active movement in kitchen - cooking detected", room: "Kitchen", motion_level: "active", duration_minutes: 30 }, + { time: "2025-12-01T07:45:00Z", type: "movement", event: "Moved from bathroom to kitchen", from_room: "Bathroom", to_room: "Kitchen" }, + { time: "2025-12-01T07:30:00Z", type: "environment", event: "Bathroom humidity spike - shower detected", room: "Bathroom", humidity: 78 }, + { time: "2025-12-01T07:00:00Z", type: "activity", event: "Extended presence in bathroom - morning routine", room: "Bathroom", duration_minutes: 30 }, + { time: "2025-12-01T06:45:00Z", type: "movement", event: "Moved from bedroom to bathroom", from_room: "Bedroom", to_room: "Bathroom" }, + { time: "2025-12-01T06:15:00Z", type: "activity", event: "Active movement in bedroom - getting ready", room: "Bedroom", motion_level: "active", duration_minutes: 30 }, + { time: "2025-12-01T06:00:00Z", type: "environment", event: "Bedroom light level increased - blinds opened", room: "Bedroom", light_lux: 180 }, + { time: "2025-12-01T05:45:00Z", type: "wake", event: "Wake-up detected - active movement began", room: "Bedroom", confidence: "high" }, + { time: "2025-12-01T05:30:00Z", type: "sleep", event: "Pre-wake restlessness detected", room: "Bedroom", motion_level: "minimal" }, + { time: "2025-12-01T03:20:00Z", type: "movement", event: "Returned to bedroom from bathroom", from_room: "Bathroom", to_room: "Bedroom" }, + { time: "2025-12-01T03:15:00Z", type: "bathroom_visit", event: "Nighttime bathroom visit detected", room: "Bathroom", duration_minutes: 5 }, + { time: "2025-12-01T03:12:00Z", type: "movement", event: "Nighttime movement - went to bathroom", from_room: "Bedroom", to_room: "Bathroom" }, + { time: "2025-12-01T01:30:00Z", type: "sleep", event: "Restlessness detected during sleep", room: "Bedroom", duration_minutes: 15 }, + + // November 30th - Yesterday + { time: "2025-11-30T22:18:00Z", type: "sleep", event: "Sleep onset detected - minimal movement began", room: "Bedroom", confidence: "high" }, + { time: "2025-11-30T22:00:00Z", type: "environment", event: "Bedroom lights turned off", room: "Bedroom", light_lux: 0 }, + { time: "2025-11-30T21:45:00Z", type: "movement", event: "Moved from living room to bedroom", from_room: "Living Room", to_room: "Bedroom" }, + { time: "2025-11-30T21:30:00Z", type: "activity", event: "Low activity in living room - likely watching TV", room: "Living Room", motion_level: "minimal", duration_minutes: 90 }, + { time: "2025-11-30T20:00:00Z", type: "movement", event: "Returned to living room from kitchen", from_room: "Kitchen", to_room: "Living Room" }, + { time: "2025-11-30T19:30:00Z", type: "activity", event: "Active in kitchen - dinner preparation and eating", room: "Kitchen", duration_minutes: 45 }, + { time: "2025-11-30T19:00:00Z", type: "environment", event: "Kitchen VOC elevated - cooking detected", room: "Kitchen", voc_index: 195, temp_f: 76 }, + { time: "2025-11-30T18:30:00Z", type: "movement", event: "Moved to kitchen from living room", from_room: "Living Room", to_room: "Kitchen" }, + { time: "2025-11-30T17:00:00Z", type: "activity", event: "Extended sedentary period in living room", room: "Living Room", duration_minutes: 120, motion_level: "minimal" }, + { time: "2025-11-30T16:30:00Z", type: "activity", event: "Afternoon rest detected - very low movement", room: "Bedroom", duration_minutes: 45, motion_level: "none" }, + { time: "2025-11-30T16:00:00Z", type: "movement", event: "Moved to bedroom", from_room: "Living Room", to_room: "Bedroom" }, + { time: "2025-11-30T15:00:00Z", type: "activity", event: "Low activity in living room", room: "Living Room", duration_minutes: 60, motion_level: "low" }, + { time: "2025-11-30T14:30:00Z", type: "bathroom_visit", event: "Bathroom visit detected", room: "Bathroom", duration_minutes: 8 }, + { time: "2025-11-30T13:00:00Z", type: "activity", event: "Moderate activity in kitchen - lunch", room: "Kitchen", duration_minutes: 35 }, + { time: "2025-11-30T12:00:00Z", type: "activity", event: "Low movement in living room", room: "Living Room", duration_minutes: 60 }, + { time: "2025-11-30T10:30:00Z", type: "movement", event: "Moving between rooms - general activity", rooms: ["Kitchen", "Living Room", "Bathroom"] }, + { time: "2025-11-30T09:00:00Z", type: "activity", event: "Morning routine completed - active movement", room: "Kitchen", duration_minutes: 45 }, + { time: "2025-11-30T07:00:00Z", type: "environment", event: "Bathroom humidity spike - shower", room: "Bathroom", humidity: 82 }, + { time: "2025-11-30T06:00:00Z", type: "wake", event: "Wake-up detected", room: "Bedroom", confidence: "high" }, + + // November 29th + { time: "2025-11-29T22:30:00Z", type: "sleep", event: "Sleep onset detected", room: "Bedroom" }, + { time: "2025-11-29T19:00:00Z", type: "activity", event: "Extended presence with occasional movement - possible visitor", room: "Living Room", duration_minutes: 90, note: "unusual pattern - social activity suspected" }, + { time: "2025-11-29T15:00:00Z", type: "activity", event: "Moderate activity period", room: "Living Room", duration_minutes: 45 }, + { time: "2025-11-29T12:00:00Z", type: "activity", event: "Active in kitchen - meal preparation", room: "Kitchen", duration_minutes: 40 }, + { time: "2025-11-29T06:15:00Z", type: "wake", event: "Wake-up detected", room: "Bedroom" }, + { time: "2025-11-29T02:45:00Z", type: "bathroom_visit", event: "Nighttime bathroom visit", room: "Bathroom", duration_minutes: 6 }, + + // November 28th - Thanksgiving (unusual patterns) + { time: "2025-11-28T23:00:00Z", type: "sleep", event: "Late sleep onset - unusual", room: "Bedroom", note: "2 hours later than typical" }, + { time: "2025-11-28T18:00:00Z", type: "activity", event: "High activity in kitchen - extended cooking", room: "Kitchen", duration_minutes: 180, motion_level: "very_active" }, + { time: "2025-11-28T17:00:00Z", type: "environment", event: "Kitchen temperature elevated - 82°F", room: "Kitchen", temp_f: 82, alert_generated: true }, + { time: "2025-11-28T16:30:00Z", type: "environment", event: "High VOC levels in kitchen", room: "Kitchen", voc_index: 280, note: "extended cooking" }, + { time: "2025-11-28T14:00:00Z", type: "activity", event: "Multiple people presence pattern detected", room: "Living Room", note: "unusual movement patterns - visitors likely" }, + { time: "2025-11-28T09:00:00Z", type: "activity", event: "Earlier than usual kitchen activity", room: "Kitchen", note: "holiday preparation" }, + + // November 27th + { time: "2025-11-27T22:15:00Z", type: "sleep", event: "Sleep onset detected", room: "Bedroom" }, + { time: "2025-11-27T16:00:00Z", type: "activity", event: "Extended sedentary period", room: "Living Room", duration_minutes: 150 }, + { time: "2025-11-27T10:00:00Z", type: "environment", event: "Low CO2 - windows likely opened", room: "Living Room", co2: 380 }, + { time: "2025-11-27T06:30:00Z", type: "wake", event: "Wake-up detected", room: "Bedroom" }, + + // November 26th + { time: "2025-11-26T22:00:00Z", type: "sleep", event: "Sleep onset detected", room: "Bedroom" }, + { time: "2025-11-26T18:00:00Z", type: "activity", event: "Extended low movement period - likely watching TV", room: "Living Room", duration_minutes: 180 }, + { time: "2025-11-26T14:00:00Z", type: "activity", event: "Unusual activity pattern - movement to garage area", note: "outdoor/garage activity suspected" }, + { time: "2025-11-26T06:00:00Z", type: "wake", event: "Wake-up detected", room: "Bedroom" }, + + // November 25th + { time: "2025-11-25T22:30:00Z", type: "sleep", event: "Late sleep onset", room: "Bedroom" }, + { time: "2025-11-25T10:00:00Z", type: "environment", event: "Low humidity alert - 25%", room: "Living Room", humidity: 25, alert_generated: true }, + { time: "2025-11-25T05:30:00Z", type: "wake", event: "Early wake-up detected", room: "Bedroom", note: "1 hour earlier than typical" }, + { time: "2025-11-25T04:00:00Z", type: "bathroom_visit", event: "Nighttime bathroom visit", room: "Bathroom" }, + { time: "2025-11-25T02:30:00Z", type: "bathroom_visit", event: "Nighttime bathroom visit", room: "Bathroom", note: "second visit - unusual" }, + + // November 24th + { time: "2025-11-24T22:00:00Z", type: "sleep", event: "Sleep onset detected", room: "Bedroom" }, + { time: "2025-11-24T15:00:00Z", type: "activity", event: "No presence detected for 2 hours", note: "likely out of home" }, + { time: "2025-11-24T11:00:00Z", type: "environment", event: "Extended high VOC in kitchen", room: "Kitchen", voc_index: 245, duration_minutes: 90, note: "baking activity" }, + { time: "2025-11-24T06:00:00Z", type: "wake", event: "Wake-up detected", room: "Bedroom" }, + + // November 23rd + { time: "2025-11-23T22:15:00Z", type: "sleep", event: "Sleep onset detected", room: "Bedroom" }, + { time: "2025-11-23T03:30:00Z", type: "bathroom_visit", event: "Nighttime bathroom visit", room: "Bathroom" }, + + // November 22nd + { time: "2025-11-22T23:50:00Z", type: "environment", event: "Back door sensor triggered late", note: "door activity detected at unusual hour" }, + { time: "2025-11-22T22:00:00Z", type: "sleep", event: "Sleep onset detected", room: "Bedroom" }, + + // November 21st + { time: "2025-11-21T12:00:00Z", type: "environment", event: "Poor air quality detected - AQI 95", room: "Kitchen", voc_index: 340, co2: 920, alert_generated: true, note: "burnt food detected" }, + { time: "2025-11-21T06:15:00Z", type: "wake", event: "Wake-up detected", room: "Bedroom" }, + + // November 20th + { time: "2025-11-20T22:00:00Z", type: "sleep", event: "Sleep onset detected", room: "Bedroom" }, + { time: "2025-11-20T15:00:00Z", type: "activity", event: "No movement for 3 hours - extended rest", room: "Bedroom", alert_generated: true }, + { time: "2025-11-20T06:00:00Z", type: "wake", event: "Wake-up detected", room: "Bedroom" }, + + // November 15th - fall incident + { time: "2025-11-15T14:35:00Z", type: "movement", event: "Normal movement resumed", room: "Bathroom" }, + { time: "2025-11-15T14:22:00Z", type: "fall_detected", event: "Sudden movement pattern change - possible fall", room: "Bathroom", alert_generated: true, severity: "high", confidence: "medium" }, + { time: "2025-11-15T14:20:00Z", type: "presence", event: "Presence in bathroom", room: "Bathroom" }, + + // November 10th + { time: "2025-11-10T22:40:00Z", type: "movement", event: "Returned inside", room: "Living Room" }, + { time: "2025-11-10T22:30:00Z", type: "environment", event: "Unusual - presence lost from all indoor sensors", alert_generated: true, note: "left home perimeter at night" } + ], + + // Alerts generated by the system + alerts: { + active: [], + recent: [ + { + id: "alert_001", + type: "possible_fall", + severity: "high", + timestamp: "2025-11-15T14:22:00Z", + room: "Bathroom", + description: "Unusual motion pattern detected - sudden downward movement followed by minimal activity", + sensor_data: { motion_change: "rapid_decrease", duration_still: "8 minutes" }, + resolved: true, + resolved_at: "2025-11-15T14:35:00Z", + resolution_note: "Movement resumed normally, family confirmed via phone call - minor stumble, no injury" + }, + { + id: "alert_002", + type: "extended_inactivity", + severity: "medium", + timestamp: "2025-11-20T15:00:00Z", + room: "Bedroom", + description: "No movement detected for 3 hours during daytime - unusual pattern", + sensor_data: { inactive_duration_minutes: 180, typical_max: 90 }, + resolved: true, + resolved_at: "2025-11-20T16:30:00Z", + resolution_note: "Extended afternoon nap - had poor sleep previous night" + }, + { + id: "alert_003", + type: "air_quality", + severity: "medium", + timestamp: "2025-11-21T12:00:00Z", + room: "Kitchen", + description: "Poor air quality detected - high VOC and CO2 levels", + sensor_data: { voc_index: 340, co2_ppm: 920, normal_voc: 100, normal_co2: 600 }, + resolved: true, + resolved_at: "2025-11-21T13:30:00Z", + resolution_note: "Burnt toast - window opened, levels returned to normal" + }, + { + id: "alert_004", + type: "temperature_anomaly", + severity: "medium", + timestamp: "2025-11-28T17:00:00Z", + room: "Kitchen", + description: "Room temperature elevated to 82°F - potential stove/oven left on", + sensor_data: { temperature_f: 82, typical_max: 76, voc_elevated: true }, + resolved: true, + resolved_at: "2025-11-28T17:30:00Z", + resolution_note: "Thanksgiving cooking - oven in use for extended period, normal activity" + }, + { + id: "alert_005", + type: "low_humidity", + severity: "low", + timestamp: "2025-11-25T10:00:00Z", + room: "Living Room", + description: "Indoor humidity dropped to 25% - very dry conditions", + sensor_data: { humidity_percent: 25, recommended_min: 35 }, + resolved: true, + resolved_at: "2025-11-25T14:00:00Z", + resolution_note: "Dry winter day with heating - humidifier recommended" + }, + { + id: "alert_006", + type: "night_wandering", + severity: "high", + timestamp: "2025-11-10T22:30:00Z", + description: "Presence lost from all indoor sensors at unusual hour", + sensor_data: { last_presence_room: "Living Room", duration_absent_minutes: 10 }, + resolved: true, + resolved_at: "2025-11-10T22:40:00Z", + resolution_note: "Went outside briefly - thought he heard something. Returned safely." + }, + { + id: "alert_007", + type: "frequent_bathroom_visits", + severity: "low", + timestamp: "2025-11-25T04:00:00Z", + description: "Multiple nighttime bathroom visits detected (2 in one night)", + sensor_data: { visits: 2, typical_max: 1 }, + resolved: true, + resolution_note: "Drank extra fluids before bed. Monitoring for pattern." + }, + { + id: "alert_008", + type: "unusual_schedule", + severity: "low", + timestamp: "2025-11-28T23:00:00Z", + room: "Bedroom", + description: "Sleep onset 2 hours later than typical", + sensor_data: { actual_time: "23:00", typical_time: "22:00" }, + resolved: true, + resolution_note: "Thanksgiving - family gathering, expected deviation" + }, + { + id: "alert_009", + type: "elevated_co2", + severity: "low", + timestamp: "2025-11-30T02:00:00Z", + room: "Bedroom", + description: "Bedroom CO2 levels elevated during sleep - 720ppm", + sensor_data: { co2_ppm: 720, recommended_max: 600 }, + resolved: true, + resolution_note: "Suggest opening window slightly before bed for better ventilation" + } + ], + statistics: { + total_this_month: 9, + by_severity: { high: 2, medium: 3, low: 4 }, + by_type: { + possible_fall: 1, + extended_inactivity: 1, + air_quality: 1, + temperature_anomaly: 1, + night_wandering: 1, + frequent_bathroom_visits: 1, + unusual_schedule: 1, + low_humidity: 1, + elevated_co2: 1 + }, + average_resolution_time_minutes: 45 + } + }, + + // Daily patterns derived from sensor data + daily_patterns: { + typical_schedule: { + wake_time: { average: "06:00", range: "05:30-06:30" }, + morning_routine: { duration_minutes: 90, rooms: ["Bedroom", "Bathroom", "Kitchen"] }, + breakfast_time: { average: "08:00", duration_minutes: 45 }, + active_periods: ["07:00-09:00", "11:00-12:00", "17:00-18:00"], + sedentary_periods: ["10:00-11:00", "14:00-16:00", "20:00-22:00"], + afternoon_rest: { typical_time: "15:00-16:30", frequency: "daily" }, + dinner_time: { average: "19:00", duration_minutes: 45 }, + evening_routine: { start: "21:00", rooms: ["Living Room", "Bathroom", "Bedroom"] }, + bed_time: { average: "22:15", range: "22:00-22:30" } + }, + bathroom_patterns: { + typical_daily_visits: 6, + typical_night_visits: 1, + average_duration_minutes: 8, + shower_detected_days: ["daily"], + typical_shower_time: "07:00" + }, + cooking_patterns: { + breakfast: { detected: true, typical_time: "08:00", voc_spike: "moderate" }, + lunch: { detected: true, typical_time: "13:00", voc_spike: "low" }, + dinner: { detected: true, typical_time: "19:00", voc_spike: "moderate" } + } + }, + + // Monthly summaries + monthly_summaries: [ + { + month: "November 2025", + sleep: { + avg_hours: 7.2, + avg_quality_score: 76, + avg_bathroom_visits_per_night: 1.2, + nights_with_poor_sleep: 3 + }, + activity: { + avg_active_minutes: 190, + avg_sedentary_minutes: 480, + most_active_room: "Kitchen", + days_with_low_activity: 4 + }, + environment: { + avg_indoor_temp_f: 71, + avg_humidity: 44, + avg_co2: 580, + air_quality_alerts: 2 + }, + alerts_count: 9, + patterns: { + schedule_consistency: "good", + notable_deviations: ["Thanksgiving day - late sleep, high kitchen activity", "Nov 25 - early wake, multiple bathroom visits"] + }, + highlights: [ + "Consistent morning routine maintained", + "One possible fall incident - resolved with no injury", + "Thanksgiving showed expected schedule changes", + "Sleep quality generally good with occasional restless nights" + ], + concerns: [ + "Bathroom fall risk - grab bars recommended", + "Occasional high CO2 in bedroom at night", + "One day with extended inactivity" + ] + }, + { + month: "October 2025", + sleep: { + avg_hours: 7.0, + avg_quality_score: 78, + avg_bathroom_visits_per_night: 1.0, + nights_with_poor_sleep: 2 + }, + activity: { + avg_active_minutes: 205, + avg_sedentary_minutes: 460, + most_active_room: "Kitchen", + days_with_low_activity: 2 + }, + environment: { + avg_indoor_temp_f: 70, + avg_humidity: 48, + avg_co2: 560, + air_quality_alerts: 0 + }, + alerts_count: 3, + patterns: { + schedule_consistency: "excellent", + notable_deviations: ["Oct 15-17 - visitors present, different patterns"] + }, + highlights: [ + "Very consistent daily patterns", + "Good activity levels", + "No significant environmental issues" + ], + concerns: [] + } + ], + + // Wellplug device info + devices: { + wellplugs: [ + { id: "wp_001", location: "Living Room", status: "active", last_reading: "2025-12-01T10:45:00Z", firmware: "2.1.4" }, + { id: "wp_002", location: "Bedroom", status: "active", last_reading: "2025-12-01T10:45:00Z", firmware: "2.1.4" }, + { id: "wp_003", location: "Kitchen", status: "active", last_reading: "2025-12-01T10:45:00Z", firmware: "2.1.4" }, + { id: "wp_004", location: "Bathroom", status: "active", last_reading: "2025-12-01T10:45:00Z", firmware: "2.1.4" } + ], + sensors_per_device: ["radar_motion", "radar_presence", "co2", "voc", "co", "humidity", "temperature", "pressure", "light"], + connectivity: "wifi", + data_sync_interval_seconds: 60 + }, + + // System prompt for Julia AI + system_prompt: `You are Julia, a warm and caring AI assistant for the WellNuo family care system. You help family members stay connected with and understand the wellbeing of their elderly loved ones through ambient sensor data. + +IMPORTANT - Data Sources: +WellNuo uses ambient sensors (Wellplug devices) that monitor: motion/presence, CO2, air quality (VOC, CO), humidity, temperature, pressure, and light. There are NO wearables, cameras, or microphones. + +What you CAN discuss (sensor-detected): +- Movement patterns and room locations +- Sleep timing and quality (from motion patterns) +- Bathroom visit frequency and timing +- Cooking activity (from VOC/temperature) +- Air quality, temperature, humidity +- Daily routine consistency +- Unusual patterns or deviations + +What you CANNOT know (no sensors for this): +- Specific activities (reading, watching TV, eating specific foods) +- Health vitals (heart rate, blood pressure, blood oxygen) +- Medication intake +- Phone calls or social interactions (unless visitor presence inferred) +- Emotional state or mood +- What they ate or drank + +Guidelines: +- Be warm, conversational, and reassuring +- Describe what sensors detected, not assumptions about activities +- Say "movement patterns suggest..." not "he was watching TV" +- Lead with positive patterns before concerns +- Provide context for alerts (most are resolved, minor issues) +- If asked about something sensors can't detect, explain kindly: "Our sensors monitor movement and environment, so I can't tell you exactly what he's doing, but I can see he's been active in the living room for the past hour" +- Keep responses concise but caring`, + + meta: { + api_version: "2.0.0", + data_source: "Wellplug ambient sensors", + sensors: ["radar_motion", "radar_presence", "co2", "voc", "co", "humidity", "temperature", "pressure", "light"], + no_wearables: true, + no_cameras: true, + no_microphones: true, + total_events: 68, + total_alerts: 9, + data_range: "2025-10-01 to 2025-12-01", + last_updated: new Date().toISOString() + } +}; + +// Landing page +const landingPage = ` + + + WellNuo Mock API + + + +

WellNuo Mock API v2.0

+

Realistic demo API based on actual Wellplug sensor capabilities.

+ +
+ ⚠️ Sensor-Based Data Only
+ This API reflects what Wellplug sensors can actually detect. No wearables, cameras, or microphones. +
+ +

Wellplug Sensors

+
+ 🎯 Radar Motion + 👤 Presence Detection + 💨 CO2 + 🌡️ Temperature + 💧 Humidity + 🔬 VOC (Smells) + ⚠️ CO Detection + 📊 Pressure + 💡 Light Level +
+ +
+
68
Events
+
9
Alerts
+
4
Rooms
+
2
Months
+
+ +
+

GET /api/patient/context

+

Returns sensor-based patient context for Julia AI.

+

Try it →

+
+ +

What's Detected

+
+
    +
  • Movement & Presence - Room-by-room tracking, activity levels
  • +
  • Sleep Patterns - Bed/wake times, night movements, bathroom visits
  • +
  • Daily Routine - Schedule consistency, deviations
  • +
  • Cooking Activity - VOC/temperature spikes in kitchen
  • +
  • Air Quality - CO2, VOC, humidity alerts
  • +
  • Environmental - Temperature, humidity, pressure per room
  • +
  • Anomalies - Falls, inactivity, night wandering
  • +
+
+ +

What's NOT Detected

+
+
    +
  • ❌ Heart rate, blood pressure, vitals (no wearables)
  • +
  • ❌ Medication intake
  • +
  • ❌ Specific activities (reading, TV, phone calls)
  • +
  • ❌ What they ate or drank
  • +
  • ❌ Conversations or social interactions
  • +
+
+ +
+

Example

+
curl https://wellnuo.smartlaunchhub.com/api/patient/context
+
+ +`; + +const fs = require("fs"); +const path = require("path"); + +const PORT = process.env.PORT || 5011; +const CONVERSATIONS_FILE = path.join(__dirname, "conversations.json"); + +// Загрузить существующие разговоры +function loadConversations() { + try { + if (fs.existsSync(CONVERSATIONS_FILE)) { + return JSON.parse(fs.readFileSync(CONVERSATIONS_FILE, "utf8")); + } + } catch (error) { + console.error("Error loading conversations:", error); + } + return []; +} + +// Сохранить разговоры +function saveConversations(conversations) { + try { + fs.writeFileSync(CONVERSATIONS_FILE, JSON.stringify(conversations, null, 2)); + } catch (error) { + console.error("Error saving conversations:", error); + } +} + +const server = http.createServer((req, res) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); + + if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } + + const url = req.url; + + if (url === "/" || url === "") { + res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); + res.end(landingPage); + return; + } + + if (url === "/api/patient/context" || url.match(/^\/api\/patient\/[^\/]+\/context$/)) { + const response = JSON.parse(JSON.stringify(patientData)); + const now = new Date(); + response.current_status.last_movement = new Date(now - 5 * 60000).toISOString(); + response.meta.last_updated = now.toISOString(); + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify(response, null, 2)); + return; + } + + // POST /api/conversations - сохранение истории разговора по схеме + if (url === "/api/conversations" && req.method === "POST") { + let body = ""; + req.on("data", chunk => { body += chunk.toString(); }); + req.on("end", () => { + try { + const conversation = JSON.parse(body); + + // Валидация + if (!conversation.session_id || !conversation.messages) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Missing required fields: session_id, messages" })); + return; + } + + // Добавить timestamp если нет + conversation.saved_at = new Date().toISOString(); + + // Загрузить, добавить, сохранить + const conversations = loadConversations(); + conversations.push(conversation); + saveConversations(conversations); + + console.log(`Conversation saved: ${conversation.session_id}, ${conversation.messages.length} messages`); + + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ + success: true, + session_id: conversation.session_id, + message: "Conversation saved successfully" + })); + } catch (error) { + console.error("Error saving conversation:", error); + res.writeHead(500, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Failed to save conversation" })); + } + }); + return; + } + + // GET /api/conversations - получить все разговоры (для отладки) + if (url === "/api/conversations" && req.method === "GET") { + const conversations = loadConversations(); + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ + count: conversations.length, + conversations: conversations.slice(-10) // последние 10 + })); + return; + } + + res.writeHead(404, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "Not found", available_endpoints: ["/api/patient/context", "/api/conversations"] })); +}); + +server.listen(PORT, () => console.log(`WellNuo Mock API v2.0 running on port ${PORT}`)); diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..a984986 --- /dev/null +++ b/eas.json @@ -0,0 +1,27 @@ +{ + "cli": { + "version": ">= 3.0.0", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal", + "ios": { + "simulator": true + } + }, + "preview": { + "distribution": "internal", + "ios": { + "simulator": false + } + }, + "production": { + "autoIncrement": true, + "ios": { + "resourceClass": "m-medium" + } + } + } +} diff --git a/index.ts b/index.tsx similarity index 55% rename from index.ts rename to index.tsx index 1d6e981..21cdb78 100644 --- a/index.ts +++ b/index.tsx @@ -1,8 +1,18 @@ import { registerRootComponent } from 'expo'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import React from 'react'; import App from './App'; +function Root() { + return ( + + + + ); +} + // 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); +registerRootComponent(Root); diff --git a/package-lock.json b/package-lock.json index 88e481f..1e86bac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,24 @@ "name": "wellnuo", "version": "1.0.0", "dependencies": { + "@react-native-async-storage/async-storage": "^2.2.0", + "eas-cli": "^16.28.0", "expo": "~54.0.25", + "expo-av": "~16.0.7", + "expo-dev-client": "~6.0.18", + "expo-linear-gradient": "~15.0.7", "expo-status-bar": "~3.0.8", "react": "19.1.0", - "react-native": "0.81.5" + "react-native": "0.81.5", + "react-native-dotenv": "^3.4.11", + "react-native-safe-area-context": "^5.6.2", + "react-native-webview": "^13.16.0", + "zaiclient": "^8.2.0" }, "devDependencies": { + "@playwright/test": "^1.57.0", "@types/react": "~19.1.0", + "babel-preset-expo": "^54.0.7", "typescript": "~5.9.2" } }, @@ -1519,6 +1530,58 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@expo/apple-utils": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@expo/apple-utils/-/apple-utils-2.1.12.tgz", + "integrity": "sha512-ugpL2URxNFxIRw943AIpX3dcvL5rhNCumpL8XTosYqZPyQQ7JRLVNd1m8FNyPg3pmFrz8M4tSucY2pt2ATuKOA==", + "license": "MIT", + "bin": { + "apple-utils": "bin.js" + } + }, + "node_modules/@expo/bunyan": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.1.tgz", + "integrity": "sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==", + "license": "MIT", + "dependencies": { + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@expo/bunyan/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@expo/code-signing-certificates": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.5.tgz", @@ -1759,6 +1822,260 @@ "node": ">=8" } }, + "node_modules/@expo/eas-build-job": { + "version": "1.0.243", + "resolved": "https://registry.npmjs.org/@expo/eas-build-job/-/eas-build-job-1.0.243.tgz", + "integrity": "sha512-wLU9q0pzIKibr4jLMV/U6BYszk5T85p/WmRYUIqH7nhmy2dDUJHz0WlP/i/ZWAsnrHplwidbCZvZGoxwgJsISA==", + "license": "MIT", + "dependencies": { + "@expo/logger": "1.0.221", + "joi": "^17.13.1", + "semver": "^7.6.2", + "zod": "^4.1.3" + } + }, + "node_modules/@expo/eas-build-job/node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/@expo/eas-build-job/node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@expo/eas-json": { + "version": "16.25.0", + "resolved": "https://registry.npmjs.org/@expo/eas-json/-/eas-json-16.25.0.tgz", + "integrity": "sha512-f81xHXW/MYY2Y00uASb81Jhnw3629tDzxlnzyHTyc7zTGdWJ2gVg4HkSw2iVonP66Ddazlt4175iUE4cZd3jPA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "7.23.5", + "@expo/eas-build-job": "1.0.231", + "chalk": "4.1.2", + "env-string": "1.0.1", + "fs-extra": "11.2.0", + "golden-fleece": "1.0.9", + "joi": "17.11.0", + "log-symbols": "4.1.0", + "semver": "7.5.2", + "terminal-link": "2.1.1", + "tslib": "2.4.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@expo/eas-json/node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@expo/eas-json/node_modules/@babel/code-frame/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/@expo/eas-json/node_modules/@expo/eas-build-job": { + "version": "1.0.231", + "resolved": "https://registry.npmjs.org/@expo/eas-build-job/-/eas-build-job-1.0.231.tgz", + "integrity": "sha512-jktsZ7IJYcC9lbgUWftbFCpfrhcGwJX4q0qSubUrqvFTMuUyLjCzJqyIZ4znbXLXfJSUPDJNAd4H0wBqKJA9kA==", + "license": "MIT", + "dependencies": { + "@expo/logger": "1.0.221", + "joi": "^17.13.1", + "semver": "^7.6.2", + "zod": "^4.1.3" + } + }, + "node_modules/@expo/eas-json/node_modules/@expo/eas-build-job/node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/@expo/eas-json/node_modules/@expo/eas-build-job/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/eas-json/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/@expo/eas-json/node_modules/chalk/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/@expo/eas-json/node_modules/chalk/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/@expo/eas-json/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/@expo/eas-json/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/@expo/eas-json/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/@expo/eas-json/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@expo/eas-json/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/eas-json/node_modules/semver": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/eas-json/node_modules/tslib": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", + "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "license": "0BSD" + }, + "node_modules/@expo/eas-json/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/@expo/eas-json/node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/@expo/env": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.0.7.tgz", @@ -2032,6 +2349,16 @@ "json5": "^2.2.3" } }, + "node_modules/@expo/logger": { + "version": "1.0.221", + "resolved": "https://registry.npmjs.org/@expo/logger/-/logger-1.0.221.tgz", + "integrity": "sha512-arNkhzq4Ludz9J7PIYWTVYXuT1bO/A8prR8HYAviKuF6fEZfitemNVriGSetQASOE8Vnv3lmBZv6YIwLiFuv/Q==", + "license": "BUSL-1.1", + "dependencies": { + "@types/bunyan": "^1.8.11", + "bunyan": "^1.8.15" + } + }, "node_modules/@expo/mcp-tunnel": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/@expo/mcp-tunnel/-/mcp-tunnel-0.1.0.tgz", @@ -2092,6 +2419,15 @@ "metro-transform-worker": "0.83.2" } }, + "node_modules/@expo/multipart-body-parser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@expo/multipart-body-parser/-/multipart-body-parser-2.0.0.tgz", + "integrity": "sha512-yS/wsqlj0d8ZKETEN7ro3dZtjdMhpte8wp+xUzjUQC3jizxcE0E62xgvGquJObiYUMGoCF5qRYr2t78STPEaSw==", + "license": "MIT", + "dependencies": { + "multipasta": "^0.2.5" + } + }, "node_modules/@expo/osascript": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.3.7.tgz", @@ -2189,6 +2525,15 @@ "node": ">=8" } }, + "node_modules/@expo/pkcs12": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@expo/pkcs12/-/pkcs12-0.1.3.tgz", + "integrity": "sha512-96MePEGppKi08vawrTPw8kMCRdsbrDbV900MlI8rrP9F57DfDl/y1P52bwIDBYCEHE3XtPMo7s1xkG0BKOLCVg==", + "license": "MIT", + "dependencies": { + "node-forge": "^1.2.1" + } + }, "node_modules/@expo/plist": { "version": "0.4.7", "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.4.7.tgz", @@ -2200,6 +2545,711 @@ "xmlbuilder": "^15.1.1" } }, + "node_modules/@expo/plugin-help": { + "version": "5.1.23", + "resolved": "https://registry.npmjs.org/@expo/plugin-help/-/plugin-help-5.1.23.tgz", + "integrity": "sha512-s0uH6cPplLj73ZVie40EYUhl7X7q9kRR+8IfZWDod3wUtVGOFInxuCPX9Jpv1UwwBgbRu2cLisqr8m45LrFgxw==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^2.11.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@expo/plugin-help/node_modules/@oclif/core": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.16.0.tgz", + "integrity": "sha512-dL6atBH0zCZl1A1IXCKJgLPrM/wR7K+Wi401E/IvqsK8m2iCHW+0TEOGrans/cuN3oTW+uxIyJFHJ8Im0k4qBw==", + "license": "MIT", + "dependencies": { + "@types/cli-progress": "^3.11.0", + "ansi-escapes": "^4.3.2", + "ansi-styles": "^4.3.0", + "cardinal": "^2.1.1", + "chalk": "^4.1.2", + "clean-stack": "^3.0.1", + "cli-progress": "^3.12.0", + "debug": "^4.3.4", + "ejs": "^3.1.8", + "get-package-type": "^0.1.0", + "globby": "^11.1.0", + "hyperlinker": "^1.0.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "js-yaml": "^3.14.1", + "natural-orderby": "^2.0.3", + "object-treeify": "^1.1.33", + "password-prompt": "^1.1.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "supports-color": "^8.1.1", + "supports-hyperlinks": "^2.2.0", + "ts-node": "^10.9.1", + "tslib": "^2.5.0", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@expo/plugin-help/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/@expo/plugin-help/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/@expo/plugin-help/node_modules/chalk/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/@expo/plugin-help/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/@expo/plugin-help/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/@expo/plugin-help/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/@expo/plugin-help/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/@expo/plugin-help/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/@expo/plugin-help/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/@expo/plugin-help/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/@expo/plugin-help/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/@expo/plugin-warn-if-update-available": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@expo/plugin-warn-if-update-available/-/plugin-warn-if-update-available-2.5.1.tgz", + "integrity": "sha512-B65QSIZ+TgFHnVXsTw+1Q6djsJByWwnIjYfoG8ZV9wizOC01gbAw1cOZ/YtrJ2BrDnzFQtM8qecjlmZ7C3MPLw==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^2.11.1", + "chalk": "^4.1.0", + "debug": "^4.3.4", + "ejs": "^3.1.7", + "fs-extra": "^10.1.0", + "http-call": "^5.2.2", + "semver": "^7.3.7", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@expo/plugin-warn-if-update-available/node_modules/@oclif/core": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.16.0.tgz", + "integrity": "sha512-dL6atBH0zCZl1A1IXCKJgLPrM/wR7K+Wi401E/IvqsK8m2iCHW+0TEOGrans/cuN3oTW+uxIyJFHJ8Im0k4qBw==", + "license": "MIT", + "dependencies": { + "@types/cli-progress": "^3.11.0", + "ansi-escapes": "^4.3.2", + "ansi-styles": "^4.3.0", + "cardinal": "^2.1.1", + "chalk": "^4.1.2", + "clean-stack": "^3.0.1", + "cli-progress": "^3.12.0", + "debug": "^4.3.4", + "ejs": "^3.1.8", + "get-package-type": "^0.1.0", + "globby": "^11.1.0", + "hyperlinker": "^1.0.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "js-yaml": "^3.14.1", + "natural-orderby": "^2.0.3", + "object-treeify": "^1.1.33", + "password-prompt": "^1.1.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "supports-color": "^8.1.1", + "supports-hyperlinks": "^2.2.0", + "ts-node": "^10.9.1", + "tslib": "^2.5.0", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@expo/plugin-warn-if-update-available/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/@expo/plugin-warn-if-update-available/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/@expo/plugin-warn-if-update-available/node_modules/chalk/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/@expo/plugin-warn-if-update-available/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/@expo/plugin-warn-if-update-available/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/@expo/plugin-warn-if-update-available/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/@expo/plugin-warn-if-update-available/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@expo/plugin-warn-if-update-available/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/@expo/plugin-warn-if-update-available/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/@expo/plugin-warn-if-update-available/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/@expo/plugin-warn-if-update-available/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/@expo/plugin-warn-if-update-available/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/@expo/prebuild-config": { + "version": "8.0.17", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-8.0.17.tgz", + "integrity": "sha512-HM+XpDox3fAZuXZXvy55VRcBbsZSDijGf8jI8i/pexgWvtsnt1ouelPXRuE1pXDicMX+lZO83QV+XkyLmBEXYQ==", + "license": "MIT", + "dependencies": { + "@expo/config": "~10.0.4", + "@expo/config-plugins": "~9.0.0", + "@expo/config-types": "^52.0.0", + "@expo/image-utils": "^0.6.0", + "@expo/json-file": "^9.0.0", + "@react-native/normalize-colors": "0.76.2", + "debug": "^4.3.1", + "fs-extra": "^9.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.6.0", + "xml2js": "0.6.0" + } + }, + "node_modules/@expo/prebuild-config/node_modules/@expo/config": { + "version": "10.0.11", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-10.0.11.tgz", + "integrity": "sha512-nociJ4zr/NmbVfMNe9j/+zRlt7wz/siISu7PjdWE4WE+elEGxWWxsGzltdJG0llzrM+khx8qUiFK5aiVcdMBww==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "@expo/config-plugins": "~9.0.17", + "@expo/config-types": "^52.0.5", + "@expo/json-file": "^9.0.2", + "deepmerge": "^4.3.1", + "getenv": "^1.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/prebuild-config/node_modules/@expo/config-plugins": { + "version": "9.0.17", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-9.0.17.tgz", + "integrity": "sha512-m24F1COquwOm7PBl5wRbkT9P9DviCXe0D7S7nQsolfbhdCWuvMkfXeoWmgjtdhy7sDlOyIgBrAdnB6MfsWKqIg==", + "license": "MIT", + "dependencies": { + "@expo/config-types": "^52.0.5", + "@expo/json-file": "~9.0.2", + "@expo/plist": "^0.2.2", + "@expo/sdk-runtime-versions": "^1.0.0", + "chalk": "^4.1.2", + "debug": "^4.3.5", + "getenv": "^1.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/prebuild-config/node_modules/@expo/config-plugins/node_modules/@expo/json-file": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.0.2.tgz", + "integrity": "sha512-yAznIUrybOIWp3Uax7yRflB0xsEpvIwIEqIjao9SGi2Gaa+N0OamWfe0fnXBSWF+2zzF4VvqwT4W5zwelchfgw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "json5": "^2.2.3", + "write-file-atomic": "^2.3.0" + } + }, + "node_modules/@expo/prebuild-config/node_modules/@expo/config-types": { + "version": "52.0.5", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-52.0.5.tgz", + "integrity": "sha512-AMDeuDLHXXqd8W+0zSjIt7f37vUd/BP8p43k68NHpyAvQO+z8mbQZm3cNQVAMySeayK2XoPigAFB1JF2NFajaA==", + "license": "MIT" + }, + "node_modules/@expo/prebuild-config/node_modules/@expo/image-utils": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.6.5.tgz", + "integrity": "sha512-RsS/1CwJYzccvlprYktD42KjyfWZECH6PPIEowvoSmXfGLfdViwcUEI4RvBfKX5Jli6P67H+6YmHvPTbGOboew==", + "license": "MIT", + "dependencies": { + "@expo/spawn-async": "^1.7.2", + "chalk": "^4.0.0", + "fs-extra": "9.0.0", + "getenv": "^1.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/prebuild-config/node_modules/@expo/image-utils/node_modules/fs-extra": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.0.tgz", + "integrity": "sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/prebuild-config/node_modules/@expo/image-utils/node_modules/universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@expo/prebuild-config/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/prebuild-config/node_modules/@expo/plist": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.2.2.tgz", + "integrity": "sha512-ZZGvTO6vEWq02UAPs3LIdja+HRO18+LRI5QuDl6Hs3Ps7KX7xU6Y6kjahWKY37Rx2YjNpX07dGpBFzzC+vKa2g==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "~0.7.7", + "base64-js": "^1.2.3", + "xmlbuilder": "^14.0.0" + } + }, + "node_modules/@expo/prebuild-config/node_modules/@react-native/normalize-colors": { + "version": "0.76.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.76.2.tgz", + "integrity": "sha512-ICoOpaTLPsFQjNLSM00NgQr6wal300cZZonHVSDXKntX+BfkLeuCHRtr/Mn+klTtW+/1v2/2FRm9dXjvyGf9Dw==", + "license": "MIT" + }, + "node_modules/@expo/prebuild-config/node_modules/@xmldom/xmldom": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", + "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", + "deprecated": "this version is no longer supported, please update to at least 0.8.*", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@expo/prebuild-config/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/@expo/prebuild-config/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/@expo/prebuild-config/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/@expo/prebuild-config/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/@expo/prebuild-config/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/prebuild-config/node_modules/getenv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", + "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@expo/prebuild-config/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/@expo/prebuild-config/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/@expo/prebuild-config/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/@expo/prebuild-config/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/@expo/prebuild-config/node_modules/xmlbuilder": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", + "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/@expo/results": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@expo/results/-/results-1.0.0.tgz", + "integrity": "sha512-qECzzXX5oJot3m2Gu9pfRDz50USdBieQVwYAzeAtQRUTD3PVeTK1tlRUoDcrK8PSruDLuVYdKkLebX4w/o55VA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/rudder-sdk-node": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@expo/rudder-sdk-node/-/rudder-sdk-node-1.1.1.tgz", + "integrity": "sha512-uy/hS/awclDJ1S88w9UGpc6Nm9XnNUjzOAAib1A3PVAnGQIwebg8DpFqOthFBTlZxeuV/BKbZ5jmTbtNZkp1WQ==", + "license": "MIT", + "dependencies": { + "@expo/bunyan": "^4.0.0", + "@segment/loosely-validate-event": "^2.0.0", + "fetch-retry": "^4.1.1", + "md5": "^2.2.1", + "node-fetch": "^2.6.1", + "remove-trailing-slash": "^0.1.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@expo/rudder-sdk-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@expo/schema-utils": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-0.1.7.tgz", @@ -2224,12 +3274,88 @@ "node": ">=12" } }, + "node_modules/@expo/steps": { + "version": "1.0.231", + "resolved": "https://registry.npmjs.org/@expo/steps/-/steps-1.0.231.tgz", + "integrity": "sha512-o7Wj6W32S4H4geWC0b3RL5finHmiPT+e8SMP4/4Il2R0oc9er8jLXaj1tQ3fTZvpnlqoMWcFn5fAvaGnxfYDPA==", + "license": "BUSL-1.1", + "dependencies": { + "@expo/eas-build-job": "1.0.231", + "@expo/logger": "1.0.221", + "@expo/spawn-async": "^1.7.2", + "arg": "^5.0.2", + "fs-extra": "^11.2.0", + "joi": "^17.13.1", + "jsep": "^1.3.8", + "lodash.clonedeep": "^4.5.0", + "lodash.get": "^4.4.2", + "this-file": "^2.0.3", + "uuid": "^9.0.1", + "yaml": "^2.4.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@expo/steps/node_modules/@expo/eas-build-job": { + "version": "1.0.231", + "resolved": "https://registry.npmjs.org/@expo/eas-build-job/-/eas-build-job-1.0.231.tgz", + "integrity": "sha512-jktsZ7IJYcC9lbgUWftbFCpfrhcGwJX4q0qSubUrqvFTMuUyLjCzJqyIZ4znbXLXfJSUPDJNAd4H0wBqKJA9kA==", + "license": "MIT", + "dependencies": { + "@expo/logger": "1.0.221", + "joi": "^17.13.1", + "semver": "^7.6.2", + "zod": "^4.1.3" + } + }, + "node_modules/@expo/steps/node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/@expo/steps/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@expo/steps/node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "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/timeago.js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@expo/timeago.js/-/timeago.js-1.0.0.tgz", + "integrity": "sha512-PD45CGlCL8kG0U3YcH1NvYxQThw5XAS7qE9bgP4L7dakm8lsMz+p8BQ1IjBFMmImawVWsV3py6JZINaEebXLnw==", + "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", @@ -2385,6 +3511,21 @@ "node": ">=8" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2751,6 +3892,432 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oclif/core": { + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-1.26.2.tgz", + "integrity": "sha512-6jYuZgXvHfOIc9GIaS4T3CIKGTjPmfAxuMcbCbMRKJJl4aq/4xeRlEz0E8/hz8HxvxZBGvN2GwAUHlrGWQVrVw==", + "license": "MIT", + "dependencies": { + "@oclif/linewrap": "^1.0.0", + "@oclif/screen": "^3.0.4", + "ansi-escapes": "^4.3.2", + "ansi-styles": "^4.3.0", + "cardinal": "^2.1.1", + "chalk": "^4.1.2", + "clean-stack": "^3.0.1", + "cli-progress": "^3.10.0", + "debug": "^4.3.4", + "ejs": "^3.1.6", + "fs-extra": "^9.1.0", + "get-package-type": "^0.1.0", + "globby": "^11.1.0", + "hyperlinker": "^1.0.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "js-yaml": "^3.14.1", + "natural-orderby": "^2.0.3", + "object-treeify": "^1.1.33", + "password-prompt": "^1.1.2", + "semver": "^7.3.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "supports-color": "^8.1.1", + "supports-hyperlinks": "^2.2.0", + "tslib": "^2.4.1", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oclif/core/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/@oclif/core/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/@oclif/core/node_modules/chalk/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/@oclif/core/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/@oclif/core/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/@oclif/core/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/@oclif/core/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@oclif/core/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/@oclif/core/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/@oclif/core/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/@oclif/core/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/@oclif/core/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/@oclif/linewrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@oclif/linewrap/-/linewrap-1.0.0.tgz", + "integrity": "sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw==", + "license": "ISC" + }, + "node_modules/@oclif/plugin-autocomplete": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@oclif/plugin-autocomplete/-/plugin-autocomplete-2.3.10.tgz", + "integrity": "sha512-Ow1AR8WtjzlyCtiWWPgzMyT8SbcDJFr47009riLioHa+MHX2BCDtVn2DVnN/E6b9JlPV5ptQpjefoRSNWBesmg==", + "license": "MIT", + "dependencies": { + "@oclif/core": "^2.15.0", + "chalk": "^4.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@oclif/plugin-autocomplete/node_modules/@oclif/core": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@oclif/core/-/core-2.16.0.tgz", + "integrity": "sha512-dL6atBH0zCZl1A1IXCKJgLPrM/wR7K+Wi401E/IvqsK8m2iCHW+0TEOGrans/cuN3oTW+uxIyJFHJ8Im0k4qBw==", + "license": "MIT", + "dependencies": { + "@types/cli-progress": "^3.11.0", + "ansi-escapes": "^4.3.2", + "ansi-styles": "^4.3.0", + "cardinal": "^2.1.1", + "chalk": "^4.1.2", + "clean-stack": "^3.0.1", + "cli-progress": "^3.12.0", + "debug": "^4.3.4", + "ejs": "^3.1.8", + "get-package-type": "^0.1.0", + "globby": "^11.1.0", + "hyperlinker": "^1.0.0", + "indent-string": "^4.0.0", + "is-wsl": "^2.2.0", + "js-yaml": "^3.14.1", + "natural-orderby": "^2.0.3", + "object-treeify": "^1.1.33", + "password-prompt": "^1.1.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "supports-color": "^8.1.1", + "supports-hyperlinks": "^2.2.0", + "ts-node": "^10.9.1", + "tslib": "^2.5.0", + "widest-line": "^3.1.0", + "wordwrap": "^1.0.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oclif/plugin-autocomplete/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/@oclif/plugin-autocomplete/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/@oclif/plugin-autocomplete/node_modules/chalk/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/@oclif/plugin-autocomplete/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/@oclif/plugin-autocomplete/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/@oclif/plugin-autocomplete/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/@oclif/plugin-autocomplete/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/@oclif/plugin-autocomplete/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/@oclif/plugin-autocomplete/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/@oclif/plugin-autocomplete/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/@oclif/plugin-autocomplete/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/@oclif/screen": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-3.0.8.tgz", + "integrity": "sha512-yx6KAqlt3TAHBduS2fMQtJDL2ufIHnDRArrJEOoTTuizxqmjLT+psGYOHpmMl3gvQpFJ11Hs76guUUktzAF9Bg==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2761,6 +4328,34 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz", @@ -3015,6 +4610,45 @@ "integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==", "license": "MIT" }, + "node_modules/@segment/ajv-human-errors": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/@segment/ajv-human-errors/-/ajv-human-errors-2.15.0.tgz", + "integrity": "sha512-tgeMMuYYJt3Aar5IIk3kyfL9zMvGsv5d7KsVT/2auri+hEH/L2M1i8X67ne4JjMWZqENYIGY1WuI4oPEL1H/xA==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.0" + } + }, + "node_modules/@segment/loosely-validate-event": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", + "integrity": "sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==", + "dependencies": { + "component-type": "^1.2.1", + "join-component": "^1.1.0" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3039,6 +4673,30 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3080,6 +4738,24 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/bunyan": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.11.tgz", + "integrity": "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cli-progress": { + "version": "3.11.6", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.6.tgz", + "integrity": "sha512-cE3+jb9WRlu+uOSAugewNpITJDt1VF8dHOopPO4IABFc3SXYL5WE/+PTz/FCdZRRfIujiWW3n3aMbv1eIGVRWA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -3228,6 +4904,18 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "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", @@ -3237,6 +4925,39 @@ "node": ">= 14" } }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/anser": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", @@ -3291,6 +5012,12 @@ "node": ">=4" } }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "license": "MIT" + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -3325,18 +5052,89 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "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/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/axios": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", + "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.8" + } + }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3581,6 +5379,49 @@ "@babel/core": "^7.0.0 || ^8.0.0-0" } }, + "node_modules/babel-preset-expo": { + "version": "54.0.7", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-54.0.7.tgz", + "integrity": "sha512-JENWk0bvxW4I1ftveO8GRtX2t2TH6N4Z0TPvIHxroZ/4SswUfyNsUNbbP7Fm4erj3ar/JHGri5kTZ+s3xdjHZw==", + "license": "MIT", + "dependencies": { + "@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.81.5", + "babel-plugin-react-compiler": "^1.0.0", + "babel-plugin-react-native-web": "~0.21.0", + "babel-plugin-syntax-hermes-parser": "^0.29.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "debug": "^4.3.4", + "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.0", + "expo": "*", + "react-refresh": ">=0.14.0 <1.0.0" + }, + "peerDependenciesMeta": { + "@babel/runtime": { + "optional": true + }, + "expo": { + "optional": true + } + } + }, "node_modules/babel-preset-jest": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", @@ -3603,6 +5444,20 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3778,12 +5633,36 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "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/bunyan": { + "version": "1.8.15", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz", + "integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==", + "engines": [ + "node >=0.10.0" + ], + "license": "MIT", + "bin": { + "bunyan": "bin/bunyan" + }, + "optionalDependencies": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3793,6 +5672,35 @@ "node": ">= 0.8" } }, + "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", @@ -3825,6 +5733,19 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "license": "MIT", + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3839,6 +5760,15 @@ "node": ">=4" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -3910,6 +5840,33 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "license": "MIT" }, + "node_modules/clean-stack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", + "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clean-stack/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/cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -3922,6 +5879,50 @@ "node": ">=4" } }, + "node_modules/cli-progress": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", + "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-progress/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/cli-progress/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/cli-progress/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/cli-spinners": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", @@ -4054,6 +6055,18 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -4063,6 +6076,15 @@ "node": ">= 10" } }, + "node_modules/component-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.2.tgz", + "integrity": "sha512-99VUHREHiN5cLeHm3YLq312p6v+HUEcwtLCAtelvUDI6+SH5g5Cr85oNR2S1o6ywzL0ykMbuwLzM2ANocjEOIA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -4153,6 +6175,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4172,6 +6203,12 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4186,6 +6223,21 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -4202,6 +6254,15 @@ "devOptional": true, "license": "MIT" }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -4258,6 +6319,15 @@ "node": ">=8" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4286,6 +6356,33 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domino": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/domino/-/domino-2.1.6.tgz", + "integrity": "sha512-3VdM/SXBZX2omc9JF9nOPCtDaYQ67BGp5CoLpIQlO2KCAPETs8TcDHacF26jXadGbvUteZzRTeos2fhID5+ucQ==", + "license": "BSD-2-Clause" + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -4313,18 +6410,1062 @@ "url": "https://dotenvx.com" } }, + "node_modules/dtrace-provider": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", + "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==", + "hasInstallScript": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "nan": "^2.14.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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/eas-cli": { + "version": "16.28.0", + "resolved": "https://registry.npmjs.org/eas-cli/-/eas-cli-16.28.0.tgz", + "integrity": "sha512-KwdeZ4/riRvzzHEGJ7OyFA5xD1NirydRjxqIQJRYvxFRG0SWsuklHb+OUiy9Pl8DnIV+OHLNu4Z7nAatnQZPrA==", + "license": "MIT", + "dependencies": { + "@expo/apple-utils": "2.1.12", + "@expo/code-signing-certificates": "0.0.5", + "@expo/config": "10.0.6", + "@expo/config-plugins": "9.0.12", + "@expo/eas-build-job": "1.0.243", + "@expo/eas-json": "16.25.0", + "@expo/env": "^1.0.0", + "@expo/json-file": "8.3.3", + "@expo/logger": "1.0.221", + "@expo/multipart-body-parser": "2.0.0", + "@expo/osascript": "2.1.4", + "@expo/package-manager": "1.7.0", + "@expo/pkcs12": "0.1.3", + "@expo/plist": "0.2.0", + "@expo/plugin-help": "5.1.23", + "@expo/plugin-warn-if-update-available": "2.5.1", + "@expo/prebuild-config": "8.0.17", + "@expo/results": "1.0.0", + "@expo/rudder-sdk-node": "1.1.1", + "@expo/spawn-async": "1.7.2", + "@expo/steps": "1.0.231", + "@expo/timeago.js": "1.0.0", + "@oclif/core": "^1.26.2", + "@oclif/plugin-autocomplete": "^2.3.10", + "@segment/ajv-human-errors": "^2.1.2", + "@urql/core": "4.0.11", + "@urql/exchange-retry": "1.2.0", + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "better-opn": "3.0.2", + "bplist-parser": "^0.3.0", + "chalk": "4.1.2", + "cli-progress": "3.12.0", + "dateformat": "4.6.3", + "diff": "7.0.0", + "dotenv": "16.3.1", + "env-paths": "2.2.0", + "envinfo": "7.11.0", + "fast-deep-equal": "3.1.3", + "fast-glob": "3.3.2", + "figures": "3.2.0", + "form-data": "^4.0.4", + "fs-extra": "11.2.0", + "getenv": "1.0.0", + "gradle-to-js": "2.0.1", + "graphql": "16.8.1", + "graphql-tag": "2.12.6", + "https-proxy-agent": "5.0.1", + "ignore": "5.3.0", + "indent-string": "4.0.0", + "jks-js": "1.1.0", + "joi": "17.11.0", + "jsonwebtoken": "9.0.0", + "keychain": "1.5.0", + "log-symbols": "4.1.0", + "mime": "3.0.0", + "minimatch": "5.1.2", + "minizlib": "3.0.1", + "nanoid": "3.3.8", + "node-fetch": "2.6.7", + "node-forge": "1.3.1", + "node-stream-zip": "1.15.0", + "nullthrows": "1.1.1", + "ora": "5.1.0", + "pkg-dir": "4.2.0", + "pngjs": "7.0.0", + "promise-limit": "2.7.0", + "promise-retry": "2.0.1", + "prompts": "2.4.2", + "qrcode-terminal": "0.12.0", + "resolve-from": "5.0.0", + "semver": "7.5.4", + "set-interval-async": "3.0.3", + "slash": "3.0.0", + "tar": "6.2.1", + "tar-stream": "3.1.7", + "terminal-link": "2.1.1", + "ts-deepmerge": "6.2.0", + "tslib": "2.6.2", + "turndown": "7.1.2", + "untildify": "4.0.0", + "uuid": "9.0.1", + "wrap-ansi": "7.0.0", + "yaml": "2.6.0", + "zod": "^4.1.3" + }, + "bin": { + "eas": "bin/run" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eas-cli/node_modules/@expo/config": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-10.0.6.tgz", + "integrity": "sha512-xXkfPElrtxznkOZxFASJ7OPa6E9IHSjcZwj5BQ6XUF2dz5M7AFa2h5sXM8AalSaDU5tEBSgoUOjTh5957TlR8g==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "@expo/config-plugins": "~9.0.10", + "@expo/config-types": "^52.0.0", + "@expo/json-file": "^9.0.0", + "deepmerge": "^4.3.1", + "getenv": "^1.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/eas-cli/node_modules/@expo/config-plugins": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-9.0.12.tgz", + "integrity": "sha512-/Ko/NM+GzvJyRkq8PITm8ms0KY5v0wmN1OQFYRMkcJqOi3PjlhndW+G6bHpJI9mkQXBaUnHwAiGLqIC3+MQ5Wg==", + "license": "MIT", + "dependencies": { + "@expo/config-types": "^52.0.0", + "@expo/json-file": "~9.0.0", + "@expo/plist": "^0.2.0", + "@expo/sdk-runtime-versions": "^1.0.0", + "chalk": "^4.1.2", + "debug": "^4.3.5", + "getenv": "^1.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/eas-cli/node_modules/@expo/config-plugins/node_modules/@expo/json-file": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.0.2.tgz", + "integrity": "sha512-yAznIUrybOIWp3Uax7yRflB0xsEpvIwIEqIjao9SGi2Gaa+N0OamWfe0fnXBSWF+2zzF4VvqwT4W5zwelchfgw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "json5": "^2.2.3", + "write-file-atomic": "^2.3.0" + } + }, + "node_modules/eas-cli/node_modules/@expo/config-types": { + "version": "52.0.5", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-52.0.5.tgz", + "integrity": "sha512-AMDeuDLHXXqd8W+0zSjIt7f37vUd/BP8p43k68NHpyAvQO+z8mbQZm3cNQVAMySeayK2XoPigAFB1JF2NFajaA==", + "license": "MIT" + }, + "node_modules/eas-cli/node_modules/@expo/config/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/eas-cli/node_modules/@expo/config/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eas-cli/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/eas-cli/node_modules/@expo/env/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/eas-cli/node_modules/@expo/env/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/eas-cli/node_modules/@expo/json-file": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-8.3.3.tgz", + "integrity": "sha512-eZ5dld9AD0PrVRiIWpRkm5aIoWBw3kAyd8VkuWEy92sEthBKDDDHAnK2a0dw0Eil6j7rK7lS/Qaq/Zzngv2h5A==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "~7.10.4", + "json5": "^2.2.2", + "write-file-atomic": "^2.3.0" + } + }, + "node_modules/eas-cli/node_modules/@expo/osascript": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.1.4.tgz", + "integrity": "sha512-LcPjxJ5FOFpqPORm+5MRLV0CuYWMthJYV6eerF+lQVXKlvgSn3EOqaHC3Vf3H+vmB0f6G4kdvvFtg40vG4bIhA==", + "license": "MIT", + "dependencies": { + "@expo/spawn-async": "^1.7.2", + "exec-async": "^2.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/eas-cli/node_modules/@expo/package-manager": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.7.0.tgz", + "integrity": "sha512-yWn5TIjd42wLHZjNtdZkvCkcxqUGxlI4YHb+bQmgm3tWZ8aBHnLhPb0rgU8+hVHCofmRvVUXfVZv8Uh+kkLXgw==", + "license": "MIT", + "dependencies": { + "@expo/json-file": "^9.0.0", + "@expo/spawn-async": "^1.7.2", + "ansi-regex": "^5.0.0", + "chalk": "^4.0.0", + "find-up": "^5.0.0", + "js-yaml": "^3.13.1", + "micromatch": "^4.0.8", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "resolve-workspace-root": "^2.0.0", + "split": "^1.0.1", + "sudo-prompt": "9.1.1" + } + }, + "node_modules/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/package-manager/node_modules/strip-ansi/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/eas-cli/node_modules/@expo/package-manager/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/eas-cli/node_modules/@expo/plist": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.2.0.tgz", + "integrity": "sha512-F/IZJQaf8OIVnVA6XWUeMPC3OH6MV00Wxf0WC0JhTQht2QgjyHUa3U5Gs3vRtDq8tXNsZneOQRDVwpaOnd4zTQ==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "~0.7.7", + "base64-js": "^1.2.3", + "xmlbuilder": "^14.0.0" + } + }, + "node_modules/eas-cli/node_modules/@urql/core": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/@urql/core/-/core-4.0.11.tgz", + "integrity": "sha512-FFdY97vF5xnUrElcGw9erOLvtu+KGMLfwrLNDfv4IPgdp2IBsiGe+Kb7Aypfd3kH//BETewVSLm3+y2sSzjX6A==", + "license": "MIT", + "dependencies": { + "@0no-co/graphql.web": "^1.0.1", + "wonka": "^6.3.2" + } + }, + "node_modules/eas-cli/node_modules/@urql/exchange-retry": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.2.0.tgz", + "integrity": "sha512-1O/biKiVhhn0EtvDF4UOvz325K4RrLupfL8rHcmqD2TBLv4qVDWQuzx4JGa1FfqjjRb+C9TNZ6w19f32Mq85Ug==", + "license": "MIT", + "dependencies": { + "@urql/core": ">=4.0.0", + "wonka": "^6.3.2" + } + }, + "node_modules/eas-cli/node_modules/@xmldom/xmldom": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", + "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", + "deprecated": "this version is no longer supported, please update to at least 0.8.*", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/eas-cli/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/eas-cli/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/eas-cli/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/eas-cli/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/eas-cli/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/eas-cli/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/eas-cli/node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/eas-cli/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/eas-cli/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eas-cli/node_modules/getenv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-1.0.0.tgz", + "integrity": "sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eas-cli/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/eas-cli/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/eas-cli/node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eas-cli/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eas-cli/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eas-cli/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eas-cli/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/eas-cli/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eas-cli/node_modules/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eas-cli/node_modules/minizlib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/eas-cli/node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "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/eas-cli/node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/eas-cli/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eas-cli/node_modules/ora": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.1.0.tgz", + "integrity": "sha512-9tXIMPvjZ7hPTbk8DFq1f7Kow/HU/pQYB60JbNq+QnGwcyhWVZaQ4hM9zQDEsPxw/muLpgiHSaumUZxCAmod/w==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.4.0", + "is-interactive": "^1.0.0", + "log-symbols": "^4.0.0", + "mute-stream": "0.0.8", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eas-cli/node_modules/ora/node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eas-cli/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eas-cli/node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/eas-cli/node_modules/qrcode-terminal": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", + "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/eas-cli/node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eas-cli/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eas-cli/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eas-cli/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/eas-cli/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/eas-cli/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/eas-cli/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/eas-cli/node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eas-cli/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/eas-cli/node_modules/tar/node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eas-cli/node_modules/tar/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eas-cli/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/eas-cli/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/eas-cli/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/eas-cli/node_modules/xmlbuilder": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-14.0.0.tgz", + "integrity": "sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/eas-cli/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/eas-cli/node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/eas-cli/node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "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/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "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/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.262", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", @@ -4355,6 +7496,48 @@ "node": ">=8" } }, + "node_modules/env-paths": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/env-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/env-string/-/env-string-1.0.1.tgz", + "integrity": "sha512-/DhCJDf5DSFK32joQiWRpWrT0h7p3hVQfMKxiBb7Nt8C8IF8BYyPtclDnuGGLOoj16d/8udKeiE7JbkotDmorQ==", + "license": "MIT" + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/error-stack-parser": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", @@ -4364,6 +7547,51 @@ "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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4419,6 +7647,15 @@ "node": ">=6" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/exec-async": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/exec-async/-/exec-async-2.2.0.tgz", @@ -4477,6 +7714,103 @@ } } }, + "node_modules/expo-av": { + "version": "16.0.7", + "resolved": "https://registry.npmjs.org/expo-av/-/expo-av-16.0.7.tgz", + "integrity": "sha512-QReef6/RYuZ4GekTcZZw5zY26pcPOmHqK6LMgFlPhnsT0ga97HJrgMc63pvIiojDP+q4oIv24g+QPD8ljZu4XQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, + "node_modules/expo-dev-client": { + "version": "6.0.18", + "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-6.0.18.tgz", + "integrity": "sha512-8QKWvhsoZpMkecAMlmWoRHnaTNiPS3aO7E42spZOMjyiaNRJMHZsnB8W2b63dt3Yg3oLyskLAoI8IOmnqVX8vA==", + "license": "MIT", + "dependencies": { + "expo-dev-launcher": "6.0.18", + "expo-dev-menu": "7.0.17", + "expo-dev-menu-interface": "2.0.0", + "expo-manifests": "~1.0.9", + "expo-updates-interface": "~2.0.0" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-dev-launcher": { + "version": "6.0.18", + "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-6.0.18.tgz", + "integrity": "sha512-JTtcIfNvHO9PTdRJLmHs+7HJILXXZjF95jxgzu6hsJrgsTg/AZDtEsIt/qa6ctEYQTqrLdsLDgDhiXVel3AoQA==", + "license": "MIT", + "dependencies": { + "expo-dev-menu": "7.0.17", + "expo-manifests": "~1.0.9" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-dev-menu": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-7.0.17.tgz", + "integrity": "sha512-NIu7TdaZf+A8+DROa6BB6lDfxjXxwaD+Q8QbNSVa0E0x6yl3P0ZJ80QbD2cCQeBzlx3Ufd3hNhczQWk4+A29HQ==", + "license": "MIT", + "dependencies": { + "expo-dev-menu-interface": "2.0.0" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-dev-menu-interface": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/expo-dev-menu-interface/-/expo-dev-menu-interface-2.0.0.tgz", + "integrity": "sha512-BvAMPt6x+vyXpThsyjjOYyjwfjREV4OOpQkZ0tNl+nGpsPfcY9mc6DRACoWnH9KpLzyIt3BOgh3cuy/h/OxQjw==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-json-utils": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.15.0.tgz", + "integrity": "sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==", + "license": "MIT" + }, + "node_modules/expo-linear-gradient": { + "version": "15.0.7", + "resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-15.0.7.tgz", + "integrity": "sha512-yF+y+9Shpr/OQFfy/wglB/0bykFMbwHBTuMRa5Of/r2P1wbkcacx8rg0JsUWkXH/rn2i2iWdubyqlxSJa3ggZA==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-manifests": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-1.0.9.tgz", + "integrity": "sha512-5uVgvIo0o+xBcEJiYn4uVh72QSIqyHePbYTWXYa4QamXd+AmGY/yWmtHaNqCqjsPLCwXyn4OxPr7jXJCeTWLow==", + "license": "MIT", + "dependencies": { + "@expo/config": "~12.0.10", + "expo-json-utils": "~0.15.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-modules-autolinking": { "version": "3.0.22", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.22.tgz", @@ -4598,6 +7932,15 @@ "react-native": "*" } }, + "node_modules/expo-updates-interface": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-2.0.0.tgz", + "integrity": "sha512-pTzAIufEZdVPKql6iMi5ylVSPqV1qbEopz9G6TSECQmnNde2nwq42PxdFBaUEd8IZJ/fdJLQnOT3m6+XJ5s7jg==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo/node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -4784,49 +8127,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/expo/node_modules/babel-preset-expo": { - "version": "54.0.7", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-54.0.7.tgz", - "integrity": "sha512-JENWk0bvxW4I1ftveO8GRtX2t2TH6N4Z0TPvIHxroZ/4SswUfyNsUNbbP7Fm4erj3ar/JHGri5kTZ+s3xdjHZw==", - "license": "MIT", - "dependencies": { - "@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.81.5", - "babel-plugin-react-compiler": "^1.0.0", - "babel-plugin-react-native-web": "~0.21.0", - "babel-plugin-syntax-hermes-parser": "^0.29.1", - "babel-plugin-transform-flow-enums": "^0.0.2", - "debug": "^4.3.4", - "resolve-from": "^5.0.0" - }, - "peerDependencies": { - "@babel/runtime": "^7.20.0", - "expo": "*", - "react-refresh": ">=0.14.0 <1.0.0" - }, - "peerDependenciesMeta": { - "@babel/runtime": { - "optional": true - }, - "expo": { - "optional": true - } - } - }, "node_modules/expo/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5063,12 +8363,49 @@ "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-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, "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/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -5078,6 +8415,48 @@ "bser": "2.1.1" } }, + "node_modules/fetch-retry": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz", + "integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==", + "license": "MIT" + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5142,6 +8521,26 @@ "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", "license": "MIT" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fontfaceobserver": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", @@ -5164,6 +8563,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/freeport-async": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/freeport-async/-/freeport-async-2.0.0.tgz", @@ -5182,6 +8597,50 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5229,6 +8688,30 @@ "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", @@ -5238,6 +8721,19 @@ "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", @@ -5267,6 +8763,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", @@ -5279,12 +8787,85 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/golden-fleece": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/golden-fleece/-/golden-fleece-1.0.9.tgz", + "integrity": "sha512-YSwLaGMOgSBx9roJlNLL12c+FRiw7VECphinc6mGucphc/ZxTHgdEz6gmJqH6NOzYEd/yr64hwjom5pZ+tJVpg==" + }, + "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/gradle-to-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/gradle-to-js/-/gradle-to-js-2.0.1.tgz", + "integrity": "sha512-is3hDn9zb8XXnjbEeAEIqxTpLHUiGBqjegLmXPuyMBfKAggpadWFku4/AP8iYAGBX6qR9/5UIUIp47V0XI3aMw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.merge": "^4.6.2" + }, + "bin": { + "gradle-to-js": "cli.js" + } + }, + "node_modules/graphql": { + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz", + "integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==", + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-tag": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", + "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5294,6 +8875,33 @@ "node": ">=4" } }, + "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", @@ -5339,6 +8947,23 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/http-call": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/http-call/-/http-call-5.3.0.tgz", + "integrity": "sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w==", + "license": "ISC", + "dependencies": { + "content-type": "^1.0.4", + "debug": "^4.1.1", + "is-retry-allowed": "^1.1.0", + "is-stream": "^2.0.0", + "parse-json": "^4.0.0", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -5377,6 +9002,21 @@ "node": ">= 14" } }, + "node_modules/humps": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz", + "integrity": "sha512-E0eIbrFWUhwfXJmsbdjRQFQPrl5pTEoKlz163j1mTqqUnU9PgR4AgB8AIITzuB3vLBdxZXyZ9TDIrwB2OASz4g==", + "license": "MIT" + }, + "node_modules/hyperlinker": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz", + "integrity": "sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5430,6 +9070,15 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5462,6 +9111,18 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -5492,6 +9153,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "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", @@ -5501,6 +9171,27 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5510,6 +9201,48 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -5577,6 +9310,23 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -5989,6 +9739,36 @@ "integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==", "license": "MIT" }, + "node_modules/jks-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jks-js/-/jks-js-1.1.0.tgz", + "integrity": "sha512-irWi8S2V029Vic63w0/TYa8NIZwXu9oeMtHQsX51JDIVBo0lrEaOoyM8ALEEh5PVKD6TrA26FixQK6TzT7dHqA==", + "license": "MIT", + "dependencies": { + "node-forge": "^1.3.1", + "node-int64": "^0.4.0", + "node-rsa": "^1.1.1" + } + }, + "node_modules/joi": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/join-component": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz", + "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6014,6 +9794,15 @@ "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", "license": "0BSD" }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6026,6 +9815,18 @@ "node": ">=6" } }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6038,6 +9839,61 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keychain": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/keychain/-/keychain-1.5.0.tgz", + "integrity": "sha512-liyp4r+93RI7EB2jhwaRd4MWfdgHH6shuldkaPMkELCJjMFvOOVXuTvw1pGqFfhsrgA6OqfykWWPQgBjQakVag==", + "license": "MIT" + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6357,12 +10213,37 @@ "node": ">=8" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, "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.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" + }, "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", @@ -6402,6 +10283,12 @@ "yallist": "^3.0.2" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -6417,18 +10304,59 @@ "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/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "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-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/metro": { "version": "0.83.2", "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.2.tgz", @@ -6894,12 +10822,118 @@ "node": ">=10" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, "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/multipasta": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz", + "integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "license": "ISC" + }, + "node_modules/mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "~0.5.1", + "ncp": "~2.0.0", + "rimraf": "~2.4.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/mv/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", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/mv/node_modules/glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mv/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mv/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mv/node_modules/rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^6.0.1" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -6911,6 +10945,13 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nan": { + "version": "2.23.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.1.tgz", + "integrity": "sha512-r7bBUGKzlqk8oPBDYxt6Z0aEdF1G1rwlMcLk8LCOMbOzf0mG+JUfUzG4fIMWwHWP0iyaLWEQZJmtB7nOHEm/qw==", + "license": "MIT", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -6929,6 +10970,25 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-orderby": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz", + "integrity": "sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "license": "MIT", + "optional": true, + "bin": { + "ncp": "bin/ncp" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -6944,6 +11004,26 @@ "integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", @@ -6965,6 +11045,28 @@ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT" }, + "node_modules/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "license": "MIT", + "dependencies": { + "asn1": "^0.2.4" + } + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -7016,6 +11118,27 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -7169,6 +11292,19 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/parse-png": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz", @@ -7190,6 +11326,16 @@ "node": ">= 0.8" } }, + "node_modules/password-prompt": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/password-prompt/-/password-prompt-1.1.3.tgz", + "integrity": "sha512-HkrjG2aJlvF0t2BMH0e2LB/EHf3Lcq3fNMzy4GYHcQblAvOl+QQji1Lx7WRBMqpVK8p+KR7bCg7oqAMXtdgqyw==", + "license": "0BSD", + "dependencies": { + "ansi-escapes": "^4.3.2", + "cross-spawn": "^7.0.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7245,6 +11391,15 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7272,6 +11427,65 @@ "node": ">= 6" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -7388,6 +11602,25 @@ "asap": "~2.0.6" } }, + "node_modules/promise-limit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", + "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", + "license": "ISC" + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -7418,6 +11651,21 @@ "qrcode-terminal": "bin/qrcode-terminal.js" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -7427,6 +11675,26 @@ "inherits": "~2.0.3" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -7533,6 +11801,18 @@ } } }, + "node_modules/react-native-dotenv": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/react-native-dotenv/-/react-native-dotenv-3.4.11.tgz", + "integrity": "sha512-6vnIE+WHABSeHCaYP6l3O1BOEhWxKH6nHAdV7n/wKn/sciZ64zPPp2NUdEUf1m7g4uuzlLbjgr+6uDt89q2DOg==", + "license": "MIT", + "dependencies": { + "dotenv": "^16.4.5" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.6" + } + }, "node_modules/react-native-is-edge-to-edge": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", @@ -7543,6 +11823,42 @@ "react-native": "*" } }, + "node_modules/react-native-safe-area-context": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", + "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-webview": { + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.0.tgz", + "integrity": "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-webview/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/react-native/node_modules/@react-native/virtualized-lists": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz", @@ -7636,6 +11952,15 @@ "node": ">=0.10.0" } }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "license": "MIT", + "dependencies": { + "esprima": "~4.0.0" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -7695,6 +12020,12 @@ "regjsparser": "bin/parser" } }, + "node_modules/remove-trailing-slash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz", + "integrity": "sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==", + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7810,6 +12141,25 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -7869,6 +12219,29 @@ "node": "*" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7889,6 +12262,19 @@ ], "license": "MIT" }, + "node_modules/safe-json-stringify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz", + "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==", + "license": "MIT", + "optional": true + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/sax": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", @@ -8006,6 +12392,15 @@ "node": ">= 0.8" } }, + "node_modules/set-interval-async": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/set-interval-async/-/set-interval-async-3.0.3.tgz", + "integrity": "sha512-o4DyBv6mko+A9cH3QKek4SAAT5UyJRkfdTi6JHii6ZCKUYFun8SwgBmQrOXd158JOwBQzA+BnO8BvT64xuCaSw==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -8045,6 +12440,78 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -8083,6 +12550,56 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/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/slice-ansi/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/slice-ansi/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/slugify": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", @@ -8129,6 +12646,18 @@ "node": ">=0.10.0" } }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -8192,6 +12721,17 @@ "node": ">= 0.10.0" } }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -8328,6 +12868,13 @@ "node": ">= 6" } }, + "node_modules/sudo-prompt": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.1.1.tgz", + "integrity": "sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -8402,6 +12949,17 @@ "node": ">=18" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/tar/node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -8517,6 +13075,15 @@ "node": "*" } }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -8538,12 +13105,27 @@ "node": ">=0.8" } }, + "node_modules/this-file": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/this-file/-/this-file-2.0.3.tgz", + "integrity": "sha512-IdMH1bUkVJdJjM7o8v83Mv4QvVPdkAofur20STl2Bbw9uMuuS/bT/PZURkEdZsy9XC/1ZXWgZ1wIL9nvouGaEg==", + "license": "MIT", + "engines": { + "node": ">=14.15.0" + } + }, "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/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -8571,12 +13153,112 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-deepmerge": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-6.2.0.tgz", + "integrity": "sha512-2qxI/FZVDPbzh63GwWIZYE7daWKtwXZYuyc8YNq0iTmMUwn4mL0jRLsp6hfFlgbdRSR4x2ppe+E86FnvEpN7Nw==", + "license": "ISC", + "engines": { + "node": ">=14.13.1" + } + }, "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/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/turndown": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.1.2.tgz", + "integrity": "sha512-ntI9R7fcUKjqBP6QU8rBK2Ehyt8LAzt3UBT9JR9tgo6GtuKvyUzpayWmeMKJw1DPdXzktvtIT8m2mVXz+bL/Qg==", + "license": "MIT", + "dependencies": { + "domino": "^2.1.6" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -8599,7 +13281,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -8676,6 +13357,15 @@ "node": ">=8" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -8685,6 +13375,15 @@ "node": ">= 0.8" } }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -8715,6 +13414,15 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -8733,6 +13441,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, "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", @@ -8790,6 +13504,16 @@ "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "license": "MIT" }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/whatwg-url-without-unicode": { "version": "8.0.0-3", "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", @@ -8804,6 +13528,12 @@ "node": ">=10" } }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8819,12 +13549,62 @@ "node": ">= 8" } }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/widest-line/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/widest-line/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/widest-line/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/wonka": { "version": "6.3.5", "resolved": "https://registry.npmjs.org/wonka/-/wonka-6.3.5.tgz", "integrity": "sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==", "license": "MIT" }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -9116,6 +13896,15 @@ "node": ">=8" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -9128,6 +13917,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zaiclient": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/zaiclient/-/zaiclient-8.2.0.tgz", + "integrity": "sha512-J6lrYb+stXbigf8fbDQE/RAM/9G9E9V6IsUIdOqKbkNWuOCxz/lYRY2IhteyXk7QhuKLB3AulzICOzNtG0IPKA==", + "license": "UNLICENSED", + "dependencies": { + "axios": "^0.26.1", + "crypto-js": "^4.1.1", + "humps": "^2.0.1", + "qs": "^6.11.2", + "uuid": "^9.0.1", + "zod": "^3.22.4" + } + }, + "node_modules/zaiclient/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", diff --git a/package.json b/package.json index 7badd70..14ee062 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,32 @@ { "name": "wellnuo", "version": "1.0.0", - "main": "index.ts", + "main": "index.tsx", "scripts": { "start": "expo start", - "android": "expo start --android", - "ios": "expo start --ios", + "android": "expo run:android", + "ios": "expo run:ios", "web": "expo start --web" }, "dependencies": { + "@react-native-async-storage/async-storage": "^2.2.0", + "eas-cli": "^16.28.0", "expo": "~54.0.25", + "expo-av": "~16.0.7", + "expo-dev-client": "~6.0.18", + "expo-linear-gradient": "~15.0.7", "expo-status-bar": "~3.0.8", "react": "19.1.0", - "react-native": "0.81.5" + "react-native": "0.81.5", + "react-native-dotenv": "^3.4.11", + "react-native-safe-area-context": "^5.6.2", + "react-native-webview": "^13.16.0", + "zaiclient": "^8.2.0" }, "devDependencies": { + "@playwright/test": "^1.57.0", "@types/react": "~19.1.0", + "babel-preset-expo": "^54.0.7", "typescript": "~5.9.2" }, "private": true diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..9550466 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,23 @@ +const { defineConfig, devices } = require('@playwright/test'); + +module.exports = defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: 0, + workers: 1, + reporter: 'list', + use: { + baseURL: 'https://react.eluxnetworks.net', + trace: 'on-first-retry', + screenshot: 'on', + video: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + outputDir: 'tests/screenshots/', +}); diff --git a/src/components/DashboardWebView.tsx b/src/components/DashboardWebView.tsx new file mode 100644 index 0000000..fa36a28 --- /dev/null +++ b/src/components/DashboardWebView.tsx @@ -0,0 +1,245 @@ +import React, { useRef, useState, useCallback } from 'react'; +import { View, StyleSheet, ActivityIndicator, Text, TouchableOpacity } from 'react-native'; +import { WebView, WebViewNavigation } from 'react-native-webview'; + +const API_BASE = 'https://eluxnetworks.net/function/well-api/api'; +const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard'; + +// Credentials +const CREDENTIALS = { + username: 'anandk', + password: 'anandk_8', + clientId: '001' +}; + +interface DashboardWebViewProps { + onPatientSelect?: (patientId: number, patientName: string) => void; + onError?: (error: string) => void; +} + +export const DashboardWebView: React.FC = ({ + onPatientSelect, + onError +}) => { + const webViewRef = useRef(null); + const [loading, setLoading] = useState(true); + const [token, setToken] = useState(null); + + // JavaScript to inject for auto-login + const getAutoLoginScript = (authToken: string) => ` + (function() { + // Save auth to localStorage + localStorage.setItem('auth2', JSON.stringify({ + username: '${CREDENTIALS.username}', + token: '${authToken}', + user_id: 43 + })); + + // Check if we're on login page + if (window.location.pathname === '/dashboard' || window.location.pathname === '/') { + // Find login form + const inputs = document.querySelectorAll('input'); + const usernameInput = inputs[0]; + const passwordInput = document.querySelector('input[type="password"]'); + + if (usernameInput && passwordInput) { + // Fill form + usernameInput.value = '${CREDENTIALS.username}'; + passwordInput.value = '${CREDENTIALS.password}'; + + // Trigger React state updates + usernameInput.dispatchEvent(new Event('input', { bubbles: true })); + passwordInput.dispatchEvent(new Event('input', { bubbles: true })); + + // Click login button + setTimeout(() => { + const loginBtn = document.querySelector('button'); + if (loginBtn) loginBtn.click(); + }, 100); + } + } + + // Listen for patient card clicks + document.addEventListener('click', function(e) { + const card = e.target.closest('[data-patient-id]'); + if (card) { + const patientId = card.getAttribute('data-patient-id'); + const patientName = card.querySelector('.patient-name')?.textContent || ''; + window.ReactNativeWebView.postMessage(JSON.stringify({ + type: 'PATIENT_SELECTED', + patientId: patientId, + patientName: patientName + })); + } + }, true); + + true; + })(); + `; + + // Fetch token on mount + const fetchToken = useCallback(async () => { + try { + const nonce = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + const params = new URLSearchParams({ + function: 'credentials', + clientId: CREDENTIALS.clientId, + user_name: CREDENTIALS.username, + ps: CREDENTIALS.password, + nonce: nonce + }); + + const response = await fetch(API_BASE, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: params.toString() + }); + + const data = await response.json(); + + if (data.access_token) { + setToken(data.access_token); + return data.access_token; + } else { + throw new Error('No token received'); + } + } catch (error) { + console.error('Failed to fetch token:', error); + onError?.('Failed to authenticate'); + return null; + } + }, [onError]); + + // Handle messages from WebView + const handleMessage = useCallback((event: { nativeEvent: { data: string } }) => { + try { + const message = JSON.parse(event.nativeEvent.data); + + if (message.type === 'PATIENT_SELECTED') { + onPatientSelect?.(message.patientId, message.patientName); + } + } catch (e) { + console.log('WebView message:', event.nativeEvent.data); + } + }, [onPatientSelect]); + + // Handle navigation state change + const handleNavigationStateChange = useCallback((navState: WebViewNavigation) => { + console.log('Navigation:', navState.url); + }, []); + + // Inject script when page loads + const handleLoadEnd = useCallback(() => { + setLoading(false); + + if (token && webViewRef.current) { + webViewRef.current.injectJavaScript(getAutoLoginScript(token)); + } + }, [token]); + + // Refresh handler + const handleRefresh = useCallback(() => { + setLoading(true); + webViewRef.current?.reload(); + }, []); + + return ( + + {/* Header */} + + WellNuo Dashboard + + Refresh + + + + {/* WebView */} + setLoading(true)} + onLoadEnd={handleLoadEnd} + onNavigationStateChange={handleNavigationStateChange} + onMessage={handleMessage} + javaScriptEnabled={true} + domStorageEnabled={true} + startInLoadingState={true} + scalesPageToFit={true} + allowsBackForwardNavigationGestures={true} + renderLoading={() => ( + + + Loading dashboard... + + )} + onError={(syntheticEvent) => { + const { nativeEvent } = syntheticEvent; + console.error('WebView error:', nativeEvent); + onError?.(`WebView error: ${nativeEvent.description}`); + }} + /> + + {/* Loading overlay */} + {loading && ( + + + + )} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#F3F4F6', + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 16, + backgroundColor: '#3B82F6', + }, + headerTitle: { + fontSize: 18, + fontWeight: 'bold', + color: '#FFFFFF', + }, + refreshButton: { + padding: 8, + borderRadius: 8, + backgroundColor: 'rgba(255,255,255,0.2)', + }, + refreshText: { + color: '#FFFFFF', + fontWeight: '600', + }, + webview: { + flex: 1, + }, + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F3F4F6', + }, + loadingText: { + marginTop: 12, + fontSize: 16, + color: '#6B7280', + }, + loadingOverlay: { + position: 'absolute', + top: 60, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(255,255,255,0.8)', + }, +}); + +export default DashboardWebView; diff --git a/src/components/StatusIndicator.tsx b/src/components/StatusIndicator.tsx new file mode 100644 index 0000000..f28438f --- /dev/null +++ b/src/components/StatusIndicator.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { ConnectionStatus } from '../types'; + +interface StatusIndicatorProps { + connectionStatus: ConnectionStatus; + isListening: boolean; + isSpeaking: boolean; + isProcessing: boolean; +} + +export function StatusIndicator({ + connectionStatus, + isListening, + isSpeaking, + isProcessing, +}: StatusIndicatorProps) { + const getStatusText = () => { + if (connectionStatus === 'error') return 'Connection Error'; + if (connectionStatus === 'connecting') return 'Connecting...'; + if (connectionStatus !== 'connected') return null; + if (isSpeaking) return 'Julia is speaking...'; + if (isProcessing) return 'Thinking...'; + if (isListening) return 'Listening...'; + return null; + }; + + const getStatusColor = () => { + if (connectionStatus === 'error') return '#EF4444'; + if (connectionStatus === 'connecting') return '#F59E0B'; + if (isListening) return '#10B981'; + if (isProcessing) return '#F59E0B'; + if (isSpeaking) return '#8B5CF6'; + return '#4A90A4'; + }; + + const statusText = getStatusText(); + + // Only show when there's something meaningful to display + if (!statusText) { + return null; + } + + return ( + + + {statusText} + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + marginBottom: 8, + }, + statusText: { + fontSize: 16, + fontWeight: '500', + }, +}); diff --git a/src/components/TranscriptView.tsx b/src/components/TranscriptView.tsx new file mode 100644 index 0000000..f80a1eb --- /dev/null +++ b/src/components/TranscriptView.tsx @@ -0,0 +1,110 @@ +import React, { useRef, useEffect } from 'react'; +import { View, Text, ScrollView, StyleSheet } from 'react-native'; + +interface Message { + role: 'user' | 'assistant'; + content: string; + timestamp: Date; +} + +interface TranscriptViewProps { + messages: Message[]; + currentTranscript?: string; +} + +export function TranscriptView({ messages, currentTranscript }: TranscriptViewProps) { + const scrollViewRef = useRef(null); + + useEffect(() => { + scrollViewRef.current?.scrollToEnd({ animated: true }); + }, [messages, currentTranscript]); + + if (messages.length === 0 && !currentTranscript) { + return ( + + + Your conversation will appear here + + + ); + } + + return ( + + {messages.map((message, index) => ( + + + {message.role === 'user' ? 'You' : 'Wellnuo'} + + {message.content} + + ))} + {currentTranscript && ( + + You (listening...) + {currentTranscript} + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + content: { + padding: 16, + gap: 12, + }, + emptyContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + padding: 32, + }, + emptyText: { + fontSize: 16, + color: '#9CA3AF', + textAlign: 'center', + }, + messageContainer: { + padding: 12, + borderRadius: 12, + maxWidth: '85%', + }, + userMessage: { + backgroundColor: '#E5E7EB', + alignSelf: 'flex-end', + }, + assistantMessage: { + backgroundColor: '#4A90A4', + alignSelf: 'flex-start', + }, + currentTranscript: { + backgroundColor: '#FEF3C7', + alignSelf: 'flex-end', + opacity: 0.8, + }, + roleLabel: { + fontSize: 11, + fontWeight: '600', + marginBottom: 4, + opacity: 0.7, + }, + messageText: { + fontSize: 15, + lineHeight: 20, + }, +}); diff --git a/src/components/VoiceButton.tsx b/src/components/VoiceButton.tsx new file mode 100644 index 0000000..013535a --- /dev/null +++ b/src/components/VoiceButton.tsx @@ -0,0 +1,281 @@ +import React, { useEffect, useRef } from 'react'; +import { + Pressable, + StyleSheet, + View, + Animated, + ViewStyle, +} from 'react-native'; + +interface VoiceButtonProps { + isListening: boolean; + isSpeaking: boolean; + isProcessing: boolean; + isInCall?: boolean; + disabled?: boolean; + onPressIn: () => void; + onPressOut: () => void; + style?: ViewStyle; +} + +export function VoiceButton({ + isListening, + isSpeaking, + isProcessing, + isInCall = false, + disabled, + onPressIn, + onPressOut, + style, +}: VoiceButtonProps) { + // Separate animated values to avoid mixing native/JS drivers + const buttonScaleAnim = useRef(new Animated.Value(1)).current; + const glowOpacityAnim = useRef(new Animated.Value(0)).current; + const pulseAnimRef = useRef(null); + + useEffect(() => { + if (isListening) { + // Pulse animation while listening + pulseAnimRef.current = Animated.loop( + Animated.sequence([ + Animated.timing(buttonScaleAnim, { + toValue: 1.1, + duration: 500, + useNativeDriver: true, + }), + Animated.timing(buttonScaleAnim, { + toValue: 1, + duration: 500, + useNativeDriver: true, + }), + ]) + ); + pulseAnimRef.current.start(); + + // Glow animation (separate, non-native) + Animated.timing(glowOpacityAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }).start(); + } else { + // Stop pulse animation + if (pulseAnimRef.current) { + pulseAnimRef.current.stop(); + pulseAnimRef.current = null; + } + // Reset scale with native driver + Animated.timing(buttonScaleAnim, { + toValue: 1, + duration: 150, + useNativeDriver: true, + }).start(); + + // Fade out glow + Animated.timing(glowOpacityAnim, { + toValue: 0, + duration: 300, + useNativeDriver: true, + }).start(); + } + }, [isListening, buttonScaleAnim, glowOpacityAnim]); + + useEffect(() => { + if (isSpeaking && !isListening) { + // Wave animation while speaking + pulseAnimRef.current = Animated.loop( + Animated.sequence([ + Animated.timing(buttonScaleAnim, { + toValue: 1.05, + duration: 300, + useNativeDriver: true, + }), + Animated.timing(buttonScaleAnim, { + toValue: 1, + duration: 300, + useNativeDriver: true, + }), + ]) + ); + pulseAnimRef.current.start(); + } else if (!isListening && !isSpeaking) { + if (pulseAnimRef.current) { + pulseAnimRef.current.stop(); + pulseAnimRef.current = null; + } + } + }, [isSpeaking, isListening, buttonScaleAnim]); + + const getButtonColor = () => { + if (disabled) return '#9CA3AF'; + if (isInCall) return '#EF4444'; // Red when in call (to indicate "end call") + if (isListening) return '#10B981'; // Green when listening + if (isSpeaking) return '#8B5CF6'; + if (isProcessing) return '#F59E0B'; + return '#4A90A4'; // Blue when ready to start + }; + + return ( + + + [ + styles.buttonWrapper, + pressed && { opacity: 0.8 }, + ]} + > + + + {isInCall ? ( + + ) : isSpeaking ? ( + + ) : isProcessing ? ( + + ) : ( + + )} + + + + + ); +} + +function PhoneIcon() { + return ( + + + + ); +} + +function EndCallIcon() { + return ( + + + + ); +} + +function SpeakingIcon() { + return ( + + + + + + ); +} + +function ProcessingIcon() { + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + justifyContent: 'center', + }, + glow: { + position: 'absolute', + width: 140, + height: 140, + borderRadius: 70, + }, + buttonWrapper: { + // Hit area for touch + }, + button: { + width: 100, + height: 100, + borderRadius: 50, + alignItems: 'center', + justifyContent: 'center', + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 8, + }, + iconContainer: { + alignItems: 'center', + justifyContent: 'center', + }, + phoneIcon: { + alignItems: 'center', + justifyContent: 'center', + }, + phoneReceiver: { + width: 36, + height: 14, + backgroundColor: 'white', + borderRadius: 7, + transform: [{ rotate: '-45deg' }], + }, + endCallIcon: { + alignItems: 'center', + justifyContent: 'center', + }, + phoneReceiverRotated: { + width: 36, + height: 14, + backgroundColor: 'white', + borderRadius: 7, + transform: [{ rotate: '135deg' }], + }, + speakingIcon: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + wave: { + width: 4, + backgroundColor: 'white', + borderRadius: 2, + }, + wave1: { + height: 20, + }, + wave2: { + height: 32, + }, + wave3: { + height: 20, + }, + processingIcon: { + flexDirection: 'row', + gap: 6, + }, + dot: { + width: 10, + height: 10, + backgroundColor: 'white', + borderRadius: 5, + }, +}); diff --git a/src/components/ZAIAnalysis.jsx b/src/components/ZAIAnalysis.jsx new file mode 100644 index 0000000..9de00b9 --- /dev/null +++ b/src/components/ZAIAnalysis.jsx @@ -0,0 +1,137 @@ +import React, { useState } from 'react'; +import { View, Text, TouchableOpacity, Image, Alert } from 'react-native'; +import * as ImagePicker from 'expo-image-picker'; +import ZAIMCPClient from '../utils/zaiMCPClient'; + +const ZAIAnalysis = () => { + const [selectedImage, setSelectedImage] = useState(null); + const [analysis, setAnalysis] = useState(''); + const [loading, setLoading] = useState(false); + const [zaiClient, setZaiClient] = useState(null); + + const initializeZAI = async () => { + try { + const client = new ZAIMCPClient(); + await client.start(); + setZaiClient(client); + console.log('Z.AI MCP Client initialized'); + } catch (error) { + console.error('Failed to initialize Z.AI:', error); + Alert.alert('Error', 'Failed to initialize Z.AI client'); + } + }; + + const pickImage = async () => { + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + allowsEditing: true, + aspect: [4, 3], + quality: 1, + }); + + if (!result.canceled) { + setSelectedImage(result.assets[0].uri); + } + }; + + const analyzeImage = async () => { + if (!selectedImage || !zaiClient) { + Alert.alert('Error', 'Please select an image and initialize Z.AI'); + return; + } + + setLoading(true); + try { + const result = await zaiClient.analyzeImage( + selectedImage, + 'Describe in detail the layout structure, color style, main components, and interactive elements of the website in this image to facilitate subsequent code generation by the model.' + ); + setAnalysis(result.content || result); + } catch (error) { + console.error('Analysis failed:', error); + Alert.alert('Error', 'Failed to analyze image'); + } finally { + setLoading(false); + } + }; + + return ( + + + Z.AI Image Analysis + + + + + Initialize Z.AI Client + + + + + + Pick Image + + + + {selectedImage && ( + + + + )} + + + + {loading ? 'Analyzing...' : 'Analyze Image'} + + + + {analysis && ( + + + Analysis Result: + + {analysis} + + )} + + ); +}; + +export default ZAIAnalysis; \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..8930841 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,4 @@ +export { VoiceButton } from './VoiceButton'; +export { StatusIndicator } from './StatusIndicator'; +export { TranscriptView } from './TranscriptView'; +export { DashboardWebView } from './DashboardWebView'; diff --git a/src/hooks/useVoiceAssistant.ts b/src/hooks/useVoiceAssistant.ts new file mode 100644 index 0000000..e7765e7 --- /dev/null +++ b/src/hooks/useVoiceAssistant.ts @@ -0,0 +1,1013 @@ +import { useState, useRef, useCallback, useEffect } from 'react'; +import { Audio } from 'expo-av'; +import { Platform, AppState, AppStateStatus, Linking } from 'react-native'; +import { WebhookContext, VoiceAssistantState, ConnectionStatus } from '../types'; +import { createWavDataUri, wavToPcm16Base64 } from '../utils/audioConverter'; + +export type PermissionStatus = 'undetermined' | 'granted' | 'denied'; + +// Updated to current model name - gpt-4o-realtime-preview is the current alias +const OPENAI_REALTIME_URL = 'wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview'; +const WS_CONNECT_TIMEOUT = 5000; // 5 секунд по схеме +const MAX_RETRIES = 3; + +// ============================================ +// SUPER DETAILED LOGGING SYSTEM +// ============================================ +const LOG_COLORS = { + WS: '🔌', // WebSocket + AUDIO: '🎤', // Audio recording + PLAY: '🔊', // Audio playback + STATE: '📊', // State changes + EVENT: '📨', // OpenAI events + ERROR: '❌', // Errors + PERM: '🔐', // Permissions + CONN: '🌐', // Connection + VAD: '👂', // Voice Activity Detection + BARGE: '🛑', // Barge-in +}; + +function log(category: keyof typeof LOG_COLORS, message: string, data?: any) { + const timestamp = new Date().toISOString().substr(11, 12); + const prefix = `[${timestamp}] ${LOG_COLORS[category]} [${category}]`; + if (data !== undefined) { + console.log(`${prefix} ${message}`, typeof data === 'object' ? JSON.stringify(data, null, 2) : data); + } else { + console.log(`${prefix} ${message}`); + } +} + +function logState(label: string, state: any) { + log('STATE', `${label}:`, state); +} + +// Retry с exponential backoff +async function retryWithBackoff( + fn: () => Promise, + maxRetries: number = MAX_RETRIES, + baseDelay: number = 1000 +): Promise { + log('CONN', `Starting retry with backoff. Max retries: ${maxRetries}, Base delay: ${baseDelay}ms`); + let lastError: Error | null = null; + for (let i = 0; i < maxRetries; i++) { + try { + log('CONN', `Attempt ${i + 1}/${maxRetries}...`); + const result = await fn(); + log('CONN', `✅ Attempt ${i + 1} succeeded!`); + return result; + } catch (err) { + lastError = err as Error; + log('ERROR', `❌ Attempt ${i + 1} failed:`, lastError.message); + if (i < maxRetries - 1) { + const delay = Math.pow(2, i) * baseDelay; // 1s, 2s, 4s + log('CONN', `⏳ Waiting ${delay}ms before retry ${i + 2}...`); + await new Promise(r => setTimeout(r, delay)); + } + } + } + log('ERROR', `❌ All ${maxRetries} attempts failed`); + throw lastError; +} + +// WebSocket подключение с timeout +function connectWebSocketWithTimeout( + url: string, + protocols: string[], + timeout: number +): Promise { + log('WS', `Connecting to WebSocket with ${timeout}ms timeout...`); + log('WS', `URL: ${url}`); + log('WS', `Protocols: ${protocols.map(p => p.startsWith('openai-insecure-api-key') ? 'openai-insecure-api-key.***' : p).join(', ')}`); + + return new Promise((resolve, reject) => { + log('WS', 'Creating WebSocket instance...'); + const ws = new WebSocket(url, protocols); + + const timer = setTimeout(() => { + log('ERROR', `❌ WebSocket connection timeout after ${timeout}ms`); + ws.close(); + reject(new Error('WebSocket connection timeout')); + }, timeout); + + ws.onopen = () => { + log('WS', '✅ WebSocket onopen fired'); + clearTimeout(timer); + resolve(ws); + }; + + ws.onerror = (error) => { + log('ERROR', '❌ WebSocket onerror during connection:', error); + clearTimeout(timer); + reject(new Error('WebSocket connection error')); + }; + }); +} + +interface UseVoiceAssistantOptions { + apiKey: string; + context: WebhookContext; + onTranscript?: (text: string, isFinal: boolean) => void; + onAssistantResponse?: (text: string) => void; +} + +export function useVoiceAssistant({ + apiKey, + context, + onTranscript, + onAssistantResponse, +}: UseVoiceAssistantOptions) { + const [state, setState] = useState({ + isConnected: false, + isListening: false, + isSpeaking: false, + isProcessing: false, + error: null, + }); + const [connectionStatus, setConnectionStatus] = useState('disconnected'); + const [permissionStatus, setPermissionStatus] = useState('undetermined'); + const [isInConversation, setIsInConversation] = useState(false); + + const wsRef = useRef(null); + const recordingRef = useRef(null); + const soundRef = useRef(null); + const audioQueueRef = useRef([]); + const isPlayingRef = useRef(false); + const recordingStartTimeRef = useRef(null); + const audioStreamIntervalRef = useRef(null); + + // Setup audio mode + const setupAudio = useCallback(async () => { + log('AUDIO', '=== SETUP AUDIO MODE ==='); + try { + const config = { + allowsRecordingIOS: true, + playsInSilentModeIOS: true, + staysActiveInBackground: true, + shouldDuckAndroid: true, + playThroughEarpieceAndroid: false, + }; + log('AUDIO', 'Setting audio mode with config:', config); + await Audio.setAudioModeAsync(config); + log('AUDIO', '✅ Audio mode set successfully'); + } catch (error) { + log('ERROR', 'Failed to setup audio:', error); + } + }, []); + + // Check microphone permission + const checkPermission = useCallback(async (): Promise => { + log('PERM', '=== CHECK PERMISSION ==='); + try { + const result = await Audio.getPermissionsAsync(); + log('PERM', 'Permission check result:', result); + const { status } = result; + if (status === 'granted') { + log('PERM', '✅ Permission GRANTED'); + setPermissionStatus('granted'); + return 'granted'; + } else if (status === 'denied') { + log('PERM', '❌ Permission DENIED'); + setPermissionStatus('denied'); + return 'denied'; + } else { + log('PERM', '⚠️ Permission UNDETERMINED'); + setPermissionStatus('undetermined'); + return 'undetermined'; + } + } catch (error) { + log('ERROR', 'Failed to check permission:', error); + return 'undetermined'; + } + }, []); + + // Request microphone permission + const requestPermission = useCallback(async (): Promise => { + log('PERM', '=== REQUEST PERMISSION ==='); + try { + log('PERM', 'Requesting microphone permission from user...'); + const result = await Audio.requestPermissionsAsync(); + log('PERM', 'Permission request result:', result); + const { status } = result; + if (status === 'granted') { + log('PERM', '✅ Permission GRANTED by user'); + setPermissionStatus('granted'); + return 'granted'; + } else { + log('PERM', '❌ Permission DENIED by user'); + setPermissionStatus('denied'); + return 'denied'; + } + } catch (error) { + log('ERROR', 'Failed to request permission:', error); + return 'denied'; + } + }, []); + + // Open app settings + const openSettings = useCallback(() => { + Linking.openSettings(); + }, []); + + // Connect to OpenAI Realtime API with timeout and retry + const connect = useCallback(async () => { + log('CONN', '========================================'); + log('CONN', '=== CONNECT TO OPENAI REALTIME API ==='); + log('CONN', '========================================'); + + if (wsRef.current?.readyState === WebSocket.OPEN) { + log('WS', '⚠️ WebSocket already open, skipping connect'); + return; + } + + log('CONN', 'Current WS state:', wsRef.current?.readyState); + logState('Setting connectionStatus', 'connecting'); + setConnectionStatus('connecting'); + setState(prev => ({ ...prev, error: null })); + + try { + log('AUDIO', 'Setting up audio before connection...'); + await setupAudio(); + + log('WS', `Connecting to: ${OPENAI_REALTIME_URL}`); + log('WS', `Timeout: ${WS_CONNECT_TIMEOUT}ms, Max retries: ${MAX_RETRIES}`); + log('WS', `API Key (first 10 chars): ${apiKey.substring(0, 10)}...`); + + // Подключение с retry и timeout (5 сек по схеме) + const ws = await retryWithBackoff(async () => { + log('WS', '🔄 Attempting WebSocket connection...'); + return connectWebSocketWithTimeout( + OPENAI_REALTIME_URL, + [ + 'realtime', + `openai-insecure-api-key.${apiKey}`, + 'openai-beta.realtime-v1', + ], + WS_CONNECT_TIMEOUT + ); + }); + + log('WS', '✅ WebSocket connected successfully!'); + log('WS', `WebSocket readyState: ${ws.readyState}`); + setConnectionStatus('connected'); + setState(prev => ({ ...prev, isConnected: true })); + logState('State after connect', { isConnected: true }); + + // Configure session + const sessionConfig = { + type: 'session.update', + session: { + modalities: ['text', 'audio'], + instructions: context.systemPrompt, + voice: context.voiceSettings?.voice || 'shimmer', + input_audio_format: 'pcm16', + output_audio_format: 'pcm16', + input_audio_transcription: { + model: 'whisper-1', + }, + turn_detection: { + type: 'server_vad', + threshold: 0.5, + prefix_padding_ms: 300, + silence_duration_ms: 500, + }, + }, + }; + + log('WS', '📤 Sending session.update config:', { + voice: sessionConfig.session.voice, + modalities: sessionConfig.session.modalities, + input_format: sessionConfig.session.input_audio_format, + output_format: sessionConfig.session.output_audio_format, + vad_threshold: sessionConfig.session.turn_detection.threshold, + silence_duration: sessionConfig.session.turn_detection.silence_duration_ms, + }); + log('WS', 'System prompt (first 100 chars):', context.systemPrompt?.substring(0, 100)); + + ws.send(JSON.stringify(sessionConfig)); + log('WS', '✅ session.update sent'); + + ws.onmessage = async (event) => { + try { + const data = JSON.parse(event.data); + log('EVENT', `📨 Received event: ${data.type}`); + handleRealtimeEvent(data); + } catch (error) { + log('ERROR', 'Failed to parse WebSocket message:', error); + } + }; + + ws.onerror = (error) => { + log('ERROR', '❌ WebSocket error:', error); + setConnectionStatus('error'); + setState(prev => ({ ...prev, error: 'Connection error', isConnected: false })); + }; + + ws.onclose = (event) => { + log('WS', `🔌 WebSocket closed. Code: ${event.code}, Reason: ${event.reason || 'none'}`); + setConnectionStatus('disconnected'); + setState(prev => ({ ...prev, isConnected: false })); + }; + + wsRef.current = ws; + log('CONN', '✅ Connection setup complete'); + } catch (error) { + log('ERROR', '❌ Failed to connect after all retries:', error); + setConnectionStatus('error'); + const errorMessage = error instanceof Error ? error.message : 'Failed to connect'; + setState(prev => ({ ...prev, error: `Connection failed: ${errorMessage}` })); + } + }, [apiKey, context, setupAudio]); + + // Handle realtime events from OpenAI + const handleRealtimeEvent = useCallback((event: any) => { + const eventType = event.type; + + switch (eventType) { + case 'session.created': + log('EVENT', '✅ SESSION CREATED'); + log('EVENT', 'Session ID:', event.session?.id); + log('EVENT', 'Session expires at:', event.session?.expires_at); + break; + + case 'session.updated': + log('EVENT', '✅ SESSION UPDATED - Ready to communicate!'); + log('EVENT', 'Voice:', event.session?.voice); + log('EVENT', 'Modalities:', event.session?.modalities); + log('EVENT', 'Turn detection:', event.session?.turn_detection?.type); + + // AI speaks first - use response.create with instructions override + // This is the correct way to trigger AI-initiated speech with server_vad + if (wsRef.current?.readyState === WebSocket.OPEN) { + log('WS', '📤 Triggering AI greeting with response.create...'); + + // Use response.create with inline instructions - works with server_vad + const greetingRequest = { + type: 'response.create', + response: { + modalities: ['text', 'audio'], + instructions: 'The user has just connected. Please greet them warmly as Julia, their care assistant. Briefly mention that you are here to help them stay connected with their loved one. Keep it concise (1-2 sentences), caring, and natural. Speak in English.', + }, + }; + wsRef.current.send(JSON.stringify(greetingRequest)); + log('WS', '✅ response.create with greeting instructions sent'); + + setState(prev => ({ ...prev, isProcessing: true })); + logState('After greeting request', { isProcessing: true }); + } else { + log('ERROR', 'WebSocket not open, cannot send greeting'); + } + break; + + case 'input_audio_buffer.speech_started': + log('VAD', '🎙️ SPEECH STARTED - User started talking'); + log('VAD', 'isPlayingRef:', isPlayingRef.current); + log('VAD', 'state.isSpeaking:', state.isSpeaking); + + // BARGE-IN: Если Julia говорит, прерываем её + if (isPlayingRef.current || state.isSpeaking) { + log('BARGE', '🛑 BARGE-IN DETECTED! Interrupting Julia...'); + log('BARGE', 'Audio queue length before clear:', audioQueueRef.current.length); + + // 1. Останавливаем воспроизведение + if (soundRef.current) { + log('BARGE', 'Stopping current audio playback'); + soundRef.current.stopAsync(); + } + // 2. Очищаем очередь аудио + audioQueueRef.current = []; + isPlayingRef.current = false; + log('BARGE', 'Audio queue cleared'); + + // 3. Отправляем response.cancel + if (wsRef.current?.readyState === WebSocket.OPEN) { + log('BARGE', '📤 Sending response.cancel'); + wsRef.current.send(JSON.stringify({ type: 'response.cancel' })); + } + setState(prev => ({ ...prev, isSpeaking: false, isProcessing: false })); + log('BARGE', '✅ Barge-in complete'); + } + + setState(prev => ({ ...prev, isListening: true })); + logState('After speech_started', { isListening: true }); + break; + + case 'input_audio_buffer.speech_stopped': + log('VAD', '🔇 SPEECH STOPPED - User stopped talking'); + setState(prev => ({ ...prev, isListening: false, isProcessing: true })); + logState('After speech_stopped', { isListening: false, isProcessing: true }); + break; + + case 'input_audio_buffer.committed': + log('AUDIO', '✅ Audio buffer committed'); + log('AUDIO', 'Item ID:', event.item_id); + break; + + case 'conversation.item.created': + log('EVENT', '📝 Conversation item created'); + log('EVENT', 'Item type:', event.item?.type); + log('EVENT', 'Item role:', event.item?.role); + log('EVENT', 'Item ID:', event.item?.id); + break; + + case 'conversation.item.input_audio_transcription.completed': + log('EVENT', '📝 USER TRANSCRIPTION COMPLETED'); + log('EVENT', 'Transcript:', event.transcript); + if (event.transcript && onTranscript) { + onTranscript(event.transcript, true); + } + break; + + case 'response.created': + log('EVENT', '🤖 RESPONSE CREATED - AI is preparing response'); + log('EVENT', 'Response ID:', event.response?.id); + break; + + case 'response.output_item.added': + log('EVENT', '📦 Output item added to response'); + log('EVENT', 'Item type:', event.item?.type); + break; + + case 'response.content_part.added': + log('EVENT', '📄 Content part added'); + log('EVENT', 'Part type:', event.part?.type); + break; + + case 'response.audio_transcript.delta': + // Log less frequently to avoid spam (every 50 chars) + if (event.delta) { + log('EVENT', `🗣️ AI transcript delta (${event.delta.length} chars): "${event.delta.substring(0, 30)}..."`); + if (onAssistantResponse) { + onAssistantResponse(event.delta); + } + } + break; + + case 'response.audio.delta': + if (event.delta) { + const audioLen = event.delta.length; + log('PLAY', `🔊 Audio delta received: ${audioLen} bytes (base64), queue size: ${audioQueueRef.current.length}`); + audioQueueRef.current.push(event.delta); + playNextAudio(); + } + break; + + case 'response.audio.done': + log('PLAY', '🔊 Audio stream complete from OpenAI'); + log('PLAY', 'Remaining in queue:', audioQueueRef.current.length); + setState(prev => ({ ...prev, isSpeaking: false })); + break; + + case 'response.audio_transcript.done': + log('EVENT', '📝 AI transcript complete'); + log('EVENT', 'Full transcript:', event.transcript?.substring(0, 100)); + break; + + case 'response.content_part.done': + log('EVENT', '📄 Content part done'); + break; + + case 'response.output_item.done': + log('EVENT', '📦 Output item done'); + break; + + case 'response.done': + log('EVENT', '✅ RESPONSE DONE - AI finished responding'); + log('EVENT', 'Response status:', event.response?.status); + log('EVENT', 'Usage:', event.response?.usage); + // Log more details if the response failed + if (event.response?.status === 'failed') { + log('ERROR', '❌ Response failed! Status details:', event.response?.status_details); + log('ERROR', 'Full response object:', event.response); + } + setState(prev => ({ ...prev, isProcessing: false, isSpeaking: false })); + logState('After response.done', { isProcessing: false, isSpeaking: false }); + break; + + case 'rate_limits.updated': + log('EVENT', '📊 Rate limits updated'); + log('EVENT', 'Limits:', event.rate_limits); + break; + + case 'error': + // Ignore non-critical errors like response_cancel_not_active + if (event.error?.code === 'response_cancel_not_active') { + log('EVENT', '⚠️ response_cancel_not_active (safe to ignore)'); + return; + } + log('ERROR', '❌ REALTIME API ERROR'); + log('ERROR', 'Error type:', event.error?.type); + log('ERROR', 'Error code:', event.error?.code); + log('ERROR', 'Error message:', event.error?.message); + log('ERROR', 'Full error:', event.error); + setState(prev => ({ ...prev, error: event.error?.message || 'Unknown error' })); + break; + + default: + log('EVENT', `📨 Unhandled event type: ${eventType}`); + log('EVENT', 'Event data:', event); + } + }, [onTranscript, onAssistantResponse]); + + // Play audio from queue + const playNextAudio = useCallback(async () => { + if (isPlayingRef.current) { + log('PLAY', '⏸️ Already playing, skipping playNextAudio'); + return; + } + if (audioQueueRef.current.length === 0) { + log('PLAY', '📭 Audio queue empty, nothing to play'); + return; + } + + log('PLAY', `▶️ Starting playback. Queue size: ${audioQueueRef.current.length}`); + isPlayingRef.current = true; + setState(prev => ({ ...prev, isSpeaking: true })); + + try { + const audioBase64 = audioQueueRef.current.shift(); + if (!audioBase64) { + log('PLAY', '⚠️ No audio data after shift'); + isPlayingRef.current = false; + return; + } + + log('PLAY', `📦 Audio chunk size: ${audioBase64.length} bytes (base64)`); + log('PLAY', 'Converting PCM16 to WAV format...'); + + // Convert PCM16 to WAV format with proper headers for playback + const uri = createWavDataUri(audioBase64); + log('PLAY', `WAV URI created: ${uri.substring(0, 50)}...`); + + log('PLAY', 'Creating Audio.Sound...'); + const { sound } = await Audio.Sound.createAsync( + { uri }, + { shouldPlay: true } + ); + log('PLAY', '✅ Sound created and playing'); + + soundRef.current = sound; + + sound.setOnPlaybackStatusUpdate((status) => { + if (status.isLoaded && status.didJustFinish) { + log('PLAY', '✅ Audio chunk finished, checking queue...'); + log('PLAY', `Remaining in queue: ${audioQueueRef.current.length}`); + isPlayingRef.current = false; + playNextAudio(); + } + }); + } catch (error) { + log('ERROR', '❌ Failed to play audio:', error); + isPlayingRef.current = false; + // Try next audio in queue + if (audioQueueRef.current.length > 0) { + log('PLAY', '🔄 Trying next audio in queue...'); + playNextAudio(); + } + } + }, []); + + // Recording config for reuse + const recordingConfig = { + android: { + extension: '.wav', + outputFormat: Audio.AndroidOutputFormat.DEFAULT, + audioEncoder: Audio.AndroidAudioEncoder.DEFAULT, + sampleRate: 24000, + numberOfChannels: 1, + bitRate: 128000, + }, + ios: { + extension: '.wav', + outputFormat: Audio.IOSOutputFormat.LINEARPCM, + audioQuality: Audio.IOSAudioQuality.HIGH, + sampleRate: 24000, + numberOfChannels: 1, + bitRate: 128000, + linearPCMBitDepth: 16, + linearPCMIsBigEndian: false, + linearPCMIsFloat: false, + }, + web: { + mimeType: 'audio/webm', + bitsPerSecond: 128000, + }, + }; + + // Start a new recording segment + const startNewRecording = useCallback(async (): Promise => { + log('AUDIO', '🎙️ Starting new recording segment...'); + try { + const recording = new Audio.Recording(); + log('AUDIO', 'Preparing recording with config:', { + sampleRate: recordingConfig.ios.sampleRate, + channels: recordingConfig.ios.numberOfChannels, + bitDepth: recordingConfig.ios.linearPCMBitDepth, + }); + await recording.prepareToRecordAsync(recordingConfig); + log('AUDIO', 'Recording prepared, starting...'); + await recording.startAsync(); + log('AUDIO', '✅ Recording started successfully'); + return recording; + } catch (error) { + log('ERROR', '❌ Failed to start new recording:', error); + return null; + } + }, []); + + // Send audio chunk to WebSocket + const sendAudioChunk = useCallback(async (recording: Audio.Recording) => { + log('AUDIO', '📤 Sending audio chunk to WebSocket...'); + try { + log('AUDIO', 'Stopping and unloading recording...'); + await recording.stopAndUnloadAsync(); + const uri = recording.getURI(); + log('AUDIO', 'Recording URI:', uri); + + if (uri && wsRef.current?.readyState === WebSocket.OPEN) { + log('AUDIO', 'Fetching audio file...'); + const response = await fetch(uri); + const blob = await response.blob(); + log('AUDIO', `Audio blob size: ${blob.size} bytes`); + + const reader = new FileReader(); + reader.onload = () => { + const wavBase64 = (reader.result as string).split(',')[1]; + if (wavBase64 && wsRef.current?.readyState === WebSocket.OPEN) { + // CRITICAL: Strip WAV headers - OpenAI expects raw PCM16, not WAV + log('AUDIO', `📦 WAV base64 length: ${wavBase64.length}`); + const pcm16Base64 = wavToPcm16Base64(wavBase64); + log('AUDIO', `📤 Sending input_audio_buffer.append (${pcm16Base64.length} bytes raw PCM16)`); + wsRef.current.send(JSON.stringify({ + type: 'input_audio_buffer.append', + audio: pcm16Base64, + })); + log('AUDIO', '✅ Audio chunk sent (WAV headers stripped)'); + } else { + log('AUDIO', '⚠️ Skipped send - no base64 or WS not open'); + } + }; + reader.onerror = (err) => { + log('ERROR', '❌ FileReader error:', err); + }; + reader.readAsDataURL(blob); + } else { + log('AUDIO', '⚠️ Cannot send - no URI or WS not open'); + log('AUDIO', 'URI:', uri); + log('AUDIO', 'WS readyState:', wsRef.current?.readyState); + } + } catch (error) { + log('ERROR', '❌ Failed to send audio chunk:', error); + } + }, []); + + // Start continuous listening mode + const startContinuousListening = useCallback(async () => { + log('AUDIO', '========================================'); + log('AUDIO', '=== START CONTINUOUS LISTENING ==='); + log('AUDIO', '========================================'); + + if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) { + log('ERROR', '❌ WebSocket not connected'); + log('ERROR', 'wsRef.current:', !!wsRef.current); + log('ERROR', 'readyState:', wsRef.current?.readyState); + return false; + } + + // Check permission first + log('PERM', 'Checking permission before starting...'); + const permStatus = await checkPermission(); + if (permStatus === 'undetermined') { + log('PERM', 'Permission undetermined, requesting...'); + const newStatus = await requestPermission(); + if (newStatus !== 'granted') { + log('PERM', '❌ Permission denied after request'); + setPermissionStatus('denied'); + return false; + } + } else if (permStatus === 'denied') { + log('PERM', '❌ Permission already denied'); + return false; + } + + log('PERM', '✅ Permission granted, proceeding with recording'); + + try { + log('AUDIO', 'Setting audio mode for recording...'); + await Audio.setAudioModeAsync({ + allowsRecordingIOS: true, + playsInSilentModeIOS: true, + }); + log('AUDIO', '✅ Audio mode set'); + + // Start first recording + log('AUDIO', 'Starting first recording segment...'); + const recording = await startNewRecording(); + if (!recording) { + log('ERROR', '❌ Failed to start first recording'); + return false; + } + + recordingRef.current = recording; + recordingStartTimeRef.current = Date.now(); + setIsInConversation(true); + log('AUDIO', '✅ First recording started, entering conversation mode'); + log('AUDIO', 'Setting up 300ms streaming interval...'); + + let chunkCounter = 0; + // Start continuous streaming - send chunks every 300ms + audioStreamIntervalRef.current = setInterval(async () => { + chunkCounter++; + if (!recordingRef.current || !wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) { + log('AUDIO', `⚠️ Interval check failed at chunk #${chunkCounter}`); + log('AUDIO', 'recordingRef:', !!recordingRef.current); + log('AUDIO', 'wsRef:', !!wsRef.current); + log('AUDIO', 'readyState:', wsRef.current?.readyState); + if (audioStreamIntervalRef.current) { + clearInterval(audioStreamIntervalRef.current); + audioStreamIntervalRef.current = null; + log('AUDIO', '🛑 Streaming interval cleared'); + } + return; + } + + try { + log('AUDIO', `📤 Streaming chunk #${chunkCounter}...`); + const currentRecording = recordingRef.current; + recordingRef.current = null; + + // FIRST: Stop and unload current recording + if (currentRecording) { + await sendAudioChunk(currentRecording); + } + + // THEN: Start new recording (after old one is released) + const newRecording = await startNewRecording(); + recordingRef.current = newRecording; + } catch (error) { + log('ERROR', `❌ Error in audio stream chunk #${chunkCounter}:`, error); + } + }, 300); + + log('AUDIO', '✅ Continuous listening started successfully'); + return true; + } catch (error) { + log('ERROR', '❌ Failed to start continuous listening:', error); + setState(prev => ({ ...prev, error: 'Failed to start recording' })); + return false; + } + }, [checkPermission, requestPermission, startNewRecording, sendAudioChunk]); + + // Stop continuous listening + const stopContinuousListening = useCallback(async () => { + log('AUDIO', '========================================'); + log('AUDIO', '=== STOP CONTINUOUS LISTENING ==='); + log('AUDIO', '========================================'); + + // Clear the streaming interval + if (audioStreamIntervalRef.current) { + log('AUDIO', 'Clearing streaming interval...'); + clearInterval(audioStreamIntervalRef.current); + audioStreamIntervalRef.current = null; + log('AUDIO', '✅ Interval cleared'); + } + + // Stop and send any remaining recording + if (recordingRef.current) { + log('AUDIO', 'Sending final recording chunk...'); + try { + await sendAudioChunk(recordingRef.current); + log('AUDIO', '✅ Final chunk sent'); + } catch (e) { + log('AUDIO', '⚠️ Error sending final chunk (ignored):', e); + } + recordingRef.current = null; + } + + recordingStartTimeRef.current = null; + setIsInConversation(false); + log('AUDIO', '✅ Continuous listening stopped'); + }, [sendAudioChunk]); + + // Legacy startListening for compatibility + const startListening = startContinuousListening; + + // Legacy streamAudioChunks (no longer needed) + const streamAudioChunks = useCallback(() => {}, []); + + // Stop recording and send final audio + const stopListening = useCallback(async () => { + log('AUDIO', '=== STOP LISTENING (Legacy) ==='); + + if (!recordingRef.current) { + log('AUDIO', '⚠️ No active recording to stop'); + return; + } + + try { + // Check minimum recording duration (100ms minimum required by OpenAI) + const MIN_RECORDING_DURATION = 150; // 150ms to be safe + const recordingDuration = recordingStartTimeRef.current + ? Date.now() - recordingStartTimeRef.current + : 0; + + log('AUDIO', `Recording duration: ${recordingDuration}ms`); + log('AUDIO', 'Stopping and unloading recording...'); + await recordingRef.current.stopAndUnloadAsync(); + const uri = recordingRef.current.getURI(); + log('AUDIO', 'Recording URI:', uri); + + // Only send audio if recording was long enough + if (recordingDuration < MIN_RECORDING_DURATION) { + log('AUDIO', `⚠️ Recording too short (${recordingDuration}ms < ${MIN_RECORDING_DURATION}ms), skipping send`); + recordingRef.current = null; + recordingStartTimeRef.current = null; + setState(prev => ({ ...prev, isListening: false })); + return; + } + + if (uri && wsRef.current?.readyState === WebSocket.OPEN) { + log('AUDIO', 'Sending final audio and committing buffer...'); + // Read the audio file and send as base64 + const response = await fetch(uri); + const blob = await response.blob(); + log('AUDIO', `Audio blob size: ${blob.size} bytes`); + + const reader = new FileReader(); + reader.onload = () => { + const wavBase64 = (reader.result as string).split(',')[1]; + // CRITICAL: Strip WAV headers - OpenAI expects raw PCM16, not WAV + const pcm16Base64 = wavToPcm16Base64(wavBase64); + log('AUDIO', `📤 Sending input_audio_buffer.append (${pcm16Base64.length} bytes raw PCM16)`); + + // Send audio to OpenAI + wsRef.current?.send(JSON.stringify({ + type: 'input_audio_buffer.append', + audio: pcm16Base64, + })); + + log('AUDIO', '📤 Sending input_audio_buffer.commit'); + // Commit the audio buffer + wsRef.current?.send(JSON.stringify({ + type: 'input_audio_buffer.commit', + })); + + log('AUDIO', '📤 Sending response.create'); + // Request response + wsRef.current?.send(JSON.stringify({ + type: 'response.create', + })); + + log('AUDIO', '✅ Final audio sent and response requested (WAV headers stripped)'); + }; + reader.readAsDataURL(blob); + } else { + log('AUDIO', '⚠️ Cannot send - no URI or WS not open'); + } + + recordingRef.current = null; + recordingStartTimeRef.current = null; + setState(prev => ({ ...prev, isListening: false, isProcessing: true })); + logState('After stopListening', { isListening: false, isProcessing: true }); + } catch (error) { + log('ERROR', '❌ Failed to stop recording:', error); + setState(prev => ({ ...prev, error: 'Failed to stop recording' })); + } + }, []); + + // Disconnect from OpenAI + const disconnect = useCallback(() => { + log('CONN', '========================================'); + log('CONN', '=== DISCONNECT FROM OPENAI ==='); + log('CONN', '========================================'); + + // Stop audio streaming + if (audioStreamIntervalRef.current) { + log('AUDIO', 'Clearing audio stream interval...'); + clearInterval(audioStreamIntervalRef.current); + audioStreamIntervalRef.current = null; + } + if (wsRef.current) { + log('WS', `Closing WebSocket (current state: ${wsRef.current.readyState})`); + wsRef.current.close(); + wsRef.current = null; + log('WS', '✅ WebSocket closed'); + } + if (recordingRef.current) { + log('AUDIO', 'Stopping active recording...'); + recordingRef.current.stopAndUnloadAsync(); + recordingRef.current = null; + } + if (soundRef.current) { + log('PLAY', 'Unloading sound...'); + soundRef.current.unloadAsync(); + soundRef.current = null; + } + + const resetState = { + isConnected: false, + isListening: false, + isSpeaking: false, + isProcessing: false, + error: null, + }; + setState(resetState); + setConnectionStatus('disconnected'); + setIsInConversation(false); + logState('After disconnect', resetState); + log('CONN', '✅ Disconnect complete'); + }, []); + + // Interrupt assistant response + const interrupt = useCallback(() => { + log('BARGE', '========================================'); + log('BARGE', '=== MANUAL INTERRUPT ==='); + log('BARGE', '========================================'); + + if (wsRef.current?.readyState === WebSocket.OPEN) { + log('BARGE', '📤 Sending response.cancel'); + wsRef.current.send(JSON.stringify({ + type: 'response.cancel', + })); + } else { + log('BARGE', '⚠️ WebSocket not open, cannot send cancel'); + } + + log('BARGE', `Audio queue length before clear: ${audioQueueRef.current.length}`); + audioQueueRef.current = []; + log('BARGE', 'Audio queue cleared'); + + if (soundRef.current) { + log('BARGE', 'Stopping current sound playback'); + soundRef.current.stopAsync(); + } + + setState(prev => ({ ...prev, isSpeaking: false, isProcessing: false })); + logState('After interrupt', { isSpeaking: false, isProcessing: false }); + log('BARGE', '✅ Interrupt complete'); + }, []); + + // AppState handling - обработка фона/активности по схеме + useEffect(() => { + const handleAppStateChange = (nextState: AppStateStatus) => { + log('STATE', `📱 AppState changed to: ${nextState}`); + + if (nextState === 'background') { + log('STATE', 'App going to background...'); + // Приложение ушло в фон + // Останавливаем запись, но сохраняем WebSocket соединение + if (recordingRef.current) { + log('AUDIO', 'Pausing recording in background'); + recordingRef.current.stopAndUnloadAsync(); + recordingRef.current = null; + setState(prev => ({ ...prev, isListening: false })); + log('STATE', 'Recording paused'); + } + log('STATE', 'WebSocket kept alive in background'); + } else if (nextState === 'active') { + log('STATE', 'App returning to foreground...'); + // Приложение вернулось из фона + // Проверяем WebSocket соединение, переподключаем если нужно + log('STATE', `WS readyState: ${wsRef.current?.readyState}, state.isConnected: ${state.isConnected}`); + if (wsRef.current?.readyState !== WebSocket.OPEN && state.isConnected) { + log('CONN', '🔄 Reconnecting WebSocket after returning from background'); + connect(); + } else { + log('STATE', 'WebSocket still connected, no reconnect needed'); + } + } + }; + + log('STATE', 'Setting up AppState listener'); + const subscription = AppState.addEventListener('change', handleAppStateChange); + return () => { + log('STATE', 'Removing AppState listener'); + subscription.remove(); + }; + }, [state.isConnected, connect]); + + // Cleanup on unmount + useEffect(() => { + log('STATE', '🚀 useVoiceAssistant hook mounted'); + return () => { + log('STATE', '🔚 useVoiceAssistant hook unmounting, cleaning up...'); + disconnect(); + }; + }, [disconnect]); + + return { + state, + connectionStatus, + permissionStatus, + isInConversation, + connect, + disconnect, + startListening, + stopListening, + startContinuousListening, + stopContinuousListening, + interrupt, + checkPermission, + requestPermission, + openSettings, + }; +} diff --git a/src/services/webhookService.ts b/src/services/webhookService.ts new file mode 100644 index 0000000..fb5be41 --- /dev/null +++ b/src/services/webhookService.ts @@ -0,0 +1,322 @@ +import { WebhookContext } from '../types'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const FETCH_TIMEOUT = 10000; // 10 секунд по схеме +const CONVERSATIONS_STORAGE_KEY = '@wellnuo_pending_conversations'; + +// Типы ошибок для обработки по схеме +export type ApiErrorType = + | 'NETWORK_ERROR' + | 'API_KEY_ERROR' + | 'PATIENT_NOT_FOUND' + | 'TIMEOUT_ERROR' + | 'UNKNOWN_ERROR'; + +export class ApiError extends Error { + type: ApiErrorType; + statusCode?: number; + + constructor(type: ApiErrorType, message: string, statusCode?: number) { + super(message); + this.type = type; + this.statusCode = statusCode; + this.name = 'ApiError'; + } +} + +const DEFAULT_CONTEXT: WebhookContext = { + systemPrompt: `You are Julia, a caring AI health assistant for the WellNuo family care system. +You help family members check on their elderly loved ones. Be warm, concise, and reassuring. + +IMPORTANT: Always speak in English by default. Only switch to another language if the user explicitly requests it or speaks to you in that language first. + +When starting a conversation, greet the user warmly and briefly summarize any important health updates or status information about their loved one.`, + voiceSettings: { + voice: 'shimmer', + speed: 1.0, + }, +}; + +function formatPatientContext(data: any): string { + const parts: string[] = []; + + // System prompt from API + if (data.system_prompt) { + parts.push(data.system_prompt); + } + + // Patient info + if (data.patient) { + const p = data.patient; + parts.push(`\n\n## Patient Information +- Name: ${p.name} +- Age: ${p.age} +- Relationship: ${p.relationship} +- Address: ${p.address} +- Emergency Contact: ${p.emergency_contact?.name} (${p.emergency_contact?.relationship}) - ${p.emergency_contact?.phone}`); + } + + // Current status + if (data.current_status) { + const s = data.current_status; + parts.push(`\n\n## Current Status +- Location: ${s.location} +- Mood: ${s.mood} +- Is Sleeping: ${s.is_sleeping ? 'Yes' : 'No'} +- Last Activity: ${new Date(s.last_activity).toLocaleString()}`); + } + + // Health metrics + if (data.health_metrics) { + const h = data.health_metrics; + parts.push(`\n\n## Health Metrics +- Heart Rate: ${h.heart_rate?.current} bpm (resting avg: ${h.heart_rate?.resting_average}, status: ${h.heart_rate?.status}) +- Blood Pressure: ${h.blood_pressure?.last_reading} (${h.blood_pressure?.status}, trend: ${h.blood_pressure?.trend}) +- Blood Oxygen: ${h.blood_oxygen?.current}% (${h.blood_oxygen?.status}) +- Body Temperature: ${h.body_temperature?.current}°C (${h.body_temperature?.status}) +- Weight: ${h.weight?.current_kg} kg (trend: ${h.weight?.trend})`); + } + + // Sleep data + if (data.sleep_data?.last_night) { + const sl = data.sleep_data.last_night; + parts.push(`\n\n## Sleep Data (Last Night) +- Duration: ${sl.duration_hours} hours (quality: ${sl.quality}) +- Deep Sleep: ${sl.deep_sleep_hours}h, REM: ${sl.rem_sleep_hours}h, Light: ${sl.light_sleep_hours}h +- Bathroom Visits: ${sl.bathroom_visits} +- Sleep Score: ${data.sleep_data.sleep_score}/100`); + } + + // Activity data + if (data.activity_data?.today) { + const a = data.activity_data.today; + parts.push(`\n\n## Today's Activity +- Steps: ${a.steps} (goal: ${data.activity_data.goals?.daily_steps}) +- Active Minutes: ${a.active_minutes} +- Calories Burned: ${a.calories_burned} +- Rooms Visited: ${a.rooms_visited?.join(', ')}`); + } + + // Medications + if (data.medications?.daily_schedule) { + const meds = data.medications.daily_schedule + .map((m: any) => `${m.name} ${m.dosage} at ${m.time} - ${m.taken_today ? 'Taken' : 'Not taken'}`) + .join('\n - '); + parts.push(`\n\n## Medications +- Adherence: ${data.medications.adherence_rate}% +- Today's Schedule: + - ${meds}`); + } + + // Recent events + if (data.recent_events?.length > 0) { + const events = data.recent_events + .slice(0, 5) + .map((e: any) => `[${e.type}] ${e.description} (${new Date(e.timestamp).toLocaleString()})`) + .join('\n - '); + parts.push(`\n\n## Recent Events + - ${events}`); + } + + // Upcoming appointments + if (data.upcoming?.appointments?.length > 0) { + const appts = data.upcoming.appointments + .map((a: any) => `${a.type} - ${new Date(a.date).toLocaleDateString()} at ${a.location}`) + .join('\n - '); + parts.push(`\n\n## Upcoming Appointments + - ${appts}`); + } + + // Caregiver notes + if (data.caregiver_notes?.length > 0) { + const notes = data.caregiver_notes + .slice(0, 3) + .map((n: any) => `[${n.date}] ${n.author}: "${n.note}"`) + .join('\n - '); + parts.push(`\n\n## Recent Caregiver Notes + - ${notes}`); + } + + return parts.join(''); +} + +export async function fetchWebhookContext(webhookUrl: string): Promise { + // AbortController для timeout 10 сек по схеме + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT); + + try { + const response = await fetch(webhookUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + // Обработка HTTP ошибок по схеме + if (response.status === 401 || response.status === 403) { + throw new ApiError( + 'API_KEY_ERROR', + 'Сервис временно недоступен. Проблема с API ключом.', + response.status + ); + } + + if (response.status === 404) { + throw new ApiError( + 'PATIENT_NOT_FOUND', + 'Данные пациента не найдены.', + response.status + ); + } + + if (!response.ok) { + throw new ApiError( + 'NETWORK_ERROR', + `Ошибка сервера: ${response.status}`, + response.status + ); + } + + const data = await response.json(); + const fullContext = formatPatientContext(data); + + return { + systemPrompt: fullContext || DEFAULT_CONTEXT.systemPrompt, + voiceSettings: { + voice: 'shimmer', + speed: 1.0, + }, + userData: data, + }; + } catch (error) { + clearTimeout(timeoutId); + + // Timeout error + if (error instanceof Error && error.name === 'AbortError') { + throw new ApiError( + 'TIMEOUT_ERROR', + 'Превышено время ожидания. Проверьте подключение к интернету.' + ); + } + + // Уже обработанная ошибка + if (error instanceof ApiError) { + throw error; + } + + // Network error (no internet, etc.) + console.error('Failed to fetch webhook context:', error); + throw new ApiError( + 'NETWORK_ERROR', + 'Проверьте подключение к интернету.' + ); + } +} + +export function getDefaultContext(): WebhookContext { + return DEFAULT_CONTEXT; +} + +// Интерфейс для сохранения истории разговора +export interface ConversationMessage { + role: 'user' | 'assistant'; + content: string; + timestamp: string; +} + +export interface ConversationData { + session_id: string; + patient_id: string; + messages: ConversationMessage[]; + duration: number; + started_at: string; + ended_at: string; +} + +// Сохранение истории разговора на сервер по схеме +export async function saveConversation( + apiUrl: string, + data: ConversationData +): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT); + + try { + const response = await fetch(`${apiUrl}/api/conversations`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error(`Server error: ${response.status}`); + } + + console.log('Conversation saved successfully'); + return true; + } catch (error) { + clearTimeout(timeoutId); + console.error('Failed to save conversation:', error); + + // По схеме: если ошибка - сохранить локально для retry позже + await savePendingConversation(data); + return false; + } +} + +// Сохранение в AsyncStorage для retry позже +async function savePendingConversation(data: ConversationData): Promise { + try { + const existing = await AsyncStorage.getItem(CONVERSATIONS_STORAGE_KEY); + const pending: ConversationData[] = existing ? JSON.parse(existing) : []; + pending.push(data); + await AsyncStorage.setItem(CONVERSATIONS_STORAGE_KEY, JSON.stringify(pending)); + console.log('Conversation saved locally for later sync'); + } catch (error) { + console.error('Failed to save conversation locally:', error); + } +} + +// Синхронизация отложенных разговоров +export async function syncPendingConversations(apiUrl: string): Promise { + try { + const existing = await AsyncStorage.getItem(CONVERSATIONS_STORAGE_KEY); + if (!existing) return; + + const pending: ConversationData[] = JSON.parse(existing); + if (pending.length === 0) return; + + console.log(`Syncing ${pending.length} pending conversations...`); + const remaining: ConversationData[] = []; + + for (const conversation of pending) { + try { + const response = await fetch(`${apiUrl}/api/conversations`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(conversation), + }); + + if (!response.ok) { + remaining.push(conversation); + } + } catch { + remaining.push(conversation); + } + } + + await AsyncStorage.setItem(CONVERSATIONS_STORAGE_KEY, JSON.stringify(remaining)); + console.log(`Sync complete. ${remaining.length} conversations still pending.`); + } catch (error) { + console.error('Failed to sync pending conversations:', error); + } +} diff --git a/src/types/env.d.ts b/src/types/env.d.ts new file mode 100644 index 0000000..82882f7 --- /dev/null +++ b/src/types/env.d.ts @@ -0,0 +1,5 @@ +declare module '@env' { + export const OPENAI_API_KEY: string; + export const WEBHOOK_URL: string; + export const DEFAULT_VOICE: string; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..1f0b61a --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,23 @@ +export interface WebhookContext { + systemPrompt: string; + voiceSettings?: { + voice?: 'alloy' | 'echo' | 'shimmer' | 'ash' | 'ballad' | 'coral' | 'sage' | 'verse'; + speed?: number; + }; + userData?: Record; +} + +export interface VoiceAssistantState { + isConnected: boolean; + isListening: boolean; + isSpeaking: boolean; + isProcessing: boolean; + error: string | null; +} + +export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error'; + +export interface RealtimeEvent { + type: string; + [key: string]: any; +} diff --git a/src/utils/audioConverter.ts b/src/utils/audioConverter.ts new file mode 100644 index 0000000..1a67867 --- /dev/null +++ b/src/utils/audioConverter.ts @@ -0,0 +1,131 @@ +/** + * PCM16 to WAV Converter + * OpenAI Realtime API returns raw PCM16 audio (24kHz, mono) + * expo-av requires WAV format with proper headers + */ + +// WAV file constants for OpenAI Realtime audio +const SAMPLE_RATE = 24000; +const NUM_CHANNELS = 1; +const BITS_PER_SAMPLE = 16; +const BYTE_RATE = SAMPLE_RATE * NUM_CHANNELS * (BITS_PER_SAMPLE / 8); +const BLOCK_ALIGN = NUM_CHANNELS * (BITS_PER_SAMPLE / 8); + +/** + * Convert base64 PCM16 audio to WAV format with proper headers + * @param pcm16Base64 - Base64 encoded PCM16 audio data from OpenAI + * @returns Base64 encoded WAV audio ready for playback + */ +export function pcm16ToWav(pcm16Base64: string): string { + // Decode base64 to binary + const pcmData = base64ToUint8Array(pcm16Base64); + const dataLength = pcmData.length; + + // WAV header is 44 bytes + const wavHeader = createWavHeader(dataLength); + + // Combine header + PCM data + const wavData = new Uint8Array(44 + dataLength); + wavData.set(wavHeader, 0); + wavData.set(pcmData, 44); + + // Convert back to base64 + return uint8ArrayToBase64(wavData); +} + +/** + * Create WAV header for PCM16 24kHz mono audio + */ +function createWavHeader(dataLength: number): Uint8Array { + const header = new ArrayBuffer(44); + const view = new DataView(header); + + // RIFF chunk descriptor + writeString(view, 0, 'RIFF'); + view.setUint32(4, 36 + dataLength, true); // File size - 8 + writeString(view, 8, 'WAVE'); + + // fmt sub-chunk + writeString(view, 12, 'fmt '); + view.setUint32(16, 16, true); // Subchunk1Size (16 for PCM) + view.setUint16(20, 1, true); // AudioFormat (1 = PCM) + view.setUint16(22, NUM_CHANNELS, true); // NumChannels + view.setUint32(24, SAMPLE_RATE, true); // SampleRate + view.setUint32(28, BYTE_RATE, true); // ByteRate + view.setUint16(32, BLOCK_ALIGN, true); // BlockAlign + view.setUint16(34, BITS_PER_SAMPLE, true); // BitsPerSample + + // data sub-chunk + writeString(view, 36, 'data'); + view.setUint32(40, dataLength, true); // Subchunk2Size + + return new Uint8Array(header); +} + +/** + * Write string to DataView at offset + */ +function writeString(view: DataView, offset: number, str: string): void { + for (let i = 0; i < str.length; i++) { + view.setUint8(offset + i, str.charCodeAt(i)); + } +} + +/** + * Convert base64 string to Uint8Array + */ +function base64ToUint8Array(base64: string): Uint8Array { + // Handle both browser and React Native environments + const binaryString = atob(base64); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; +} + +/** + * Convert Uint8Array to base64 string + */ +function uint8ArrayToBase64(bytes: Uint8Array): string { + let binary = ''; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary); +} + +/** + * Create a data URI for WAV audio that can be played by expo-av + * @param pcm16Base64 - Base64 encoded PCM16 audio data from OpenAI + * @returns Data URI string ready for Audio.Sound.createAsync + */ +export function createWavDataUri(pcm16Base64: string): string { + const wavBase64 = pcm16ToWav(pcm16Base64); + return `data:audio/wav;base64,${wavBase64}`; +} + +/** + * Strip WAV header from recorded audio to get raw PCM16 data + * expo-av records in WAV format (44-byte header + PCM data) + * OpenAI Realtime API expects raw PCM16 without headers + * @param wavBase64 - Base64 encoded WAV file from recording + * @returns Base64 encoded raw PCM16 data + */ +export function wavToPcm16Base64(wavBase64: string): string { + // Decode base64 to binary + const wavData = base64ToUint8Array(wavBase64); + + // WAV header is 44 bytes for standard PCM format + // Skip first 44 bytes to get raw PCM data + if (wavData.length <= 44) { + console.log('[audioConverter] WAV data too short, returning as-is'); + return wavBase64; + } + + // Extract PCM data (skip 44-byte WAV header) + const pcmData = wavData.slice(44); + + // Convert back to base64 + return uint8ArrayToBase64(pcmData); +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..85fe10c --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1 @@ +export { pcm16ToWav, createWavDataUri } from './audioConverter'; diff --git a/src/utils/zaiMCPClient.js b/src/utils/zaiMCPClient.js new file mode 100644 index 0000000..a8cc32a --- /dev/null +++ b/src/utils/zaiMCPClient.js @@ -0,0 +1,87 @@ +import { spawn } from 'child_process'; +import { createInterface } from 'readline'; + +class ZAIMCPClient { + constructor() { + this.process = null; + this.requestId = 0; + } + + async start() { + this.process = spawn('zai-mcp-server', [], { + stdio: ['pipe', 'pipe', 'pipe'] + }); + + this.rl = createInterface({ + input: this.process.stdout, + output: this.process.stdin + }); + + return new Promise((resolve, reject) => { + this.process.on('spawn', () => { + console.log('Z.AI MCP Server started'); + resolve(); + }); + + this.process.on('error', reject); + }); + } + + async callTool(toolName, args) { + return new Promise((resolve, reject) => { + const request = { + jsonrpc: "2.0", + id: ++this.requestId, + method: "tools/call", + params: { + name: toolName, + arguments: args + } + }; + + this.process.stdin.write(JSON.stringify(request) + '\n'); + + let output = ''; + const onData = (data) => { + output += data; + try { + const response = JSON.parse(output); + if (response.id === this.requestId) { + this.process.stdout.removeListener('data', onData); + if (response.error) { + reject(new Error(response.error.message)); + } else { + resolve(response.result); + } + } + } catch (e) { + // Still collecting data + } + }; + + this.process.stdout.on('data', onData); + }); + } + + async analyzeImage(imagePath, prompt) { + return this.callTool('analyze_image', { + image_source: imagePath, + prompt: prompt + }); + } + + async analyzeVideo(videoPath, prompt) { + return this.callTool('analyze_video', { + video_source: videoPath, + prompt: prompt + }); + } + + stop() { + if (this.process) { + this.process.kill(); + } + } +} + +export default ZAIMCPClient; \ No newline at end of file diff --git a/tests/api-check.spec.js b/tests/api-check.spec.js new file mode 100644 index 0000000..45372d0 --- /dev/null +++ b/tests/api-check.spec.js @@ -0,0 +1,164 @@ +const { test, expect } = require('@playwright/test'); + +// API credentials from client +const API_BASE = 'https://react.eluxnetworks.net'; +const DASHBOARD_URL = `${API_BASE}/dashboard`; +const LOGIN_CREDENTIALS = { + username: 'anandk', + password: 'anandk_8' +}; + +test.describe('WellNuo API Check', () => { + + test('should load main page', async ({ page }) => { + const response = await page.goto(API_BASE); + expect(response.status()).toBe(200); + + // Check if it's the WellNuo app + await expect(page).toHaveTitle(/WellNuo/); + + // Take screenshot + await page.screenshot({ path: 'tests/screenshots/main-page.png', fullPage: true }); + console.log('Main page loaded successfully'); + }); + + test('should navigate to dashboard and login', async ({ page }) => { + // Go to dashboard + await page.goto(DASHBOARD_URL); + + // Wait for page to load + await page.waitForLoadState('networkidle'); + + // Take screenshot of login page + await page.screenshot({ path: 'tests/screenshots/dashboard-initial.png', fullPage: true }); + + // Look for login form elements + const usernameField = page.locator('input[name="username"], input[name="user_name"], input[type="text"]').first(); + const passwordField = page.locator('input[name="password"], input[type="password"]').first(); + const submitButton = page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Sign in")').first(); + + // Check if login form exists + const hasLoginForm = await usernameField.isVisible().catch(() => false); + + if (hasLoginForm) { + console.log('Login form found, attempting to login...'); + + // Fill credentials + await usernameField.fill(LOGIN_CREDENTIALS.username); + await passwordField.fill(LOGIN_CREDENTIALS.password); + + // Take screenshot before submit + await page.screenshot({ path: 'tests/screenshots/login-filled.png', fullPage: true }); + + // Click login + await submitButton.click(); + + // Wait for navigation or response + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + + // Take screenshot after login + await page.screenshot({ path: 'tests/screenshots/after-login.png', fullPage: true }); + + // Check URL to verify login success + const currentUrl = page.url(); + console.log('Current URL after login:', currentUrl); + + // Check page content + const pageContent = await page.content(); + console.log('Page contains "dashboard":', pageContent.toLowerCase().includes('dashboard')); + console.log('Page contains "error":', pageContent.toLowerCase().includes('error')); + console.log('Page contains "invalid":', pageContent.toLowerCase().includes('invalid')); + + } else { + console.log('No login form found, checking page state...'); + const pageContent = await page.content(); + console.log('Page HTML (first 500 chars):', pageContent.substring(0, 500)); + } + }); + + test('should check API endpoints directly', async ({ request }) => { + // Try common API endpoints + const endpoints = [ + '/api/auth/login', + '/api/login', + '/auth/login', + '/api/token', + '/api/v1/auth/login' + ]; + + for (const endpoint of endpoints) { + const url = `${API_BASE}${endpoint}`; + console.log(`Checking endpoint: ${url}`); + + try { + // Try POST with credentials + const response = await request.post(url, { + data: { + user_name: LOGIN_CREDENTIALS.username, + password: LOGIN_CREDENTIALS.password + }, + headers: { + 'Content-Type': 'application/json' + } + }); + + console.log(` Status: ${response.status()}`); + + if (response.status() === 200) { + const body = await response.json().catch(() => ({})); + console.log(` Response:`, JSON.stringify(body, null, 2)); + + if (body.token || body.access_token) { + console.log('TOKEN FOUND!'); + } + } + } catch (e) { + console.log(` Error: ${e.message}`); + } + } + }); + + test('should try login with various body formats', async ({ request }) => { + const loginEndpoint = `${API_BASE}/api/auth/login`; + + // Format 1: user_name / password + console.log('Trying format 1: user_name/password'); + let response = await request.post(loginEndpoint, { + data: { + user_name: LOGIN_CREDENTIALS.username, + password: LOGIN_CREDENTIALS.password + } + }); + console.log(`Status: ${response.status()}`); + if (response.status() === 200) { + console.log('Response:', await response.text()); + } + + // Format 2: username / password + console.log('Trying format 2: username/password'); + response = await request.post(loginEndpoint, { + data: { + username: LOGIN_CREDENTIALS.username, + password: LOGIN_CREDENTIALS.password + } + }); + console.log(`Status: ${response.status()}`); + if (response.status() === 200) { + console.log('Response:', await response.text()); + } + + // Format 3: email-style + console.log('Trying format 3: email/password'); + response = await request.post(loginEndpoint, { + data: { + email: LOGIN_CREDENTIALS.username, + password: LOGIN_CREDENTIALS.password + } + }); + console.log(`Status: ${response.status()}`); + if (response.status() === 200) { + console.log('Response:', await response.text()); + } + }); +}); diff --git a/tests/api-discovery.spec.js b/tests/api-discovery.spec.js new file mode 100644 index 0000000..dbbecc3 --- /dev/null +++ b/tests/api-discovery.spec.js @@ -0,0 +1,185 @@ +const { test, expect } = require('@playwright/test'); + +const API_BASE = 'https://eluxnetworks.net/function/well-api/api'; +const TOKEN = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFuYW5kayIsImV4cCI6MTc2NTQ5MzI5N30.Jon7IJtBscErkt4-8qjas45Irwg_MQTRyS1ASrPGb1U'; +const USER_NAME = 'anandk'; +const PATIENT_ID = 25; // Ferdinand Zmrzli + +test.describe('API Discovery - Find all available functions', () => { + + // Common API functions to try + const functionsToTry = [ + // Patient related + 'patient_details', + 'patient_info', + 'get_patient', + 'patient_data', + 'patient_history', + 'patient_context', + + // Health data + 'health_data', + 'health_metrics', + 'vitals', + 'wellness', + 'wellness_data', + + // Activity + 'activity', + 'activity_log', + 'activity_history', + 'location_history', + + // Chat/Messages + 'chat', + 'messages', + 'send_message', + 'get_messages', + 'conversation', + 'conversations', + + // Alerts + 'alerts', + 'notifications', + 'alert_history', + + // Settings + 'settings', + 'user_settings', + 'preferences', + + // Reports + 'report', + 'daily_report', + 'weekly_report', + + // Other + 'status', + 'system_status', + 'api_list', + 'functions', + 'help' + ]; + + test('discover available API functions', async ({ request }) => { + console.log('\\n=== TESTING API FUNCTIONS ===\\n'); + + const workingFunctions = []; + + for (const func of functionsToTry) { + const params = new URLSearchParams({ + function: func, + user_name: USER_NAME, + token: TOKEN, + patient_id: PATIENT_ID.toString(), + user_id: PATIENT_ID.toString(), + date: '2025-12-10', + nonce: `test-${Date.now()}` + }); + + try { + const response = await request.post(API_BASE, { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + data: params.toString() + }); + + const status = response.status(); + const text = await response.text(); + + // Check if function returned valid data + if (status === 200 && text && !text.includes('error') && !text.includes('Error')) { + console.log(`[OK] ${func}: ${text.substring(0, 200)}`); + workingFunctions.push({ function: func, response: text.substring(0, 500) }); + } else if (status === 200) { + console.log(`[??] ${func}: ${text.substring(0, 100)}`); + } else { + console.log(`[${status}] ${func}`); + } + } catch (e) { + console.log(`[ERR] ${func}: ${e.message}`); + } + } + + console.log('\\n=== WORKING FUNCTIONS ==='); + console.log(JSON.stringify(workingFunctions, null, 2)); + }); + + test('explore web interface for more endpoints', async ({ page }) => { + const apiCalls = []; + + // Intercept all API calls + page.on('request', req => { + if (req.url().includes('eluxnetworks.net')) { + apiCalls.push({ + url: req.url(), + method: req.method(), + postData: req.postData() + }); + } + }); + + // Login and navigate + await page.goto('https://react.eluxnetworks.net/dashboard'); + await page.waitForLoadState('networkidle'); + + // Fill login + await page.locator('input').first().fill('anandk'); + await page.locator('input[type="password"]').fill('anandk_8'); + await page.locator('button:has-text("Log In")').click(); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + + // Click on first patient card to see details + const patientCard = page.locator('.bg-white').first(); + if (await patientCard.isVisible()) { + await patientCard.click(); + await page.waitForTimeout(2000); + await page.screenshot({ path: 'tests/screenshots/patient-details.png', fullPage: true }); + } + + // Try to find and click on different sections + const sections = ['Health', 'Activity', 'Sleep', 'Alerts', 'Settings', 'Chat', 'Messages']; + for (const section of sections) { + const link = page.locator(`text=${section}`).first(); + if (await link.isVisible().catch(() => false)) { + console.log(`Found section: ${section}`); + await link.click().catch(() => {}); + await page.waitForTimeout(1000); + } + } + + console.log('\\n=== ALL API CALLS CAPTURED ==='); + apiCalls.forEach((call, i) => { + console.log(`\\n[${i + 1}] ${call.method} ${call.url}`); + if (call.postData) { + console.log(' Data:', call.postData); + } + }); + }); + + test('test patient context API for chat', async ({ request }) => { + // Test the webhook URL from .env + console.log('\\n=== Testing Patient Context API ===\\n'); + + const contextUrl = 'https://wellnuo.smartlaunchhub.com/api/patient/context'; + + try { + const response = await request.get(contextUrl); + console.log(`Status: ${response.status()}`); + const body = await response.text(); + console.log(`Response: ${body.substring(0, 1000)}`); + } catch (e) { + console.log(`Error: ${e.message}`); + } + + // Try with patient ID + try { + const response = await request.get(`${contextUrl}?patient_id=${PATIENT_ID}`); + console.log(`\\nWith patient_id - Status: ${response.status()}`); + const body = await response.text(); + console.log(`Response: ${body.substring(0, 1000)}`); + } catch (e) { + console.log(`Error: ${e.message}`); + } + }); +}); diff --git a/tests/check-repo.spec.js b/tests/check-repo.spec.js new file mode 100644 index 0000000..22ec709 --- /dev/null +++ b/tests/check-repo.spec.js @@ -0,0 +1,18 @@ +const { test } = require('@playwright/test'); + +test('check repo content', async ({ page }) => { + // Login first + await page.goto('https://gitea.wellnua.com/user/login'); + await page.locator('input#user_name').fill('sergei_t'); + await page.locator('input#password').fill('WellNuo2025!Secure'); + await page.locator('button:has-text("Sign In")').click(); + await page.waitForTimeout(2000); + + // Go to repo + await page.goto('https://gitea.wellnua.com/robert/MobileApp_react_native'); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/repo-content.png', fullPage: true }); + + console.log('Page title:', await page.title()); + console.log('URL:', page.url()); +}); diff --git a/tests/gitea-complete.spec.js b/tests/gitea-complete.spec.js new file mode 100644 index 0000000..234388f --- /dev/null +++ b/tests/gitea-complete.spec.js @@ -0,0 +1,76 @@ +const { test, expect } = require('@playwright/test'); + +const GITEA_URL = 'https://gitea.wellnua.com'; +const EMAIL = 'serter2069@gmail.com'; +const USERNAME = 'serter2069'; +const PASSWORD = 'WellNuo2025!Secure'; + +test('complete Gitea registration', async ({ page }) => { + console.log('=== GITEA REGISTRATION ===\n'); + + // Go to register page directly + await page.goto(`${GITEA_URL}/user/sign_up`); + await page.waitForLoadState('networkidle'); + + console.log('1. On registration page'); + + // Fill form + await page.locator('input#user_name').fill(USERNAME); + await page.locator('input#email').fill(EMAIL); + await page.locator('input#password').fill(PASSWORD); + await page.locator('input#retype').fill(PASSWORD); + + console.log('2. Form filled'); + await page.screenshot({ path: 'tests/screenshots/gitea-form.png' }); + + // Click Register Account button + await page.locator('button.ui.primary.button:has-text("Register Account")').click(); + console.log('3. Clicked Register button'); + + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + + await page.screenshot({ path: 'tests/screenshots/gitea-after-register.png', fullPage: true }); + + const currentUrl = page.url(); + console.log('4. Current URL:', currentUrl); + + // Check for errors or success + const pageText = await page.textContent('body'); + if (pageText.includes('error') || pageText.includes('Error')) { + console.log(' Error detected on page'); + } + if (pageText.includes('confirm') || pageText.includes('email')) { + console.log(' Email confirmation may be required'); + } + if (pageText.includes('success') || pageText.includes('Success')) { + console.log(' Registration successful!'); + } + + // Check if already registered - try login + if (currentUrl.includes('sign_up') || pageText.includes('already')) { + console.log('5. User may already exist, trying login...'); + await page.goto(`${GITEA_URL}/user/login`); + await page.waitForLoadState('networkidle'); + + await page.locator('input#user_name').fill(USERNAME); + await page.locator('input#password').fill(PASSWORD); + await page.locator('button.ui.primary.button').click(); + + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + await page.screenshot({ path: 'tests/screenshots/gitea-after-login.png', fullPage: true }); + + console.log(' After login URL:', page.url()); + } + + // Try accessing repo + console.log('6. Accessing repository...'); + await page.goto(`${GITEA_URL}/robert/MobileApp_react_native`); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/gitea-repo-final.png', fullPage: true }); + + console.log(' Repo page title:', await page.title()); + + console.log('\n=== DONE ==='); +}); diff --git a/tests/gitea-final.spec.js b/tests/gitea-final.spec.js new file mode 100644 index 0000000..015e11b --- /dev/null +++ b/tests/gitea-final.spec.js @@ -0,0 +1,75 @@ +const { test, expect } = require('@playwright/test'); + +const RESET_URL = 'https://gitea.wellnua.com/user/recover_account?code=202512111241000180718f2083d36c90ef12f385dc56ccea121af2b2437365726765695f74'; +const NEW_PASSWORD = 'WellNuo2025!Secure'; +const USERNAME = 'sergei_t'; + +test('set password and access repo', async ({ page }) => { + console.log('=== GITEA FINAL SETUP ===\n'); + + // Step 1: Set new password + await page.goto(RESET_URL); + await page.waitForLoadState('networkidle'); + + console.log('1. On recovery page'); + + // Fill password + await page.locator('input[name="password"]').fill(NEW_PASSWORD); + console.log('2. Password filled'); + + // Click Recover Account button + await page.locator('button:has-text("Recover Account")').click(); + console.log('3. Clicked Recover Account'); + + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + await page.screenshot({ path: 'tests/screenshots/gitea-final-1.png', fullPage: true }); + + console.log('4. After recovery URL:', page.url()); + + // Step 2: Login + console.log('\n5. Logging in...'); + await page.goto('https://gitea.wellnua.com/user/login'); + await page.waitForLoadState('networkidle'); + + await page.locator('input#user_name').fill(USERNAME); + await page.locator('input#password').fill(NEW_PASSWORD); + await page.locator('button:has-text("Sign In")').click(); + + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + await page.screenshot({ path: 'tests/screenshots/gitea-final-2.png', fullPage: true }); + + console.log('6. After login URL:', page.url()); + + // Step 3: Access repo + console.log('\n7. Accessing repository...'); + await page.goto('https://gitea.wellnua.com/robert/MobileApp_react_native'); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/gitea-final-3.png', fullPage: true }); + + const title = await page.title(); + console.log('8. Repo title:', title); + + // Check if repo is accessible + const is404 = title.includes('Not Found') || title.includes('404'); + if (!is404) { + console.log('\nSUCCESS! Repository is accessible!'); + + // Get clone URL + const cloneUrl = await page.locator('input[value*="git"]').first().inputValue().catch(() => ''); + if (cloneUrl) { + console.log('Clone URL:', cloneUrl); + } + } else { + console.log('\nRepository still not accessible'); + } + + console.log('\n=== CREDENTIALS ==='); + console.log('Gitea URL: https://gitea.wellnua.com'); + console.log('Username:', USERNAME); + console.log('Password:', NEW_PASSWORD); + console.log('Email: serter2069@gmail.com'); + + console.log('\n=== DONE ==='); +}); diff --git a/tests/gitea-register.spec.js b/tests/gitea-register.spec.js new file mode 100644 index 0000000..5eb6e5c --- /dev/null +++ b/tests/gitea-register.spec.js @@ -0,0 +1,107 @@ +const { test, expect } = require('@playwright/test'); + +const GITEA_URL = 'https://gitea.wellnua.com'; +const EMAIL = 'serter2069@gmail.com'; +const USERNAME = 'serter2069'; +const PASSWORD = 'WellNuo2025!Secure'; + +test('register on Gitea and accept invitation', async ({ page }) => { + console.log('=== GITEA REGISTRATION ===\n'); + + // Step 1: Go to Gitea + console.log('1. Opening Gitea...'); + await page.goto(GITEA_URL); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/gitea-01-home.png', fullPage: true }); + + // Check current page + const pageContent = await page.content(); + console.log(' Page title:', await page.title()); + + // Step 2: Find register/login link + const registerLink = page.locator('a:has-text("Register"), a:has-text("Sign Up"), a[href*="register"]').first(); + const loginLink = page.locator('a:has-text("Sign In"), a:has-text("Login"), a[href*="login"]').first(); + + if (await registerLink.isVisible().catch(() => false)) { + console.log('2. Found Register link, clicking...'); + await registerLink.click(); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/gitea-02-register.png', fullPage: true }); + + // Fill registration form + console.log('3. Filling registration form...'); + + const usernameField = page.locator('input[name="user_name"], input[id="user_name"], input[placeholder*="Username"]').first(); + const emailField = page.locator('input[name="email"], input[type="email"]').first(); + const passwordField = page.locator('input[name="password"], input[type="password"]').first(); + const confirmField = page.locator('input[name="retype"], input[name="confirm_password"], input[type="password"]').nth(1); + + if (await usernameField.isVisible()) { + await usernameField.fill(USERNAME); + console.log(' - Username filled'); + } + if (await emailField.isVisible()) { + await emailField.fill(EMAIL); + console.log(' - Email filled'); + } + if (await passwordField.isVisible()) { + await passwordField.fill(PASSWORD); + console.log(' - Password filled'); + } + if (await confirmField.isVisible()) { + await confirmField.fill(PASSWORD); + console.log(' - Confirm password filled'); + } + + await page.screenshot({ path: 'tests/screenshots/gitea-03-form-filled.png', fullPage: true }); + + // Submit registration + const submitBtn = page.locator('button[type="submit"], input[type="submit"]').first(); + if (await submitBtn.isVisible()) { + console.log('4. Submitting registration...'); + await submitBtn.click(); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + await page.screenshot({ path: 'tests/screenshots/gitea-04-after-submit.png', fullPage: true }); + + console.log(' Current URL:', page.url()); + } + + } else if (await loginLink.isVisible().catch(() => false)) { + console.log('2. Found Login link (maybe already registered), trying to login...'); + await loginLink.click(); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/gitea-02-login.png', fullPage: true }); + + // Try to login + const usernameField = page.locator('input[name="user_name"], input[id="user_name"]').first(); + const passwordField = page.locator('input[name="password"], input[type="password"]').first(); + + if (await usernameField.isVisible()) { + await usernameField.fill(USERNAME); + await passwordField.fill(PASSWORD); + + const submitBtn = page.locator('button[type="submit"]').first(); + await submitBtn.click(); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + await page.screenshot({ path: 'tests/screenshots/gitea-03-after-login.png', fullPage: true }); + + console.log(' Current URL:', page.url()); + } + } else { + console.log('2. Cannot find register/login links'); + console.log(' Page HTML:', pageContent.substring(0, 1000)); + } + + // Step 5: Try to access the repository + console.log('5. Trying to access repository...'); + await page.goto(`${GITEA_URL}/robert/MobileApp_react_native`); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/gitea-05-repo.png', fullPage: true }); + + console.log(' Repo URL:', page.url()); + console.log(' Page title:', await page.title()); + + console.log('\n=== DONE ==='); +}); diff --git a/tests/gitea-reset-password.spec.js b/tests/gitea-reset-password.spec.js new file mode 100644 index 0000000..816473b --- /dev/null +++ b/tests/gitea-reset-password.spec.js @@ -0,0 +1,44 @@ +const { test, expect } = require('@playwright/test'); + +const GITEA_URL = 'https://gitea.wellnua.com'; +const EMAIL = 'serter2069@gmail.com'; + +test('reset Gitea password', async ({ page }) => { + console.log('=== GITEA PASSWORD RESET ===\n'); + + // Go to forgot password page + await page.goto(`${GITEA_URL}/user/forgot_password`); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/gitea-forgot-1.png', fullPage: true }); + + console.log('1. On forgot password page'); + + // Fill email + const emailField = page.locator('input[name="email"], input#email, input[type="email"]').first(); + if (await emailField.isVisible()) { + await emailField.fill(EMAIL); + console.log('2. Email filled:', EMAIL); + + await page.screenshot({ path: 'tests/screenshots/gitea-forgot-2.png', fullPage: true }); + + // Click submit + const submitBtn = page.locator('button[type="submit"], button:has-text("Send"), button:has-text("Reset")').first(); + if (await submitBtn.isVisible()) { + await submitBtn.click(); + console.log('3. Clicked submit button'); + + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + await page.screenshot({ path: 'tests/screenshots/gitea-forgot-3.png', fullPage: true }); + + console.log('4. Current URL:', page.url()); + console.log('5. Check email for reset link!'); + } + } else { + console.log('Email field not found'); + const content = await page.content(); + console.log('Page content:', content.substring(0, 500)); + } + + console.log('\n=== DONE ==='); +}); diff --git a/tests/gitea-set-password.spec.js b/tests/gitea-set-password.spec.js new file mode 100644 index 0000000..79320f5 --- /dev/null +++ b/tests/gitea-set-password.spec.js @@ -0,0 +1,78 @@ +const { test, expect } = require('@playwright/test'); + +const RESET_URL = 'https://gitea.wellnua.com/user/recover_account?code=202512111241000180718f2083d36c90ef12f385dc56ccea121af2b2437365726765695f74'; +const NEW_PASSWORD = 'WellNuo2025!Secure'; +const USERNAME = 'sergei_t'; + +test('set new Gitea password and login', async ({ page }) => { + console.log('=== SET NEW PASSWORD ===\n'); + + // Go to reset link + await page.goto(RESET_URL); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/gitea-reset-1.png', fullPage: true }); + + console.log('1. Opened reset link'); + console.log(' URL:', page.url()); + + // Look for password fields + const passwordField = page.locator('input[name="password"], input#password, input[type="password"]').first(); + const confirmField = page.locator('input[name="retype"], input[name="confirm"], input[type="password"]').nth(1); + + if (await passwordField.isVisible()) { + await passwordField.fill(NEW_PASSWORD); + console.log('2. Password filled'); + + if (await confirmField.isVisible()) { + await confirmField.fill(NEW_PASSWORD); + console.log('3. Confirm password filled'); + } + + await page.screenshot({ path: 'tests/screenshots/gitea-reset-2.png', fullPage: true }); + + // Submit + const submitBtn = page.locator('button[type="submit"]').first(); + await submitBtn.click(); + console.log('4. Submitted new password'); + + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + await page.screenshot({ path: 'tests/screenshots/gitea-reset-3.png', fullPage: true }); + + console.log('5. Current URL:', page.url()); + } else { + console.log('Password field not found on page'); + const text = await page.textContent('body'); + console.log('Page text:', text.substring(0, 500)); + } + + // Try to login with new password + console.log('\n6. Trying to login...'); + await page.goto('https://gitea.wellnua.com/user/login'); + await page.waitForLoadState('networkidle'); + + await page.locator('input#user_name').fill(USERNAME); + await page.locator('input#password').fill(NEW_PASSWORD); + await page.locator('button:has-text("Sign In")').click(); + + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(2000); + await page.screenshot({ path: 'tests/screenshots/gitea-logged-in.png', fullPage: true }); + + console.log('7. After login URL:', page.url()); + + // Access repo + console.log('\n8. Accessing repository...'); + await page.goto('https://gitea.wellnua.com/robert/MobileApp_react_native'); + await page.waitForLoadState('networkidle'); + await page.screenshot({ path: 'tests/screenshots/gitea-repo-access.png', fullPage: true }); + + const title = await page.title(); + console.log('9. Repo page title:', title); + + if (!title.includes('Not Found')) { + console.log('SUCCESS! Repository is accessible!'); + } + + console.log('\n=== DONE ==='); +}); diff --git a/tests/network-capture.spec.js b/tests/network-capture.spec.js new file mode 100644 index 0000000..000aeb3 --- /dev/null +++ b/tests/network-capture.spec.js @@ -0,0 +1,104 @@ +const { test, expect } = require('@playwright/test'); + +const API_BASE = 'https://react.eluxnetworks.net'; +const LOGIN_CREDENTIALS = { + username: 'anandk', + password: 'anandk_8' +}; + +test('capture network requests during login', async ({ page }) => { + // Collect all network requests + const requests = []; + const responses = []; + + page.on('request', request => { + if (request.url().includes('eluxnetworks') || request.url().includes('api')) { + requests.push({ + url: request.url(), + method: request.method(), + postData: request.postData(), + headers: request.headers() + }); + } + }); + + page.on('response', async response => { + if (response.url().includes('eluxnetworks') || response.url().includes('api')) { + let body = ''; + try { + body = await response.text(); + } catch (e) { + body = 'Could not read body'; + } + responses.push({ + url: response.url(), + status: response.status(), + body: body.substring(0, 1000) + }); + } + }); + + // Go to dashboard (will redirect to login) + await page.goto(`${API_BASE}/dashboard`); + await page.waitForLoadState('networkidle'); + + // Fill login form + const usernameField = page.locator('input').first(); + const passwordField = page.locator('input[type="password"]'); + const submitButton = page.locator('button:has-text("Log In")'); + + await usernameField.fill(LOGIN_CREDENTIALS.username); + await passwordField.fill(LOGIN_CREDENTIALS.password); + + console.log('\\n=== CLICKING LOGIN BUTTON ===\\n'); + + // Click login and wait for network + await submitButton.click(); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(3000); + + console.log('\\n=== ALL REQUESTS ==='); + requests.forEach((req, i) => { + console.log(`\\n[${i + 1}] ${req.method} ${req.url}`); + if (req.postData) { + console.log(' POST Data:', req.postData); + } + }); + + console.log('\\n=== ALL RESPONSES ==='); + responses.forEach((res, i) => { + console.log(`\\n[${i + 1}] ${res.status} ${res.url}`); + if (res.body && res.body !== 'Could not read body' && !res.body.includes(' { + const items = {}; + for (let i = 0; i < window.localStorage.length; i++) { + const key = window.localStorage.key(i); + items[key] = window.localStorage.getItem(key); + } + return items; + }); + + console.log('\\n=== LOCAL STORAGE ==='); + console.log(JSON.stringify(localStorage, null, 2)); + + // Check sessionStorage + const sessionStorage = await page.evaluate(() => { + const items = {}; + for (let i = 0; i < window.sessionStorage.length; i++) { + const key = window.sessionStorage.key(i); + items[key] = window.sessionStorage.getItem(key); + } + return items; + }); + + console.log('\\n=== SESSION STORAGE ==='); + console.log(JSON.stringify(sessionStorage, null, 2)); + + // Take final screenshot + await page.screenshot({ path: 'tests/screenshots/network-test-final.png', fullPage: true }); +}); diff --git a/tests/screenshots/.last-run.json b/tests/screenshots/.last-run.json new file mode 100644 index 0000000..363812c --- /dev/null +++ b/tests/screenshots/.last-run.json @@ -0,0 +1,6 @@ +{ + "status": "failed", + "failedTests": [ + "703d711b83804a97c7aa-3618f1d342369de44ba0" + ] +} \ No newline at end of file diff --git a/tests/screenshots/check-repo-check-repo-content-chromium/error-context.md b/tests/screenshots/check-repo-check-repo-content-chromium/error-context.md new file mode 100644 index 0000000..1d774a1 --- /dev/null +++ b/tests/screenshots/check-repo-check-repo-content-chromium/error-context.md @@ -0,0 +1,126 @@ +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - generic [ref=e2]: + - navigation "Navigation Bar" [ref=e3]: + - generic [ref=e4]: + - link "Dashboard" [ref=e5] [cursor=pointer]: + - /url: / + - img [ref=e6] + - link "Issues" [ref=e7] [cursor=pointer]: + - /url: /issues + - link "Pull Requests" [ref=e8] [cursor=pointer]: + - /url: /pulls + - link "Milestones" [ref=e9] [cursor=pointer]: + - /url: /milestones + - link "Explore" [ref=e10] [cursor=pointer]: + - /url: /explore/repos + - generic [ref=e11]: + - link "Notifications" [ref=e12] [cursor=pointer]: + - /url: /notifications + - img [ref=e14] + - menu "Create…" [ref=e16] [cursor=pointer]: + - generic [ref=e17]: + - img [ref=e18] + - img [ref=e21] + - menu "Profile and Settings…" [ref=e23] [cursor=pointer]: + - generic [ref=e24]: + - img "sergei_t" [ref=e25] + - img [ref=e27] + - 'main "robert/MobileApp_react_native: Scaled down mobile app written in React Native" [ref=e29]': + - generic [ref=e30]: + - generic [ref=e32]: + - generic [ref=e33]: + - img [ref=e35] + - generic [ref=e38]: + - link "robert" [ref=e39] [cursor=pointer]: + - /url: /robert + - text: / + - link "MobileApp_react_native" [ref=e40] [cursor=pointer]: + - /url: /robert/MobileApp_react_native + - generic [ref=e42]: Private + - generic [ref=e43]: + - link "RSS Feed" [ref=e44] [cursor=pointer]: + - /url: /robert/MobileApp_react_native.rss + - img [ref=e45] + - generic [ref=e48] [cursor=pointer]: + - button "Watch" [ref=e49]: + - img [ref=e50] + - generic [ref=e52]: Watch + - link "1" [ref=e53]: + - /url: /robert/MobileApp_react_native/watchers + - generic [ref=e55] [cursor=pointer]: + - button "Star" [ref=e56]: + - img [ref=e57] + - generic [ref=e59]: Star + - link "0" [ref=e60]: + - /url: /robert/MobileApp_react_native/stars + - navigation [ref=e62]: + - generic [ref=e63]: + - link "Code" [ref=e64] [cursor=pointer]: + - /url: /robert/MobileApp_react_native + - img [ref=e65] + - generic [ref=e67]: Code + - link "Issues" [ref=e68] [cursor=pointer]: + - /url: /robert/MobileApp_react_native/issues + - img [ref=e69] + - generic [ref=e72]: Issues + - link "Packages" [ref=e73] [cursor=pointer]: + - /url: /robert/MobileApp_react_native/packages + - img [ref=e74] + - generic [ref=e76]: Packages + - link "Projects" [ref=e77] [cursor=pointer]: + - /url: /robert/MobileApp_react_native/projects + - img [ref=e78] + - generic [ref=e80]: Projects + - link "Wiki" [ref=e81] [cursor=pointer]: + - /url: /robert/MobileApp_react_native/wiki + - img [ref=e82] + - generic [ref=e84]: Wiki + - generic [ref=e88]: + - heading "Quick Guide" [level=4] [ref=e89] + - generic [ref=e90]: + - generic [ref=e91]: + - heading "Clone this repository Need help cloning? Visit Help." [level=3] [ref=e92]: + - text: Clone this repository + - generic [ref=e93]: + - text: Need help cloning? Visit + - link "Help" [ref=e94] [cursor=pointer]: + - /url: http://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository + - text: . + - generic [ref=e95]: + - link "New File" [ref=e96] [cursor=pointer]: + - /url: /robert/MobileApp_react_native/_new/main/ + - link "Upload File" [ref=e97] [cursor=pointer]: + - /url: /robert/MobileApp_react_native/_upload/main/ + - generic [ref=e98]: + - button "HTTPS" [ref=e99] [cursor=pointer] + - button "SSH" [ref=e100] [cursor=pointer] + - textbox [ref=e101]: https://gitea.wellnua.com/robert/MobileApp_react_native.git + - button "Copy URL" [ref=e102] [cursor=pointer]: + - img [ref=e103] + - generic [ref=e107]: + - heading "Creating a new repository on the command line" [level=3] [ref=e108] + - code [ref=e111]: touch README.md git init git checkout -b main git add README.md git commit -m "first commit" git remote add origin https://gitea.wellnua.com/robert/MobileApp_react_native.git git push -u origin main + - generic [ref=e113]: + - heading "Pushing an existing repository from the command line" [level=3] [ref=e114] + - code [ref=e117]: git remote add origin https://gitea.wellnua.com/robert/MobileApp_react_native.git git push -u origin main + - group "Footer" [ref=e118]: + - contentinfo "About Software" [ref=e119]: + - link "Powered by Gitea" [ref=e120] [cursor=pointer]: + - /url: https://about.gitea.com + - text: "Version: 1.23.6 Page:" + - strong [ref=e121]: 87ms + - text: "Template:" + - strong [ref=e122]: 5ms + - group "Links" [ref=e123]: + - menu [ref=e124] [cursor=pointer]: + - generic [ref=e125]: + - img [ref=e126] + - text: English + - link "Licenses" [ref=e128] [cursor=pointer]: + - /url: /assets/licenses.txt + - link "API" [ref=e129] [cursor=pointer]: + - /url: /api/swagger +``` \ No newline at end of file diff --git a/tests/screenshots/check-repo-check-repo-content-chromium/test-failed-1.png b/tests/screenshots/check-repo-check-repo-content-chromium/test-failed-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7fffcbceb9015b6e14918f560f4a8a5bd546be73 GIT binary patch literal 85698 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 d4!2}D$3|1Nq+ { + https.get(CONTEXT_URL, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { + resolve(JSON.parse(data)); + } catch (e) { + reject(e); + } + }); + }).on('error', reject); + }); +} + +async function askAboutPatient(context, question) { + // Build system prompt from context + const systemPrompt = `You are Julia, a caring AI health assistant for WellNuo family care system. + +Current patient information: +- Name: ${context.patient.name} +- Age: ${context.patient.age} +- Relationship: ${context.patient.relationship} +- Location: ${context.current_status.location} +- Activity: ${context.current_status.estimated_activity} + +Recent sleep data: +- Last night: ${context.sleep_analysis?.last_night?.total_hours || 'N/A'} hours +- Quality: ${context.sleep_analysis?.last_night?.quality_score || 'N/A'}/100 + +Today's activity: +- Active minutes: ${context.activity_patterns?.today?.total_active_minutes || 'N/A'} +- Rooms visited: ${context.activity_patterns?.today?.rooms_visited?.join(', ') || 'N/A'} + +Environment (${context.current_status.location}): +- Temperature: ${context.environment?.living_room?.temperature_f || 'N/A'}°F +- Humidity: ${context.environment?.living_room?.humidity_percent || 'N/A'}% +- Air quality: ${context.environment?.living_room?.air_quality_status || 'N/A'} + +Be warm, concise, and reassuring. Answer questions about the patient's wellbeing.`; + + const requestData = JSON.stringify({ + model: 'gpt-4o-mini', + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: question } + ], + max_tokens: 300 + }); + + return new Promise((resolve, reject) => { + const req = https.request({ + hostname: 'api.openai.com', + path: '/v1/chat/completions', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${OPENAI_API_KEY}`, + 'Content-Length': Buffer.byteLength(requestData) + } + }, (res) => { + let data = ''; + res.on('data', chunk => data += chunk); + res.on('end', () => { + try { + const response = JSON.parse(data); + if (response.error) { + reject(new Error(response.error.message)); + } else { + resolve(response.choices[0].message.content); + } + } catch (e) { + reject(e); + } + }); + }); + + req.on('error', reject); + req.write(requestData); + req.end(); + }); +} + +async function main() { + console.log('=== TESTING WELLNUO CHAT ===\n'); + + // Step 1: Fetch patient context + console.log('1. Fetching patient context...'); + let context; + try { + context = await fetchPatientContext(); + console.log(` Patient: ${context.patient.name}, ${context.patient.age} years old`); + console.log(` Location: ${context.current_status.location}`); + console.log(` Activity: ${context.current_status.estimated_activity}`); + console.log(' Context API: OK\n'); + } catch (e) { + console.log(` ERROR: ${e.message}\n`); + process.exit(1); + } + + // Step 2: Ask questions about patient + const questions = [ + 'How is my father doing today?', + 'Did he sleep well last night?', + 'Where is he right now?' + ]; + + console.log('2. Testing AI chat responses:\n'); + + for (const question of questions) { + console.log(`Q: "${question}"`); + try { + const answer = await askAboutPatient(context, question); + console.log(`A: ${answer}\n`); + } catch (e) { + console.log(`ERROR: ${e.message}\n`); + } + } + + console.log('=== CHAT TEST COMPLETE ==='); +} + +main().catch(console.error);