diff --git a/.DS_Store b/.DS_Store index d56c0a7b..1b1d37eb 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/workflows/react-native-cicd.yml b/.github/workflows/react-native-cicd.yml index f0f206ba..90f8cf2b 100644 --- a/.github/workflows/react-native-cicd.yml +++ b/.github/workflows/react-native-cicd.yml @@ -47,6 +47,7 @@ env: UNIT_SENTRY_DSN: ${{ secrets.UNIT_SENTRY_DSN }} UNIT_ANDROID_KS: ${{ secrets.UNIT_ANDROID_KS }} UNIT_GOOGLE_SERVICES: ${{ secrets.UNIT_GOOGLE_SERVICES }} + UNIT_IOS_GOOGLE_SERVICES: ${{ secrets.UNIT_IOS_GOOGLE_SERVICES }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }} APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} @@ -71,6 +72,8 @@ env: UNIT_APP_KEY: ${{ secrets.UNIT_APP_KEY }} APP_KEY: ${{ secrets.APP_KEY }} NODE_OPTIONS: --openssl-legacy-provider + CHANGERAWR_API_KEY: ${{ secrets.CHANGERAWR_API_KEY }} + CHANGERAWR_API_URL: ${{ secrets.CHANGERAWR_API_URL }} jobs: check-skip: @@ -153,6 +156,11 @@ jobs: run: | echo $UNIT_GOOGLE_SERVICES | base64 -d > google-services.json + - name: 📋 Create Google Json File for iOS + if: ${{ matrix.platform == 'ios' }} + run: | + echo $UNIT_IOS_GOOGLE_SERVICES | base64 -d > GoogleService-Info.plist + - name: 📋 Update package.json Versions run: | # Ensure jq exists on both Linux and macOS @@ -287,33 +295,145 @@ jobs: - name: 📋 Prepare Release Notes file if: ${{ matrix.platform == 'android' }} - env: - RELEASE_NOTES_INPUT: ${{ github.event.inputs.release_notes }} - PR_BODY: ${{ github.event.pull_request.body }} run: | set -eo pipefail - # Determine source of release notes: workflow input, PR body, or recent commits - if [ -n "$RELEASE_NOTES_INPUT" ]; then - NOTES="$RELEASE_NOTES_INPUT" - elif [ -n "$PR_BODY" ]; then - NOTES="$(printf '%s\n' "$PR_BODY" \ + + # Function to extract release notes from PR body + extract_release_notes() { + local body="$1" + # Try to extract content under "## Release Notes" heading + local notes="$(printf '%s\n' "$body" \ | awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')" - else + + # If no specific section found, use the entire body (up to first 500 chars for safety) + if [ -z "$notes" ]; then + notes="$(printf '%s\n' "$body" | head -c 500)" + fi + + printf '%s\n' "$notes" + } + + # Determine source of release notes + NOTES="" + + # Check if this was triggered by a push event (likely a merge) + if [ "${{ github.event_name }}" = "push" ]; then + echo "Fetching PR body for merged commit..." + + # First, try to find PR number from commit message (most reliable) + PR_FROM_COMMIT=$(git log -1 --pretty=%B | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "") + + if [ -n "$PR_FROM_COMMIT" ]; then + echo "Found PR #$PR_FROM_COMMIT from commit message" + PR_BODY=$(gh pr view "$PR_FROM_COMMIT" --json body --jq '.body' 2>/dev/null || echo "") + + if [ -n "$PR_BODY" ]; then + NOTES="$(extract_release_notes "$PR_BODY")" + fi + else + echo "No PR reference in commit message, searching by commit SHA..." + # Get PRs that contain this commit (using GitHub API to search by commit) + PR_NUMBERS=$(gh api \ + "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \ + --jq '.[].number' 2>/dev/null || echo "") + + if [ -n "$PR_NUMBERS" ]; then + # Take the first PR found (most recently merged) + PR_NUMBER=$(echo "$PR_NUMBERS" | head -n 1) + echo "Found PR #$PR_NUMBER associated with commit" + + # Fetch the PR body + PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null || echo "") + + if [ -n "$PR_BODY" ]; then + NOTES="$(extract_release_notes "$PR_BODY")" + fi + else + echo "No associated PR found for this commit" + fi + fi + fi + + # Fallback to recent commits if no PR body found + if [ -z "$NOTES" ]; then + echo "No PR body found, using recent commits..." NOTES="$(git log -n 5 --pretty=format:'- %s')" fi + # Fail if no notes extracted if [ -z "$NOTES" ]; then echo "Error: No release notes extracted" >&2 exit 1 fi + # Write header and notes to file { echo "## Version 7.${{ github.run_number }} - $(date +%Y-%m-%d)" echo printf '%s\n' "$NOTES" } > RELEASE_NOTES.md + + echo "Release notes prepared:" + cat RELEASE_NOTES.md + env: + GH_TOKEN: ${{ github.token }} + + - name: 📝 Send Release Notes to Changerawr + if: ${{ matrix.platform == 'android' }} + run: | + set -eo pipefail + + # Check if required secrets are set + if [ -z "$CHANGERAWR_API_URL" ] || [ -z "$CHANGERAWR_API_KEY" ]; then + echo "⚠️ Changerawr API credentials not configured, skipping release notes submission" + exit 0 + fi + + # Read release notes + RELEASE_NOTES=$(cat RELEASE_NOTES.md) + VERSION="7.${{ github.run_number }}" + + # Prepare JSON payload + PAYLOAD=$(jq -n \ + --arg version "$VERSION" \ + --arg notes "$RELEASE_NOTES" \ + --arg platform "android" \ + --arg buildNumber "${{ github.run_number }}" \ + --arg commitSha "${{ github.sha }}" \ + --arg buildUrl "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ + '{ + version: $version, + releaseNotes: $notes, + platform: $platform, + buildNumber: $buildNumber, + commitSha: $commitSha, + buildUrl: $buildUrl, + timestamp: now | todate + }') + + echo "Sending release notes to Changerawr..." + + # Send to Changerawr API + RESPONSE=$(curl -X POST "$CHANGERAWR_API_URL" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $CHANGERAWR_API_KEY" \ + -d "$PAYLOAD" \ + -w "\n%{http_code}" \ + -s) + + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + RESPONSE_BODY=$(echo "$RESPONSE" | sed '$d') + + if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then + echo "✅ Successfully sent release notes to Changerawr (HTTP $HTTP_CODE)" + echo "Response: $RESPONSE_BODY" + else + echo "⚠️ Failed to send release notes to Changerawr (HTTP $HTTP_CODE)" + echo "Response: $RESPONSE_BODY" + # Don't fail the build, just warn + fi - - name: 📦 Create Release + - name: �📦 Create Release if: ${{ matrix.platform == 'android' && (github.event.inputs.buildType == 'all' || github.event_name == 'push' || github.event.inputs.buildType == 'prod-apk') }} uses: ncipollo/release-action@v1 with: diff --git a/.gitignore b/.gitignore index a7dec767..9ee0cbe5 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ yarn-error.log /build /automatic-build google-services.json +GoogleService-Info.plist credentials.json Gemfile.lock Gemfile diff --git a/__mocks__/react-native-ble-plx.ts b/__mocks__/react-native-ble-plx.ts index b39b31aa..46e96fdb 100644 --- a/__mocks__/react-native-ble-plx.ts +++ b/__mocks__/react-native-ble-plx.ts @@ -47,8 +47,6 @@ export class BleManager { private static mockDevices: Device[] = []; private static stateListener: ((state: State) => void) | null = null; - constructor() {} - static setMockState(state: State) { this.mockState = state; if (this.stateListener) { diff --git a/app.config.ts b/app.config.ts index 4a9c9b2c..b4e78418 100644 --- a/app.config.ts +++ b/app.config.ts @@ -44,6 +44,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ supportsTablet: true, bundleIdentifier: Env.BUNDLE_ID, requireFullScreen: true, + googleServicesFile: 'GoogleService-Info.plist', infoPlist: { UIBackgroundModes: ['remote-notification', 'audio', 'bluetooth-central', 'voip'], ITSAppUsesNonExemptEncryption: false, @@ -214,6 +215,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ }, ios: { deploymentTarget: '18.1', + useFrameworks: 'static', }, }, ], @@ -262,6 +264,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ '@livekit/react-native-expo-plugin', '@config-plugins/react-native-webrtc', '@config-plugins/react-native-callkeep', + '@react-native-firebase/app', './customGradle.plugin.js', './customManifest.plugin.js', ['app-icon-badge', appIconBadgeConfig], diff --git a/eas.json b/eas.json index 464eb3e3..4d287bb1 100644 --- a/eas.json +++ b/eas.json @@ -80,6 +80,22 @@ "EXPO_NO_DOTENV": "1" } }, + "dev-sim": { + "developmentClient": false, + "yarn": "1.22.22", + "ios": { + "simulator": true, + "image": "latest" + }, + "android": { + "buildType": "apk", + "image": "latest" + }, + "env": { + "APP_ENV": "development", + "EXPO_NO_DOTENV": "1" + } + }, "simulator": { "yarn": "1.22.22", "ios": { diff --git a/package.json b/package.json index b1853009..b4f79a5b 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,9 @@ "@notifee/react-native": "^9.1.8", "@novu/react-native": "~2.6.6", "@react-native-community/netinfo": "^11.4.1", + "@react-native-firebase/analytics": "^23.5.0", + "@react-native-firebase/app": "^23.5.0", + "@react-native-firebase/messaging": "^23.5.0", "@rnmapbox/maps": "10.1.42-rc.0", "@semantic-release/git": "^10.0.1", "@sentry/react-native": "~6.14.0", @@ -122,7 +125,7 @@ "expo-localization": "~16.1.6", "expo-location": "~18.1.6", "expo-navigation-bar": "~4.2.8", - "expo-notifications": "~0.31.4", + "expo-notifications": "0.28.3", "expo-router": "~5.1.7", "expo-screen-orientation": "~8.1.7", "expo-sharing": "~13.1.5", diff --git a/src/components/calls/dispatch-selection-modal.tsx b/src/components/calls/dispatch-selection-modal.tsx index 6edb4262..d109d2d2 100644 --- a/src/components/calls/dispatch-selection-modal.tsx +++ b/src/components/calls/dispatch-selection-modal.tsx @@ -1,6 +1,6 @@ import { CheckIcon, SearchIcon, UsersIcon, X } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; -import React, { useEffect, useMemo } from 'react'; +import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { ScrollView, TouchableOpacity, View } from 'react-native'; @@ -27,7 +27,7 @@ export const DispatchSelectionModal: React.FC = ({ const { data, selection, isLoading, error, searchQuery, fetchDispatchData, setSelection, toggleEveryone, toggleUser, toggleGroup, toggleRole, toggleUnit, setSearchQuery, clearSelection, getFilteredData } = useDispatchStore(); - const filteredData = useMemo(() => getFilteredData(), [data, searchQuery]); + const filteredData = getFilteredData(); useEffect(() => { if (isVisible) { @@ -121,9 +121,8 @@ export const DispatchSelectionModal: React.FC = ({ toggleUser(user.Id)}> {selection.users.includes(user.Id) && } @@ -148,9 +147,8 @@ export const DispatchSelectionModal: React.FC = ({ toggleGroup(group.Id)}> {selection.groups.includes(group.Id) && } @@ -175,9 +173,8 @@ export const DispatchSelectionModal: React.FC = ({ toggleRole(role.Id)}> {selection.roles.includes(role.Id) && } @@ -202,9 +199,8 @@ export const DispatchSelectionModal: React.FC = ({ toggleUnit(unit.Id)}> {selection.units.includes(unit.Id) && } diff --git a/src/components/push-notification/push-notification-modal.tsx b/src/components/push-notification/push-notification-modal.tsx index fc6da3c4..1f5f2f43 100644 --- a/src/components/push-notification/push-notification-modal.tsx +++ b/src/components/push-notification/push-notification-modal.tsx @@ -11,30 +11,20 @@ import { VStack } from '@/components/ui/vstack'; import { useAnalytics } from '@/hooks/use-analytics'; import { type NotificationType, usePushNotificationModalStore } from '@/stores/push-notification/store'; -interface NotificationIconProps { - type: NotificationType; - size?: number; - color?: string; -} - const NotificationIcon = ({ type }: { type: NotificationType }) => { - const iconProps = { - size: 24, - color: '$red500', - testID: 'notification-icon', - }; + const iconSize = 24; switch (type) { case 'call': - return ; + return ; case 'message': - return ; + return ; case 'chat': - return ; + return ; case 'group-chat': - return ; + return ; default: - return ; + return ; } }; @@ -81,26 +71,10 @@ export const PushNotificationModal: React.FC = () => { } }; - const getNotificationColor = (type: NotificationType): string => { - switch (type) { - case 'call': - return '#EF4444'; // Red for calls - case 'message': - return '#3B82F6'; // Blue for messages - case 'chat': - return '#10B981'; // Green for chat - case 'group-chat': - return '#8B5CF6'; // Purple for group chat - default: - return '#6B7280'; // Gray for unknown - } - }; - if (!notification) { return null; } - const iconColor = getNotificationColor(notification.type); const typeText = getNotificationTypeText(notification.type); return ( @@ -111,8 +85,8 @@ export const PushNotificationModal: React.FC = () => { - {t('push_notifications.new_notification')} - {typeText} + {t('push_notifications.new_notification')} + {typeText} @@ -122,21 +96,21 @@ export const PushNotificationModal: React.FC = () => { {notification.title ? ( {t('push_notifications.title')} - {notification.title} + {notification.title} ) : null} {notification.body ? ( {t('push_notifications.message')} - {notification.body} + {notification.body} ) : null} {notification.type === 'unknown' ? ( - - - {t('push_notifications.unknown_type_warning')} + + + {t('push_notifications.unknown_type_warning')} ) : null} diff --git a/src/services/__tests__/app-initialization.service.test.ts b/src/services/__tests__/app-initialization.service.test.ts index 17ddc5e7..4208afcd 100644 --- a/src/services/__tests__/app-initialization.service.test.ts +++ b/src/services/__tests__/app-initialization.service.test.ts @@ -22,14 +22,23 @@ jest.mock('../callkeep.service.ios', () => ({ }, })); +// Mock Push Notification service +jest.mock('../push-notification', () => ({ + pushNotificationService: { + initialize: jest.fn(), + }, +})); + import { Platform } from 'react-native'; import { logger } from '../../lib/logging'; import { appInitializationService } from '../app-initialization.service'; import { callKeepService } from '../callkeep.service.ios'; +import { pushNotificationService } from '../push-notification'; const mockLogger = logger as jest.Mocked; const mockCallKeepService = callKeepService as jest.Mocked; +const mockPushNotificationService = pushNotificationService as jest.Mocked; describe('AppInitializationService', () => { beforeEach(() => { @@ -38,6 +47,7 @@ describe('AppInitializationService', () => { appInitializationService.reset(); mockCallKeepService.setup.mockResolvedValue(undefined); mockCallKeepService.cleanup.mockResolvedValue(undefined); + mockPushNotificationService.initialize.mockResolvedValue(undefined); }); describe('Initialization', () => { @@ -54,12 +64,17 @@ describe('AppInitializationService', () => { supportsVideo: false, }); + expect(mockPushNotificationService.initialize).toHaveBeenCalled(); + expect(mockLogger.info).toHaveBeenCalledWith({ message: 'Starting app initialization', }); expect(mockLogger.info).toHaveBeenCalledWith({ message: 'CallKeep initialized successfully', }); + expect(mockLogger.info).toHaveBeenCalledWith({ + message: 'Push Notification Service initialized successfully', + }); expect(mockLogger.info).toHaveBeenCalledWith({ message: 'App initialization completed successfully', }); @@ -73,6 +88,7 @@ describe('AppInitializationService', () => { await appInitializationService.initialize(); expect(mockCallKeepService.setup).not.toHaveBeenCalled(); + expect(mockPushNotificationService.initialize).toHaveBeenCalled(); expect(mockLogger.debug).toHaveBeenCalledWith({ message: 'CallKeep initialization skipped - not iOS platform', context: { platform: 'android' }, @@ -90,10 +106,12 @@ describe('AppInitializationService', () => { // First call await appInitializationService.initialize(); expect(mockCallKeepService.setup).toHaveBeenCalledTimes(1); + expect(mockPushNotificationService.initialize).toHaveBeenCalledTimes(1); // Second call await appInitializationService.initialize(); expect(mockCallKeepService.setup).toHaveBeenCalledTimes(1); // Should not be called again + expect(mockPushNotificationService.initialize).toHaveBeenCalledTimes(1); // Should not be called again expect(mockLogger.debug).toHaveBeenCalledWith({ message: 'App initialization already completed, skipping', @@ -114,6 +132,7 @@ describe('AppInitializationService', () => { // CallKeep setup should only be called once expect(mockCallKeepService.setup).toHaveBeenCalledTimes(1); + expect(mockPushNotificationService.initialize).toHaveBeenCalledTimes(1); expect(appInitializationService.isAppInitialized()).toBe(true); }); @@ -130,6 +149,29 @@ describe('AppInitializationService', () => { context: { error }, }); + // Push notifications should still be initialized + expect(mockPushNotificationService.initialize).toHaveBeenCalled(); + + // App should still be considered initialized + expect(appInitializationService.isAppInitialized()).toBe(true); + }); + + it('should handle Push Notification Service initialization errors gracefully', async () => { + (Platform as any).OS = 'ios'; + const error = new Error('Push notification initialization failed'); + mockPushNotificationService.initialize.mockRejectedValue(error); + + // Should not throw error - Push notification failure shouldn't prevent app startup + await appInitializationService.initialize(); + + expect(mockLogger.error).toHaveBeenCalledWith({ + message: 'Failed to initialize Push Notification Service', + context: { error }, + }); + + // CallKeep should still be initialized + expect(mockCallKeepService.setup).toHaveBeenCalled(); + // App should still be considered initialized expect(appInitializationService.isAppInitialized()).toBe(true); }); diff --git a/src/services/__tests__/notification-sound.service.test.ts b/src/services/__tests__/notification-sound.service.test.ts new file mode 100644 index 00000000..45a96c09 --- /dev/null +++ b/src/services/__tests__/notification-sound.service.test.ts @@ -0,0 +1,142 @@ +import { Audio } from 'expo-av'; + +import { notificationSoundService } from '../notification-sound.service'; + +// Mock expo-av +jest.mock('expo-av', () => ({ + Audio: { + Sound: { + createAsync: jest.fn(() => + Promise.resolve({ + sound: { + setPositionAsync: jest.fn(() => Promise.resolve()), + playAsync: jest.fn(() => Promise.resolve()), + unloadAsync: jest.fn(() => Promise.resolve()), + }, + }) + ), + }, + setAudioModeAsync: jest.fn(() => Promise.resolve()), + }, + InterruptionModeIOS: { + DuckOthers: 1, + }, +})); + +// Mock expo-asset +jest.mock('expo-asset', () => ({ + Asset: { + fromModule: jest.fn(() => ({ + downloadAsync: jest.fn(() => Promise.resolve()), + localUri: 'file://mock-sound.wav', + uri: 'file://mock-sound.wav', + })), + loadAsync: jest.fn(() => Promise.resolve()), + }, +})); + +// Mock logger +jest.mock('@/lib/logging', () => ({ + logger: { + info: jest.fn(), + debug: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +describe('NotificationSoundService', () => { + beforeEach(async () => { + jest.clearAllMocks(); + // Clean up any previous initialization state + await notificationSoundService.cleanup(); + }); + + afterEach(async () => { + // Clean up after each test + await notificationSoundService.cleanup(); + }); + + describe('initialization', () => { + it('should initialize audio mode when initialize() is called', async () => { + // Initialization is lazy, so Audio.setAudioModeAsync should not be called until initialize() is called + await notificationSoundService.initialize(); + expect(Audio.setAudioModeAsync).toHaveBeenCalled(); + }); + + it('should preload and load audio assets', async () => { + await notificationSoundService.initialize(); + + // Verify sounds are created (called during initialization) + expect(Audio.Sound.createAsync).toHaveBeenCalled(); + }); + + it('should not initialize multiple times concurrently', async () => { + // Call initialize multiple times concurrently + await Promise.all([ + notificationSoundService.initialize(), + notificationSoundService.initialize(), + notificationSoundService.initialize(), + ]); + + // Should only initialize once + expect(Audio.setAudioModeAsync).toHaveBeenCalledTimes(1); + }); + }); + + describe('playNotificationSound', () => { + it('should play call sound for call notification type', async () => { + await notificationSoundService.playNotificationSound('call'); + + // Just verify it doesn't throw + expect(true).toBe(true); + }); + + it('should play message sound for message notification type', async () => { + await notificationSoundService.playNotificationSound('message'); + + // Just verify it doesn't throw + expect(true).toBe(true); + }); + + it('should play chat sound for chat notification type', async () => { + await notificationSoundService.playNotificationSound('chat'); + + // Just verify it doesn't throw + expect(true).toBe(true); + }); + + it('should play group chat sound for group-chat notification type', async () => { + await notificationSoundService.playNotificationSound('group-chat'); + + // Just verify it doesn't throw + expect(true).toBe(true); + }); + + it('should play default sound for unknown notification type', async () => { + await notificationSoundService.playNotificationSound('unknown'); + + // Just verify it doesn't throw + expect(true).toBe(true); + }); + + it('should handle errors when playing sound gracefully', async () => { + // Should not throw even if there's an error + await expect(notificationSoundService.playNotificationSound('call')).resolves.not.toThrow(); + }); + }); + + describe('cleanup', () => { + it('should unload all sounds', async () => { + await notificationSoundService.cleanup(); + + // Just verify it doesn't throw + expect(true).toBe(true); + }); + + it('should handle cleanup errors gracefully', async () => { + // Should not throw + await expect(notificationSoundService.cleanup()).resolves.not.toThrow(); + }); + }); +}); diff --git a/src/services/__tests__/push-notification.test.ts b/src/services/__tests__/push-notification.test.ts index 6314b1dc..32cf26a9 100644 --- a/src/services/__tests__/push-notification.test.ts +++ b/src/services/__tests__/push-notification.test.ts @@ -26,9 +26,13 @@ jest.mock('react-native', () => ({ })); // Mock expo-notifications +const mockRemove = jest.fn(); +const mockAddNotificationReceivedListener = jest.fn().mockReturnValue({ remove: mockRemove }); +const mockAddNotificationResponseReceivedListener = jest.fn().mockReturnValue({ remove: mockRemove }); + jest.mock('expo-notifications', () => ({ - addNotificationReceivedListener: jest.fn(), - addNotificationResponseReceivedListener: jest.fn(), + addNotificationReceivedListener: mockAddNotificationReceivedListener, + addNotificationResponseReceivedListener: mockAddNotificationResponseReceivedListener, removeNotificationSubscription: jest.fn(), setNotificationHandler: jest.fn(), setNotificationChannelAsync: jest.fn(), @@ -80,16 +84,15 @@ jest.mock('@/stores/security/store', () => ({ }, })); -// Mock the push notification service -const mockNotificationHandler = jest.fn(); +// Mock Firebase messaging +const mockFcmUnsubscribe = jest.fn(); +const mockOnMessage = jest.fn().mockReturnValue(mockFcmUnsubscribe); -jest.mock('../push-notification', () => ({ - pushNotificationService: { - getInstance: jest.fn(() => ({ - handleNotificationReceived: mockNotificationHandler, - })), - }, -})); +jest.mock('@react-native-firebase/messaging', () => { + return jest.fn(() => ({ + onMessage: mockOnMessage, + })); +}); describe('Push Notification Service Integration', () => { const mockShowNotificationModal = jest.fn(); @@ -122,9 +125,11 @@ describe('Push Notification Service Integration', () => { data: data.data || {}, sound: null, }, - trigger: null, + trigger: { + type: 'push', + }, }, - } as Notifications.Notification); + } as unknown as Notifications.Notification); // Test the notification handling logic directly const simulateNotificationReceived = (notification: Notifications.Notification): void => { @@ -367,4 +372,65 @@ describe('Push Notification Service Integration', () => { expect(mockShowNotificationModal).not.toHaveBeenCalled(); }); }); + + describe('listener cleanup', () => { + // Dynamically require the service to avoid mocking issues + let PushNotificationService: any; + let service: any; + + beforeEach(() => { + jest.clearAllMocks(); + jest.resetModules(); + + // Re-require the module to get fresh instance + jest.unmock('../push-notification'); + const module = require('../push-notification'); + service = module.pushNotificationService; + }); + + it('should store listener handles on initialization', async () => { + // Initialize should add listeners + await service.initialize(); + + // Verify listeners were registered + expect(mockAddNotificationReceivedListener).toHaveBeenCalled(); + expect(mockAddNotificationResponseReceivedListener).toHaveBeenCalled(); + expect(mockOnMessage).toHaveBeenCalled(); + }); + + it('should properly cleanup all listeners', async () => { + // Initialize first to set up listeners + await service.initialize(); + + // Clear previous calls + mockRemove.mockClear(); + mockFcmUnsubscribe.mockClear(); + + // Call cleanup + service.cleanup(); + + // Verify all listeners were removed + // Expo listeners should have .remove() called twice (once for each listener) + expect(mockRemove).toHaveBeenCalledTimes(2); + + // FCM unsubscribe should be called once + expect(mockFcmUnsubscribe).toHaveBeenCalledTimes(1); + }); + + it('should not error when cleanup is called without initialization', () => { + // Should not throw when cleanup is called without initializing + expect(() => service.cleanup()).not.toThrow(); + }); + + it('should not error when cleanup is called multiple times', async () => { + await service.initialize(); + + // Should not throw when cleanup is called multiple times + expect(() => { + service.cleanup(); + service.cleanup(); + service.cleanup(); + }).not.toThrow(); + }); + }); }); diff --git a/src/services/app-initialization.service.ts b/src/services/app-initialization.service.ts index 9fc09df2..94384644 100644 --- a/src/services/app-initialization.service.ts +++ b/src/services/app-initialization.service.ts @@ -2,6 +2,7 @@ import { Platform } from 'react-native'; import { logger } from '../lib/logging'; import { callKeepService } from './callkeep.service.ios'; +import { pushNotificationService } from './push-notification'; /** * Global app initialization service that handles one-time setup operations @@ -73,6 +74,9 @@ class AppInitializationService { // Initialize CallKeep for iOS background audio support await this._initializeCallKeep(); + // Initialize Push Notification Service + await this._initializePushNotifications(); + // Add other global initialization tasks here as needed // e.g., analytics, crash reporting, background services, etc. } @@ -111,6 +115,26 @@ class AppInitializationService { } } + /** + * Initialize Push Notification Service + */ + private async _initializePushNotifications(): Promise { + try { + await pushNotificationService.initialize(); + + logger.info({ + message: 'Push Notification Service initialized successfully', + }); + } catch (error) { + logger.error({ + message: 'Failed to initialize Push Notification Service', + context: { error }, + }); + // Don't throw here - push notification failure shouldn't prevent app startup + // but notifications may not work properly + } + } + /** * Check if the service has been initialized */ diff --git a/src/services/notification-sound.service.ts b/src/services/notification-sound.service.ts new file mode 100644 index 00000000..7043dc5a --- /dev/null +++ b/src/services/notification-sound.service.ts @@ -0,0 +1,275 @@ +import { Asset } from 'expo-asset'; +import { Audio, type AVPlaybackSource, InterruptionModeIOS } from 'expo-av'; + +import { logger } from '@/lib/logging'; +import type { NotificationType } from '@/stores/push-notification/store'; + +class NotificationSoundService { + private static instance: NotificationSoundService; + private callSound: Audio.Sound | null = null; + private messageSound: Audio.Sound | null = null; + private chatSound: Audio.Sound | null = null; + private groupChatSound: Audio.Sound | null = null; + private defaultSound: Audio.Sound | null = null; + private isInitialized = false; + private initializationPromise: Promise | null = null; + + private constructor() { + // Initialization will happen lazily on first use + } + + static getInstance(): NotificationSoundService { + if (!NotificationSoundService.instance) { + NotificationSoundService.instance = new NotificationSoundService(); + } + return NotificationSoundService.instance; + } + + public async initialize(): Promise { + await this.initializeAudio(); + } + + private async initializeAudio(): Promise { + // If already initialized, return immediately + if (this.isInitialized) { + return; + } + + // If initialization is in progress, wait for it to complete + if (this.initializationPromise) { + return this.initializationPromise; + } + + // Start initialization and store the promise + this.initializationPromise = this.performInitialization(); + await this.initializationPromise; + } + + private async performInitialization(): Promise { + try { + // Configure audio mode + await Audio.setAudioModeAsync({ + allowsRecordingIOS: false, + staysActiveInBackground: false, + playsInSilentModeIOS: true, + shouldDuckAndroid: true, + playThroughEarpieceAndroid: false, + interruptionModeIOS: InterruptionModeIOS.DuckOthers, + }); + + // Pre-load audio assets + await this.preloadAudioAssets(); + + // Load audio files + await this.loadAudioFiles(); + + this.isInitialized = true; + + logger.info({ + message: 'Notification sound service initialized successfully', + }); + } catch (error) { + logger.error({ + message: 'Failed to initialize notification sound service', + context: { error }, + }); + // Reset initialization promise on failure so it can be retried + this.initializationPromise = null; + } + } + + private async preloadAudioAssets(): Promise { + try { + await Promise.all([ + Asset.loadAsync(require('@assets/audio/newcall.wav')), + Asset.loadAsync(require('@assets/audio/newmessage.wav')), + Asset.loadAsync(require('@assets/audio/newchat.wav')), + Asset.loadAsync(require('@assets/audio/notification.wav')), + ]); + + logger.debug({ + message: 'Notification audio assets preloaded successfully', + }); + } catch (error) { + logger.error({ + message: 'Error preloading notification audio assets', + context: { error }, + }); + } + } + + /** + * Helper method to load a sound asset from a require path + * @param assetRequire - The required asset module + * @param soundName - Name of the sound for logging purposes + * @returns The created Audio.Sound or null on failure + */ + private async loadSound(assetRequire: number, soundName: string): Promise { + try { + const asset = Asset.fromModule(assetRequire); + await asset.downloadAsync(); + + const { sound } = await Audio.Sound.createAsync({ uri: asset.localUri || asset.uri } as AVPlaybackSource, { + shouldPlay: false, + isLooping: false, + volume: 1.0, + }); + + return sound; + } catch (error) { + logger.error({ + message: `Failed to load sound: ${soundName}`, + context: { error }, + }); + return null; + } + } + + private async loadAudioFiles(): Promise { + try { + // Load call sound + this.callSound = await this.loadSound(require('@assets/audio/newcall.wav'), 'call'); + + // Load message sound + this.messageSound = await this.loadSound(require('@assets/audio/newmessage.wav'), 'message'); + + // Load chat sound + const chatSoundAsset = Asset.fromModule(require('@assets/audio/newchat.wav')); + await chatSoundAsset.downloadAsync(); + + this.chatSound = await this.loadSound(require('@assets/audio/newchat.wav'), 'chat'); + + // Group chat uses the same sound as regular chat + const { sound: groupChatSound } = await Audio.Sound.createAsync({ uri: chatSoundAsset.localUri || chatSoundAsset.uri } as AVPlaybackSource, { + shouldPlay: false, + isLooping: false, + volume: 1.0, + }); + this.groupChatSound = groupChatSound; + + // Load default notification sound + this.defaultSound = await this.loadSound(require('@assets/audio/notification.wav'), 'default'); + + logger.debug({ + message: 'Notification audio files loaded successfully', + }); + } catch (error) { + logger.error({ + message: 'Failed to load notification audio files', + context: { error }, + }); + } + } + + private async playSound(sound: Audio.Sound | null, soundName: string): Promise { + try { + if (!sound) { + logger.warn({ + message: `Notification sound not loaded: ${soundName}`, + }); + return; + } + + // Ensure audio service is initialized + if (!this.isInitialized) { + await this.initializeAudio(); + } + + // Reset to start and play + await sound.setPositionAsync(0); + await sound.playAsync(); + + logger.debug({ + message: 'Notification sound played successfully', + context: { soundName }, + }); + } catch (error) { + logger.error({ + message: 'Failed to play notification sound', + context: { soundName, error }, + }); + } + } + + public async playNotificationSound(type: NotificationType): Promise { + try { + // Ensure audio is initialized before accessing sound properties + await this.initializeAudio(); + + // Capture sound properties into local variables after initialization + const callSound = this.callSound; + const messageSound = this.messageSound; + const chatSound = this.chatSound; + const groupChatSound = this.groupChatSound; + const defaultSound = this.defaultSound; + + switch (type) { + case 'call': + await this.playSound(callSound, 'call'); + break; + case 'message': + await this.playSound(messageSound, 'message'); + break; + case 'chat': + await this.playSound(chatSound, 'chat'); + break; + case 'group-chat': + await this.playSound(groupChatSound, 'group-chat'); + break; + case 'unknown': + default: + await this.playSound(defaultSound, 'default'); + break; + } + } catch (error) { + logger.error({ + message: 'Failed to play notification sound for type', + context: { type, error }, + }); + } + } + + async cleanup(): Promise { + try { + // Unload all sounds + if (this.callSound) { + await this.callSound.unloadAsync(); + this.callSound = null; + } + + if (this.messageSound) { + await this.messageSound.unloadAsync(); + this.messageSound = null; + } + + if (this.chatSound) { + await this.chatSound.unloadAsync(); + this.chatSound = null; + } + + if (this.groupChatSound) { + await this.groupChatSound.unloadAsync(); + this.groupChatSound = null; + } + + if (this.defaultSound) { + await this.defaultSound.unloadAsync(); + this.defaultSound = null; + } + + this.isInitialized = false; + this.initializationPromise = null; + + logger.info({ + message: 'Notification sound service cleaned up', + }); + } catch (error) { + logger.error({ + message: 'Error during notification sound service cleanup', + context: { error }, + }); + } + } +} + +export const notificationSoundService = NotificationSoundService.getInstance(); diff --git a/src/services/push-notification.ts b/src/services/push-notification.ts index 42aceb8a..2238fc11 100644 --- a/src/services/push-notification.ts +++ b/src/services/push-notification.ts @@ -1,3 +1,5 @@ +import notifee from '@notifee/react-native'; +import messaging from '@react-native-firebase/messaging'; import * as Device from 'expo-device'; import * as Notifications from 'expo-notifications'; import { useEffect, useRef } from 'react'; @@ -17,26 +19,12 @@ export interface PushNotificationData { data?: Record; } -// Configure notifications behavior -Notifications.setNotificationHandler({ - handleNotification: async () => ({ - shouldShowAlert: true, - shouldPlaySound: true, - shouldSetBadge: true, - shouldShowBanner: true, - shouldShowList: true, - }), -}); - class PushNotificationService { private static instance: PushNotificationService; private pushToken: string | null = null; - private notificationListener: { remove: () => void } | null = null; - private responseListener: { remove: () => void } | null = null; - - private constructor() { - this.initialize(); - } + private notificationReceivedListener: { remove: () => void } | null = null; + private notificationResponseListener: { remove: () => void } | null = null; + private fcmOnMessageUnsubscribe: (() => void) | null = null; public static getInstance(): PushNotificationService { if (!PushNotificationService.instance) { @@ -89,13 +77,48 @@ class PushNotificationService { } } - private async initialize(): Promise { + async initialize(): Promise { // Set up Android notification channels await this.setupAndroidNotificationChannels(); - // Set up notification listeners - this.notificationListener = Notifications.addNotificationReceivedListener(this.handleNotificationReceived); - this.responseListener = Notifications.addNotificationResponseReceivedListener(this.handleNotificationResponse); + // Configure notifications behavior + Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: true, + shouldSetBadge: false, + shouldShowBanner: true, + shouldShowList: true, + }), + }); + + // Set up notification listeners and store the subscription handles + this.notificationReceivedListener = Notifications.addNotificationReceivedListener(this.handleNotificationReceived); + this.notificationResponseListener = Notifications.addNotificationResponseReceivedListener(this.handleNotificationResponse); + + // Listen for foreground messages and store the unsubscribe function + this.fcmOnMessageUnsubscribe = messaging().onMessage(async (remoteMessage) => { + logger.info({ + message: 'FCM Notification received', + context: { + data: remoteMessage.data, + }, + }); + + // Check if the notification has an eventCode and show modal + // eventCode must be a string to be valid + if (remoteMessage.data && remoteMessage.data.eventCode && typeof remoteMessage.data.eventCode === 'string') { + const notificationData = { + eventCode: remoteMessage.data.eventCode as string, + title: remoteMessage.notification?.title || undefined, + body: remoteMessage.notification?.body || undefined, + data: remoteMessage.data, + }; + + // Show the notification modal using the store + usePushNotificationModalStore.getState().showNotificationModal(notificationData); + } + }); logger.info({ message: 'Push notification service initialized', @@ -264,14 +287,19 @@ class PushNotificationService { } public cleanup(): void { - if (this.notificationListener) { - this.notificationListener.remove(); - this.notificationListener = null; + if (this.notificationReceivedListener) { + this.notificationReceivedListener.remove(); + this.notificationReceivedListener = null; + } + + if (this.notificationResponseListener) { + this.notificationResponseListener.remove(); + this.notificationResponseListener = null; } - if (this.responseListener) { - this.responseListener.remove(); - this.responseListener = null; + if (this.fcmOnMessageUnsubscribe) { + this.fcmOnMessageUnsubscribe(); + this.fcmOnMessageUnsubscribe = null; } } } diff --git a/src/stores/push-notification/__tests__/store.test.ts b/src/stores/push-notification/__tests__/store.test.ts index 764b8de4..5ab2f556 100644 --- a/src/stores/push-notification/__tests__/store.test.ts +++ b/src/stores/push-notification/__tests__/store.test.ts @@ -1,4 +1,5 @@ import { logger } from '@/lib/logging'; +import { notificationSoundService } from '@/services/notification-sound.service'; import { usePushNotificationModalStore } from '../store'; // Mock logger service @@ -10,6 +11,13 @@ jest.mock('@/lib/logging', () => ({ }, })); +// Mock notification sound service +jest.mock('@/services/notification-sound.service', () => ({ + notificationSoundService: { + playNotificationSound: jest.fn(() => Promise.resolve()), + }, +})); + describe('usePushNotificationModalStore', () => { beforeEach(() => { // Reset store state before each test @@ -30,7 +38,7 @@ describe('usePushNotificationModalStore', () => { describe('showNotificationModal', () => { it('should show modal with call notification', () => { const callData = { - eventCode: 'C:1234', + eventCode: 'C1234', title: 'Emergency Call', body: 'Structure fire reported at Main St', }; @@ -43,15 +51,16 @@ describe('usePushNotificationModalStore', () => { expect(state.notification).toEqual({ type: 'call', id: '1234', - eventCode: 'C:1234', + eventCode: 'C1234', title: 'Emergency Call', body: 'Structure fire reported at Main St', + data: undefined, }); }); it('should show modal with message notification', () => { const messageData = { - eventCode: 'M:5678', + eventCode: 'M5678', title: 'New Message', body: 'You have a new message from dispatch', }; @@ -64,15 +73,16 @@ describe('usePushNotificationModalStore', () => { expect(state.notification).toEqual({ type: 'message', id: '5678', - eventCode: 'M:5678', + eventCode: 'M5678', title: 'New Message', body: 'You have a new message from dispatch', + data: undefined, }); }); it('should show modal with chat notification', () => { const chatData = { - eventCode: 'T:9101', + eventCode: 'T9101', title: 'Chat Message', body: 'New message in chat', }; @@ -85,15 +95,16 @@ describe('usePushNotificationModalStore', () => { expect(state.notification).toEqual({ type: 'chat', id: '9101', - eventCode: 'T:9101', + eventCode: 'T9101', title: 'Chat Message', body: 'New message in chat', + data: undefined, }); }); it('should show modal with group chat notification', () => { const groupChatData = { - eventCode: 'G:1121', + eventCode: 'G1121', title: 'Group Chat', body: 'New message in group chat', }; @@ -106,15 +117,16 @@ describe('usePushNotificationModalStore', () => { expect(state.notification).toEqual({ type: 'group-chat', id: '1121', - eventCode: 'G:1121', + eventCode: 'G1121', title: 'Group Chat', body: 'New message in group chat', + data: undefined, }); }); it('should handle unknown notification type', () => { const unknownData = { - eventCode: 'X:9999', + eventCode: 'X9999', title: 'Unknown', body: 'Unknown notification type', }; @@ -127,15 +139,16 @@ describe('usePushNotificationModalStore', () => { expect(state.notification).toEqual({ type: 'unknown', id: '9999', - eventCode: 'X:9999', + eventCode: 'X9999', title: 'Unknown', body: 'Unknown notification type', + data: undefined, }); }); it('should handle notification without valid eventCode', () => { const dataWithInvalidEventCode = { - eventCode: 'INVALID', + eventCode: 'I', title: 'Invalid Event Code', body: 'Notification with invalid event code', }; @@ -148,15 +161,16 @@ describe('usePushNotificationModalStore', () => { expect(state.notification).toEqual({ type: 'unknown', id: '', - eventCode: 'INVALID', + eventCode: 'I', title: 'Invalid Event Code', body: 'Notification with invalid event code', + data: undefined, }); }); it('should log info message when showing notification', () => { const callData = { - eventCode: 'C:1234', + eventCode: 'C1234', title: 'Emergency Call', body: 'Structure fire reported at Main St', }; @@ -169,17 +183,51 @@ describe('usePushNotificationModalStore', () => { context: { type: 'call', id: '1234', - eventCode: 'C:1234', + eventCode: 'C1234', }, }); }); + + it('should play notification sound when showing modal', () => { + const callData = { + eventCode: 'C1234', + title: 'Emergency Call', + body: 'Structure fire reported at Main St', + }; + + const store = usePushNotificationModalStore.getState(); + store.showNotificationModal(callData); + + expect(notificationSoundService.playNotificationSound).toHaveBeenCalledWith('call'); + }); + + it('should handle sound playback error gracefully', async () => { + const mockError = new Error('Sound playback failed'); + (notificationSoundService.playNotificationSound as jest.Mock).mockRejectedValueOnce(mockError); + + const callData = { + eventCode: 'C1234', + title: 'Emergency Call', + body: 'Structure fire reported at Main St', + }; + + const store = usePushNotificationModalStore.getState(); + store.showNotificationModal(callData); + + // Wait for async error handling + await new Promise((resolve) => setTimeout(resolve, 0)); + + // Modal should still be shown even if sound fails + const state = usePushNotificationModalStore.getState(); + expect(state.isOpen).toBe(true); + }); }); describe('hideNotificationModal', () => { it('should hide modal and clear notification', () => { // First show a notification const callData = { - eventCode: 'C:1234', + eventCode: 'C1234', title: 'Emergency Call', body: 'Structure fire reported at Main St', }; @@ -215,79 +263,79 @@ describe('usePushNotificationModalStore', () => { it('should parse call event code correctly', () => { const store = usePushNotificationModalStore.getState(); const parsed = store.parseNotification({ - eventCode: 'C:1234', + eventCode: 'C1234', title: 'Emergency Call', body: 'Structure fire', }); expect(parsed.type).toBe('call'); expect(parsed.id).toBe('1234'); - expect(parsed.eventCode).toBe('C:1234'); + expect(parsed.eventCode).toBe('C1234'); }); it('should parse message event code correctly', () => { const store = usePushNotificationModalStore.getState(); const parsed = store.parseNotification({ - eventCode: 'M:5678', + eventCode: 'M5678', title: 'New Message', body: 'Message content', }); expect(parsed.type).toBe('message'); expect(parsed.id).toBe('5678'); - expect(parsed.eventCode).toBe('M:5678'); + expect(parsed.eventCode).toBe('M5678'); }); it('should parse chat event code correctly', () => { const store = usePushNotificationModalStore.getState(); const parsed = store.parseNotification({ - eventCode: 'T:9101', + eventCode: 'T9101', title: 'Chat Message', body: 'Chat content', }); expect(parsed.type).toBe('chat'); expect(parsed.id).toBe('9101'); - expect(parsed.eventCode).toBe('T:9101'); + expect(parsed.eventCode).toBe('T9101'); }); it('should parse group chat event code correctly', () => { const store = usePushNotificationModalStore.getState(); const parsed = store.parseNotification({ - eventCode: 'G:1121', + eventCode: 'G1121', title: 'Group Chat', body: 'Group chat content', }); expect(parsed.type).toBe('group-chat'); expect(parsed.id).toBe('1121'); - expect(parsed.eventCode).toBe('G:1121'); + expect(parsed.eventCode).toBe('G1121'); }); it('should handle lowercase event codes', () => { const store = usePushNotificationModalStore.getState(); const parsed = store.parseNotification({ - eventCode: 'c:1234', + eventCode: 'c1234', title: 'Emergency Call', body: 'Structure fire', }); expect(parsed.type).toBe('call'); expect(parsed.id).toBe('1234'); - expect(parsed.eventCode).toBe('c:1234'); + expect(parsed.eventCode).toBe('c1234'); }); - it('should handle event code without colon', () => { + it('should handle single character event code', () => { const store = usePushNotificationModalStore.getState(); const parsed = store.parseNotification({ - eventCode: 'C1234', + eventCode: 'C', title: 'Emergency Call', body: 'Structure fire', }); expect(parsed.type).toBe('unknown'); expect(parsed.id).toBe(''); - expect(parsed.eventCode).toBe('C1234'); + expect(parsed.eventCode).toBe('C'); }); it('should handle invalid event code format', () => { @@ -299,7 +347,7 @@ describe('usePushNotificationModalStore', () => { }); expect(parsed.type).toBe('unknown'); - expect(parsed.id).toBe(''); + expect(parsed.id).toBe('NVALID'); expect(parsed.eventCode).toBe('INVALID'); }); diff --git a/src/stores/push-notification/store.ts b/src/stores/push-notification/store.ts index 15082838..a4bc6fde 100644 --- a/src/stores/push-notification/store.ts +++ b/src/stores/push-notification/store.ts @@ -1,6 +1,7 @@ import { create } from 'zustand'; import { logger } from '@/lib/logging'; +import { notificationSoundService } from '@/services/notification-sound.service'; export interface PushNotificationData { eventCode: string; @@ -37,22 +38,21 @@ export const usePushNotificationModalStore = create( let type: NotificationType = 'unknown'; let id = ''; - // Parse event code format like "C:1234", "M:5678", "T:9012", "G:3456" - if (eventCode && eventCode.includes(':')) { - const [prefix, notificationId] = eventCode.split(':'); - const lowerPrefix = prefix.toLowerCase(); + // Parse event code format like "C1234", "M5678", "T9012", "G3456" + // First character is the type prefix, rest is the ID + if (eventCode && eventCode.length > 1) { + const prefix = eventCode[0].toLowerCase(); + id = eventCode.slice(1); - if (lowerPrefix.startsWith('c')) { + if (prefix === 'c') { type = 'call'; - } else if (lowerPrefix.startsWith('m')) { + } else if (prefix === 'm') { type = 'message'; - } else if (lowerPrefix.startsWith('t')) { + } else if (prefix === 't') { type = 'chat'; - } else if (lowerPrefix.startsWith('g')) { + } else if (prefix === 'g') { type = 'group-chat'; } - - id = notificationId || ''; } return { @@ -77,6 +77,14 @@ export const usePushNotificationModalStore = create( }, }); + // Play the appropriate sound for this notification type + notificationSoundService.playNotificationSound(parsedNotification.type).catch((error) => { + logger.error({ + message: 'Failed to play notification sound', + context: { error, type: parsedNotification.type }, + }); + }); + set({ isOpen: true, notification: parsedNotification, diff --git a/src/translations/ar.json b/src/translations/ar.json index 9ab68a22..9ee3983c 100644 --- a/src/translations/ar.json +++ b/src/translations/ar.json @@ -513,8 +513,10 @@ "call": "مكالمة", "chat": "محادثة", "group_chat": "محادثة جماعية", - "message": "رسالة" + "message": "رسالة", + "notification": "إشعار" }, + "unknown_type_warning": "نوع هذا الإشعار غير معروف. يرجى الاتصال بالدعم إذا استمر ظهور هذه الرسالة.", "view_call": "عرض المكالمة" }, "roles": { diff --git a/src/translations/en.json b/src/translations/en.json index d37836e2..1c0d6c20 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -507,15 +507,17 @@ "push_notifications": { "close": "Close", "message": "Message", - "new_notification": "New notification", + "new_notification": "New Notification", "title": "Title", "types": { "call": "Call", "chat": "Chat", "group_chat": "Group Chat", - "message": "Message" + "message": "Message", + "notification": "Notification" }, - "view_call": "View call" + "unknown_type_warning": "This notification type is not recognized. Please contact support if you continue to see this message.", + "view_call": "View Call" }, "roles": { "modal": { diff --git a/src/translations/es.json b/src/translations/es.json index ee1b4a29..2e39239d 100644 --- a/src/translations/es.json +++ b/src/translations/es.json @@ -507,15 +507,17 @@ "push_notifications": { "close": "Cerrar", "message": "Mensaje", - "new_notification": "Nueva notificación", + "new_notification": "Nueva Notificación", "title": "Título", "types": { "call": "Llamada", "chat": "Chat", - "group_chat": "Chat grupal", - "message": "Mensaje" + "group_chat": "Chat Grupal", + "message": "Mensaje", + "notification": "Notificación" }, - "view_call": "Ver llamada" + "unknown_type_warning": "Este tipo de notificación no se reconoce. Por favor, contacte con soporte si continúa viendo este mensaje.", + "view_call": "Ver Llamada" }, "roles": { "modal": { diff --git a/yarn.lock b/yarn.lock index 71027350..08f556e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1175,6 +1175,32 @@ xcode "^3.0.1" xml2js "0.6.0" +"@expo/config-plugins@~8.0.8": + version "8.0.11" + resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-8.0.11.tgz#b814395a910f4c8b7cc95d9719dccb6ca53ea4c5" + integrity sha512-oALE1HwnLFthrobAcC9ocnR9KXLzfWEjgIe4CPe+rDsfC6GDs8dGYCXfRFoCEzoLN4TGYs9RdZ8r0KoCcNrm2A== + dependencies: + "@expo/config-types" "^51.0.3" + "@expo/json-file" "~8.3.0" + "@expo/plist" "^0.1.0" + "@expo/sdk-runtime-versions" "^1.0.0" + chalk "^4.1.2" + debug "^4.3.1" + find-up "~5.0.0" + getenv "^1.0.0" + glob "7.1.6" + 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" + +"@expo/config-types@^51.0.3": + version "51.0.3" + resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-51.0.3.tgz#520bdce5fd75f9d234fd81bd0347443086419450" + integrity sha512-hMfuq++b8VySb+m9uNNrlpbvGxYc8OcFCUX9yTmi9tlx6A4k8SDabWFBgmnr4ao3wEArvWrtUQIfQCVtPRdpKA== + "@expo/config-types@^53.0.5": version "53.0.5" resolved "https://registry.yarnpkg.com/@expo/config-types/-/config-types-53.0.5.tgz#bba7e0712c2c5b1d8963348d68ea96339f858db4" @@ -1199,6 +1225,23 @@ slugify "^1.3.4" sucrase "3.35.0" +"@expo/config@~9.0.0": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@expo/config/-/config-9.0.4.tgz#52f0a94edd0e2c36dfb5e284cc1a6d99d9d2af97" + integrity sha512-g5ns5u1JSKudHYhjo1zaSfkJ/iZIcWmUmIQptMJZ6ag1C0ShL2sj8qdfU8MmAMuKLOgcIfSaiWlQnm4X3VJVkg== + dependencies: + "@babel/code-frame" "~7.10.4" + "@expo/config-plugins" "~8.0.8" + "@expo/config-types" "^51.0.3" + "@expo/json-file" "^8.3.0" + getenv "^1.0.0" + glob "7.1.6" + require-from-string "^2.0.2" + resolve-from "^5.0.0" + semver "^7.6.0" + slugify "^1.3.4" + sucrase "3.34.0" + "@expo/devcert@^1.1.2": version "1.2.0" resolved "https://registry.yarnpkg.com/@expo/devcert/-/devcert-1.2.0.tgz#7b32c2d959e36baaa0649433395e5170c808b44f" @@ -1208,6 +1251,17 @@ debug "^3.1.0" glob "^10.4.2" +"@expo/env@~0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@expo/env/-/env-0.3.0.tgz#a66064e5656e0e48197525f47f3398034fdf579e" + integrity sha512-OtB9XVHWaXidLbHvrVDeeXa09yvTl3+IQN884sO6PhIi2/StXfgSH/9zC7IvzrDB8kW3EBJ1PPLuCUJ2hxAT7Q== + dependencies: + chalk "^4.0.0" + debug "^4.3.4" + dotenv "~16.4.5" + dotenv-expand "~11.0.6" + getenv "^1.0.0" + "@expo/env@~1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@expo/env/-/env-1.0.7.tgz#6ee604e158d0f140fc2be711b9a7cb3adc341889" @@ -1242,6 +1296,22 @@ resolved "https://registry.yarnpkg.com/@expo/html-elements/-/html-elements-0.10.1.tgz#ec2625370cf1d4cb78efa954df45d422532d5ab6" integrity sha512-3PTmtkV15D7+lykXVtvkH1jQ5Y6JE+e3zCaoMMux7z2cSLGQUNwDEUwG37gew3OEB1/E4/SEWgjvg8m7E6/e2Q== +"@expo/image-utils@^0.5.0": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.5.1.tgz#06fade141facebcd8431355923d30f3839309942" + integrity sha512-U/GsFfFox88lXULmFJ9Shfl2aQGcwoKPF7fawSCLixIKtMCpsI+1r0h+5i0nQnmt9tHuzXZDL8+Dg1z6OhkI9A== + 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" + node-fetch "^2.6.0" + parse-png "^2.1.0" + resolve-from "^5.0.0" + semver "^7.6.0" + tempy "0.3.0" + "@expo/image-utils@^0.7.6": version "0.7.6" resolved "https://registry.yarnpkg.com/@expo/image-utils/-/image-utils-0.7.6.tgz#b8442bef770e1c7b39997d57f666bffeeced0a7a" @@ -1265,6 +1335,15 @@ "@babel/code-frame" "~7.10.4" json5 "^2.2.3" +"@expo/json-file@^8.3.0", "@expo/json-file@~8.3.0": + version "8.3.3" + resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-8.3.3.tgz#7926e3592f76030ce63d6b1308ac8f5d4d9341f4" + integrity sha512-eZ5dld9AD0PrVRiIWpRkm5aIoWBw3kAyd8VkuWEy92sEthBKDDDHAnK2a0dw0Eil6j7rK7lS/Qaq/Zzngv2h5A== + dependencies: + "@babel/code-frame" "~7.10.4" + json5 "^2.2.2" + write-file-atomic "^2.3.0" + "@expo/json-file@^9.1.5", "@expo/json-file@~9.1.5": version "9.1.5" resolved "https://registry.yarnpkg.com/@expo/json-file/-/json-file-9.1.5.tgz#7d7b2dc4990dc2c2de69a571191aba984b7fb7ed" @@ -1323,6 +1402,15 @@ ora "^3.4.0" resolve-workspace-root "^2.0.0" +"@expo/plist@^0.1.0": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.1.3.tgz#b4fbee2c4f7a88512a4853d85319f4d95713c529" + integrity sha512-GW/7hVlAylYg1tUrEASclw1MMk9FP4ZwyFAY/SUTJIhPDQHtfOlXREyWV3hhrHdX/K+pS73GNgdfT6E/e+kBbg== + dependencies: + "@xmldom/xmldom" "~0.7.7" + base64-js "^1.2.3" + xmlbuilder "^14.0.0" + "@expo/plist@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@expo/plist/-/plist-0.3.5.tgz#11913c64951936101529cb26d7260ef16970fc31" @@ -1400,6 +1488,397 @@ find-up "^5.0.0" js-yaml "^4.1.0" +"@firebase/ai@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@firebase/ai/-/ai-2.4.0.tgz#fe67130f8b8770aa74c028f511dc61b67440a30a" + integrity sha512-YilG6AJ/nYpCKtxZyvEzBRAQv5bU+2tBOKX4Ps0rNNSdxN39aT37kGhjATbk1kq1z5Lq7mkWglw/ajAF3lOWUg== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/analytics-compat@0.2.25": + version "0.2.25" + resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.25.tgz#1f48bb6237bed7d6a3cf8136957aa5ceb245507b" + integrity sha512-fdzoaG0BEKbqksRDhmf4JoyZf16Wosrl0Y7tbZtJyVDOOwziE0vrFjmZuTdviL0yhak+Nco6rMsUUbkbD+qb6Q== + dependencies: + "@firebase/analytics" "0.10.19" + "@firebase/analytics-types" "0.8.3" + "@firebase/component" "0.7.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/analytics-types@0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.3.tgz#d08cd39a6209693ca2039ba7a81570dfa6c1518f" + integrity sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg== + +"@firebase/analytics@0.10.19": + version "0.10.19" + resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.19.tgz#6bddeb9db287fa2367066855b12ec514e2914697" + integrity sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/installations" "0.6.19" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/app-check-compat@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz#94ac0cf9f66cab1d81a7b14e0c151dcc2684bc95" + integrity sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g== + dependencies: + "@firebase/app-check" "0.11.0" + "@firebase/app-check-types" "0.5.3" + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/app-check-interop-types@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz#ed9c4a4f48d1395ef378f007476db3940aa5351a" + integrity sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A== + +"@firebase/app-check-types@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.3.tgz#38ba954acf4bffe451581a32fffa20337f11d8e5" + integrity sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng== + +"@firebase/app-check@0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.11.0.tgz#a7e1d1e3f5ae36eabed1455db937114fe869ce8f" + integrity sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/app-compat@0.5.4": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.5.4.tgz#eb8f47d51c57887c9979279d4d39e4ee85270fe4" + integrity sha512-T7ifGmb+awJEcp542Ek4HtNfBxcBrnuk1ggUdqyFEdsXHdq7+wVlhvE6YukTL7NS8hIkEfL7TMAPx/uCNqt30g== + dependencies: + "@firebase/app" "0.14.4" + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/app-types@0.9.3": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.3.tgz#8408219eae9b1fb74f86c24e7150a148460414ad" + integrity sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw== + +"@firebase/app@0.14.4": + version "0.14.4" + resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.14.4.tgz#1d2ce74c09752dec9664e2f981b20335c4efbec1" + integrity sha512-pUxEGmR+uu21OG/icAovjlu1fcYJzyVhhT0rsCrn+zi+nHtrS43Bp9KPn9KGa4NMspCUE++nkyiqziuIvJdwzw== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/auth-compat@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.6.0.tgz#1464ea6049b2ad0aae83b4fdcd5e5e5aba6b1c50" + integrity sha512-J0lGSxXlG/lYVi45wbpPhcWiWUMXevY4fvLZsN1GHh+po7TZVng+figdHBVhFheaiipU8HZyc7ljw1jNojM2nw== + dependencies: + "@firebase/auth" "1.11.0" + "@firebase/auth-types" "0.13.0" + "@firebase/component" "0.7.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/auth-interop-types@0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz#176a08686b0685596ff03d7879b7e4115af53de0" + integrity sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA== + +"@firebase/auth-types@0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.13.0.tgz#ae6e0015e3bd4bfe18edd0942b48a0a118a098d9" + integrity sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg== + +"@firebase/auth@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.11.0.tgz#81a4f77b16d97c502e493b2a14a97443e243a2a0" + integrity sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/component@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.7.0.tgz#3736644fdb6d3572dceae7fdc1c35a8bd3819adc" + integrity sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg== + dependencies: + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/data-connect@0.3.11": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@firebase/data-connect/-/data-connect-0.3.11.tgz#60a7a9649e4aedd005546032466ef9abc0a544c1" + integrity sha512-G258eLzAD6im9Bsw+Qm1Z+P4x0PGNQ45yeUuuqe5M9B1rn0RJvvsQCRHXgE52Z+n9+WX1OJd/crcuunvOGc7Vw== + dependencies: + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/database-compat@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-2.1.0.tgz#c64488d741c6da2ed8dcf02f2e433089dae2f590" + integrity sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/database" "1.1.0" + "@firebase/database-types" "1.0.16" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/database-types@1.0.16": + version "1.0.16" + resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.16.tgz#262f54b8dbebbc46259757b3ba384224fb2ede48" + integrity sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw== + dependencies: + "@firebase/app-types" "0.9.3" + "@firebase/util" "1.13.0" + +"@firebase/database@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.1.0.tgz#bdf60f1605079a87ceb2b5e30d90846e0bde294b" + integrity sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + faye-websocket "0.11.4" + tslib "^2.1.0" + +"@firebase/firestore-compat@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.4.2.tgz#2c9f5bef1dff004ae2db5759608f17d7973da78c" + integrity sha512-cy7ov6SpFBx+PHwFdOOjbI7kH00uNKmIFurAn560WiPCZXy9EMnil1SOG7VF4hHZKdenC+AHtL4r3fNpirpm0w== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/firestore" "4.9.2" + "@firebase/firestore-types" "3.0.3" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/firestore-types@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.3.tgz#7d0c3dd8850c0193d8f5ee0cc8f11961407742c1" + integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q== + +"@firebase/firestore@4.9.2": + version "4.9.2" + resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.9.2.tgz#0c65203f9754d5aa801ec31c6f885445cfb346f4" + integrity sha512-iuA5+nVr/IV/Thm0Luoqf2mERUvK9g791FZpUJV1ZGXO6RL2/i/WFJUj5ZTVXy5pRjpWYO+ZzPcReNrlilmztA== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + "@firebase/webchannel-wrapper" "1.0.5" + "@grpc/grpc-js" "~1.9.0" + "@grpc/proto-loader" "^0.7.8" + tslib "^2.1.0" + +"@firebase/functions-compat@0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.4.1.tgz#b253b761845f0c82bbdf76ef59975978ed84eb65" + integrity sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/functions" "0.13.1" + "@firebase/functions-types" "0.6.3" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/functions-types@0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.3.tgz#f5faf770248b13f45d256f614230da6a11bfb654" + integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg== + +"@firebase/functions@0.13.1": + version "0.13.1" + resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.13.1.tgz#472e8456568689154b87a494ee8c10ee2e610d94" + integrity sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw== + dependencies: + "@firebase/app-check-interop-types" "0.3.3" + "@firebase/auth-interop-types" "0.2.4" + "@firebase/component" "0.7.0" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/installations-compat@0.2.19": + version "0.2.19" + resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.19.tgz#4bc57c8c57d241eeca95900ff3033d6ec3dbcc7c" + integrity sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/installations" "0.6.19" + "@firebase/installations-types" "0.5.3" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/installations-types@0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.3.tgz#cac8a14dd49f09174da9df8ae453f9b359c3ef2f" + integrity sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA== + +"@firebase/installations@0.6.19": + version "0.6.19" + resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.19.tgz#93c569321f6fb399f4f1a197efc0053ce6452c7c" + integrity sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/util" "1.13.0" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/logger@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.5.0.tgz#a9e55b1c669a0983dc67127fa4a5964ce8ed5e1b" + integrity sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g== + dependencies: + tslib "^2.1.0" + +"@firebase/messaging-compat@0.2.23": + version "0.2.23" + resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz#2ca6b36ea238fae4dff53bf85442c4a2af516224" + integrity sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/messaging" "0.12.23" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/messaging-interop-types@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz#e647c9cd1beecfe6a6e82018a6eec37555e4da3e" + integrity sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q== + +"@firebase/messaging@0.12.23": + version "0.12.23" + resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.23.tgz#71f932a521ac39d9f036175672e37897531010eb" + integrity sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/installations" "0.6.19" + "@firebase/messaging-interop-types" "0.2.3" + "@firebase/util" "1.13.0" + idb "7.1.1" + tslib "^2.1.0" + +"@firebase/performance-compat@0.2.22": + version "0.2.22" + resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.22.tgz#1c24ea360b03cfef831bdf379b4fc7080f412741" + integrity sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/performance" "0.7.9" + "@firebase/performance-types" "0.2.3" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/performance-types@0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.3.tgz#5ce64e90fa20ab5561f8b62a305010cf9fab86fb" + integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ== + +"@firebase/performance@0.7.9": + version "0.7.9" + resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.7.9.tgz#7e3a072b1542f0df3f502684a38a0516b0d72cab" + integrity sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/installations" "0.6.19" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + web-vitals "^4.2.4" + +"@firebase/remote-config-compat@0.2.20": + version "0.2.20" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.20.tgz#4bca09b1361867d0c882411970486ee06622e071" + integrity sha512-P/ULS9vU35EL9maG7xp66uljkZgcPMQOxLj3Zx2F289baTKSInE6+YIkgHEi1TwHoddC/AFePXPpshPlEFkbgg== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/logger" "0.5.0" + "@firebase/remote-config" "0.7.0" + "@firebase/remote-config-types" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/remote-config-types@0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz#f0f503b32edda3384f5252f9900cd9613adbb99c" + integrity sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg== + +"@firebase/remote-config@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.7.0.tgz#0e5e5879a7a9121c9da55606be8fa40ff70ddae1" + integrity sha512-dX95X6WlW7QlgNd7aaGdjAIZUiQkgWgNS+aKNu4Wv92H1T8Ue/NDUjZHd9xb8fHxLXIHNZeco9/qbZzr500MjQ== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/installations" "0.6.19" + "@firebase/logger" "0.5.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/storage-compat@0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.4.0.tgz#a09bd33c262123e7e3ed0cd590b4c6e2ce4a8902" + integrity sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/storage" "0.14.0" + "@firebase/storage-types" "0.8.3" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/storage-types@0.8.3": + version "0.8.3" + resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.3.tgz#2531ef593a3452fc12c59117195d6485c6632d3d" + integrity sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg== + +"@firebase/storage@0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.14.0.tgz#01acb97d413ada7c91de860fb260623468baa25d" + integrity sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA== + dependencies: + "@firebase/component" "0.7.0" + "@firebase/util" "1.13.0" + tslib "^2.1.0" + +"@firebase/util@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.13.0.tgz#2e9e7569722a1e3fc86b1b4076d5cbfbfa7265d6" + integrity sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ== + dependencies: + tslib "^2.1.0" + +"@firebase/webchannel-wrapper@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz#39cf5a600450cb42f1f0b507cc385459bf103b27" + integrity sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw== + "@floating-ui/core@^1.6.0", "@floating-ui/core@^1.7.3": version "1.7.3" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.3.tgz#462d722f001e23e46d86fd2bd0d21b7693ccb8b7" @@ -1813,6 +2292,24 @@ dependencies: nanoid "^3.3.1" +"@grpc/grpc-js@~1.9.0": + version "1.9.15" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.9.15.tgz#433d7ac19b1754af690ea650ab72190bd700739b" + integrity sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ== + dependencies: + "@grpc/proto-loader" "^0.7.8" + "@types/node" ">=12.12.47" + +"@grpc/proto-loader@^0.7.8": + version "0.7.15" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.15.tgz#4cdfbf35a35461fc843abe8b9e2c0770b5095e60" + integrity sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ== + dependencies: + lodash.camelcase "^4.3.0" + long "^5.0.0" + protobufjs "^7.2.5" + yargs "^17.7.2" + "@hookform/resolvers@~3.9.0": version "3.9.1" resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.9.1.tgz#a23883c40bfd449cb6c6ab5a0fa0729184c950ff" @@ -2728,6 +3225,59 @@ "@pnpm/network.ca-file" "^1.0.1" config-chain "^1.1.11" +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@radix-ui/react-compose-refs@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30" @@ -3063,6 +3613,25 @@ resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-11.4.1.tgz#a3c247aceab35f75dd0aa4bfa85d2be5a4508688" integrity sha512-B0BYAkghz3Q2V09BF88RA601XursIEA111tnc2JOaN7axJWmNefmfjZqw/KdSxKZp7CZUuPpjBmz/WCR9uaHYg== +"@react-native-firebase/analytics@^23.5.0": + version "23.5.0" + resolved "https://registry.yarnpkg.com/@react-native-firebase/analytics/-/analytics-23.5.0.tgz#c79e1222d26d65a963d44df44e9572903e86a0f2" + integrity sha512-tLUVOB1dsrjFrh48OCMxS44MCn5OyOmE0Am1jPFoHsnDF1Rj61zXdn9POQjHAXPxsbZmZSP0opxIpdzfNULn7Q== + dependencies: + superstruct "^2.0.2" + +"@react-native-firebase/app@^23.5.0": + version "23.5.0" + resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-23.5.0.tgz#b84540b1822e510dfd3c3890e8ca10b2655759fe" + integrity sha512-TOlm6V6fbILwgFP37QZM9Y0nfAW6zqNGVIWlMlepQB6b/BzzFMrCl1FiyknqD5l7i1jgdFQrqX1WH6ZO4ePa/g== + dependencies: + firebase "12.4.0" + +"@react-native-firebase/messaging@^23.5.0": + version "23.5.0" + resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-23.5.0.tgz#8e39a44a90f7bf95a9a167649efa7f1e8251f9b7" + integrity sha512-2EM28isDWgqCauar/kOnhpFQZ8ARnq9iE0N093TrS/sr+Mu6PHkPEDJElV9LFfp6nfxxjlm75h+x+nJrEDRkhQ== + "@react-native/assets-registry@0.79.6": version "0.79.6" resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.79.6.tgz#cecc2a1140a9584d590000b951a08a0611ec30c3" @@ -4383,6 +4952,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708" integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g== +"@types/node@>=12.12.47", "@types/node@>=13.7.0": + version "24.9.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.9.2.tgz#90ded2422dbfcafcf72080f28975adc21366148d" + integrity sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA== + dependencies: + undici-types "~7.16.0" + "@types/normalize-package-data@^2.4.0", "@types/normalize-package-data@^2.4.3": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" @@ -4725,6 +5301,11 @@ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz#b79de2d67389734c57c52595f7a7305e30c2d608" integrity sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw== +"@xmldom/xmldom@~0.7.7": + version "0.7.13" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3" + integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g== + "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" @@ -6176,6 +6757,11 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + integrity sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -7374,6 +7960,11 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" +expo-application@~5.9.0: + version "5.9.1" + resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-5.9.1.tgz#a12e0cf2741b6f084cc49cd0121ad0a70c770459" + integrity sha512-uAfLBNZNahnDZLRU41ZFmNSKtetHUT9Ua557/q189ua0AWV7pQjoVAx49E4953feuvqc9swtU3ScZ/hN1XO/FQ== + expo-application@~6.1.5: version "6.1.5" resolved "https://registry.yarnpkg.com/expo-application/-/expo-application-6.1.5.tgz#78e569ed8ab237c9bae67d693fec629dd447e53d" @@ -7405,6 +7996,14 @@ expo-build-properties@~0.14.8: ajv "^8.11.0" semver "^7.6.0" +expo-constants@~16.0.0: + version "16.0.2" + resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-16.0.2.tgz#eb5a1bddb7308fd8cadac8fc44decaf4784cac5e" + integrity sha512-9tNY3OVO0jfiMzl7ngb6IOyR5VFzNoN5OOazUWoeGfmMqVB5kltTemRvKraK9JRbBKIw+SOYLEmF0sEqgFZ6OQ== + dependencies: + "@expo/config" "~9.0.0" + "@expo/env" "~0.3.0" + expo-constants@~17.1.7: version "17.1.7" resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-17.1.7.tgz#35194c1cef51f1ea756333418f1e077be79a012b" @@ -7562,18 +8161,19 @@ expo-navigation-bar@~4.2.8: react-native-edge-to-edge "1.6.0" react-native-is-edge-to-edge "^1.1.6" -expo-notifications@~0.31.4: - version "0.31.4" - resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.31.4.tgz#295a4345f7fea6ff974df9005790dda1d17ce00e" - integrity sha512-NnGKIFGpgZU66qfiFUyjEBYsS77VahURpSSeWEOLt+P1zOaUFlgx2XqS+dxH3/Bn1Vm7TMj04qKsK5KvzR/8Lw== +expo-notifications@0.28.3: + version "0.28.3" + resolved "https://registry.yarnpkg.com/expo-notifications/-/expo-notifications-0.28.3.tgz#9076c2bd69c3de3338a2e2161c8bd5f18cb440cb" + integrity sha512-Xaj82eQUJzJXa8+giZr708ih86GGtkGS8N01epoiDkTKC8Z9783UJ8Pf8+PSFSfHsY3Sd8TJpQrD9n7QnGHwGQ== dependencies: - "@expo/image-utils" "^0.7.6" + "@expo/image-utils" "^0.5.0" "@ide/backoff" "^1.0.0" abort-controller "^3.0.0" assert "^2.0.0" badgin "^1.1.5" - expo-application "~6.1.5" - expo-constants "~17.1.7" + expo-application "~5.9.0" + expo-constants "~16.0.0" + fs-extra "^9.1.0" expo-router@~5.1.7: version "5.1.7" @@ -7720,6 +8320,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +faye-websocket@0.11.4: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + fb-watchman@^2.0.0, fb-watchman@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" @@ -7848,7 +8455,7 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -find-up@^5.0.0: +find-up@^5.0.0, find-up@~5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== @@ -7872,6 +8479,40 @@ find-yarn-workspace-root@^2.0.0: dependencies: micromatch "^4.0.2" +firebase@12.4.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/firebase/-/firebase-12.4.0.tgz#c9de52c23e707d90aa43343ab3ad1c1ce6b71301" + integrity sha512-/chNgDQ6ppPPGOQO4jctxOa/5JeQxuhaxA7Y90K0I+n/wPfoO8mRveedhVUdo7ExLcWUivnnow/ouSLYSI5Icw== + dependencies: + "@firebase/ai" "2.4.0" + "@firebase/analytics" "0.10.19" + "@firebase/analytics-compat" "0.2.25" + "@firebase/app" "0.14.4" + "@firebase/app-check" "0.11.0" + "@firebase/app-check-compat" "0.4.0" + "@firebase/app-compat" "0.5.4" + "@firebase/app-types" "0.9.3" + "@firebase/auth" "1.11.0" + "@firebase/auth-compat" "0.6.0" + "@firebase/data-connect" "0.3.11" + "@firebase/database" "1.1.0" + "@firebase/database-compat" "2.1.0" + "@firebase/firestore" "4.9.2" + "@firebase/firestore-compat" "0.4.2" + "@firebase/functions" "0.13.1" + "@firebase/functions-compat" "0.4.1" + "@firebase/installations" "0.6.19" + "@firebase/installations-compat" "0.2.19" + "@firebase/messaging" "0.12.23" + "@firebase/messaging-compat" "0.2.23" + "@firebase/performance" "0.7.9" + "@firebase/performance-compat" "0.2.22" + "@firebase/remote-config" "0.7.0" + "@firebase/remote-config-compat" "0.2.20" + "@firebase/storage" "0.14.0" + "@firebase/storage-compat" "0.4.0" + "@firebase/util" "1.13.0" + flat-cache@^3.0.4: version "3.2.0" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" @@ -7958,6 +8599,16 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fs-extra@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.0.tgz#b6afc31036e247b2466dc99c29ae797d5d4580a3" + integrity sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs-extra@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -7967,7 +8618,7 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^9.0.0: +fs-extra@^9.0.0, fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -8100,6 +8751,11 @@ get-tsconfig@^4.10.0, get-tsconfig@^4.7.5: dependencies: resolve-pkg-maps "^1.0.0" +getenv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/getenv/-/getenv-1.0.0.tgz#874f2e7544fbca53c7a4738f37de8605c3fcfc31" + integrity sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg== + getenv@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/getenv/-/getenv-2.0.0.tgz#b1698c7b0f29588f4577d06c42c73a5b475c69e0" @@ -8141,6 +8797,18 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^10.3.10, glob@^10.4.2: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" @@ -8393,6 +9061,11 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-parser-js@>=0.5.1: + version "0.5.10" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" + integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== + http-proxy-agent@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" @@ -8461,6 +9134,11 @@ iconv-lite@^0.7.0: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +idb@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" + integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -9835,7 +10513,7 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" -json5@^2.2.3: +json5@^2.2.2, json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -10353,6 +11031,11 @@ loglevel@^1.8.0, loglevel@^1.9.2: resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08" integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg== +long@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/long/-/long-5.3.2.tgz#1d84463095999262d7d7b7f8bfd4a8cc55167f83" + integrity sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== + loose-envify@^1.0.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -10876,7 +11559,7 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-fetch@^2.6.1, node-fetch@^2.6.7, node-fetch@^2.7.0: +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -11824,6 +12507,24 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== +protobufjs@^7.2.5: + version "7.5.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" + integrity sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" @@ -12631,7 +13332,7 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" -safe-buffer@5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -13425,6 +14126,19 @@ styleq@^0.1.3: resolved "https://registry.yarnpkg.com/styleq/-/styleq-0.1.3.tgz#8efb2892debd51ce7b31dc09c227ad920decab71" integrity sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA== +sucrase@3.34.0: + version "3.34.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f" + integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "7.1.6" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + sucrase@3.35.0, sucrase@^3.32.0: version "3.35.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" @@ -13438,6 +14152,11 @@ sucrase@3.35.0, sucrase@^3.32.0: pirates "^4.0.1" ts-interface-checker "^0.1.9" +superstruct@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" + integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -13585,11 +14304,25 @@ tar@^7.4.3: minizlib "^3.1.0" yallist "^5.0.0" +temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d" + integrity sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ== + temp-dir@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== +tempy@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tempy/-/tempy-0.3.0.tgz#6f6c5b295695a16130996ad5ab01a8bd726e8bf8" + integrity sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ== + dependencies: + temp-dir "^1.0.0" + type-fest "^0.3.1" + unique-string "^1.0.0" + terminal-link@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" @@ -13853,6 +14586,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" + integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -13975,6 +14713,11 @@ undici-types@~7.12.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.12.0.tgz#15c5c7475c2a3ba30659529f5cdb4674b622fafb" integrity sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ== +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + undici@^6.18.2: version "6.21.3" resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.3.tgz#185752ad92c3d0efe7a7d1f6854a50f83b552d7a" @@ -14018,6 +14761,13 @@ unimodules-app-loader@~5.1.3: resolved "https://registry.yarnpkg.com/unimodules-app-loader/-/unimodules-app-loader-5.1.3.tgz#c3be527cd36120fc77d6843253075c8a9246f622" integrity sha512-nPUkwfkpJWvdOQrVvyQSUol93/UdmsCVd9Hkx9RgAevmKSVYdZI+S87W73NGKl6QbwK9L1BDSY5OrQuo8Oq15g== +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + integrity sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg== + dependencies: + crypto-random-string "^1.0.0" + unique-string@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -14030,6 +14780,11 @@ universalify@^0.2.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== +universalify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" + integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -14234,6 +14989,11 @@ web-streams-polyfill@^4.1.0: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.2.0.tgz#93295e67af95889a1e044a6beff1366c82720650" integrity sha512-0rYDzGOh9EZpig92umN5g5D/9A1Kff7k0/mzPSSCY8jEQeYkgRMoY7LhbXtUCWzLCMX0TUE9aoHkjFNB7D9pfA== +web-vitals@^4.2.4: + version "4.2.4" + resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-4.2.4.tgz#1d20bc8590a37769bd0902b289550936069184b7" + integrity sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw== + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -14261,6 +15021,20 @@ webrtc-adapter@^9.0.1: dependencies: sdp "^3.2.0" +websocket-driver@>=0.5.1: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + well-known-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/well-known-symbols/-/well-known-symbols-4.1.0.tgz#7f748817d7bfdd4a95395127a056ce5205910624" @@ -14451,6 +15225,15 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +write-file-atomic@^2.3.0: + version "2.4.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" + integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ== + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + write-file-atomic@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" @@ -14550,6 +15333,11 @@ xml@^1.0.1: resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== +xmlbuilder@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-14.0.0.tgz#876b5aec4f05ffd5feb97b0a871c855d16fbeb8c" + integrity sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg== + xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" @@ -14605,7 +15393,7 @@ yargs-parser@^21.0.1, yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.0.0, yargs@^17.3.1, yargs@^17.6.2: +yargs@^17.0.0, yargs@^17.3.1, yargs@^17.6.2, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==