From 83bb4f0bdc4229fe41604ecead3e71bc6e6965af Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Sun, 25 Jan 2026 09:03:09 -0800 Subject: [PATCH 1/5] RU-T47 Working on Web and App support --- Dockerfile | 59 +- docker-compose.yml | 33 + docker/docker-entrypoint.sh | 51 + electron-builder.config.js | 94 + electron/entitlements.mac.plist | 26 + electron/main.js | 208 +++ electron/preload.js | 37 + expo-env.d.ts | 2 +- metro.config.js | 36 +- nginx.conf | 62 + package.json | 18 +- public/service-worker.js | 122 ++ src/app/(app)/index.tsx | 2 +- src/app/_layout.tsx | 10 +- .../maps/full-screen-location-picker.tsx | 2 +- src/components/maps/location-picker.tsx | 2 +- src/components/maps/map-pins.tsx | 2 +- src/components/maps/map-view.native.tsx | 31 + src/components/maps/map-view.web.tsx | 386 ++++ src/components/maps/mapbox.ts | 65 + src/components/maps/pin-marker.tsx | 2 +- src/components/maps/static-map.tsx | 2 +- src/lib/env.js | 39 +- src/lib/native-module-shims.web.ts | 162 ++ src/lib/platform.ts | 64 + src/services/push-notification.electron.ts | 163 ++ src/services/push-notification.web.ts | 236 +++ types/docker-env.d.ts | 31 + yarn.lock | 1616 ++++++++++++++++- 29 files changed, 3484 insertions(+), 79 deletions(-) create mode 100644 docker-compose.yml create mode 100644 docker/docker-entrypoint.sh create mode 100644 electron-builder.config.js create mode 100644 electron/entitlements.mac.plist create mode 100644 electron/main.js create mode 100644 electron/preload.js create mode 100644 nginx.conf create mode 100644 public/service-worker.js create mode 100644 src/components/maps/map-view.native.tsx create mode 100644 src/components/maps/map-view.web.tsx create mode 100644 src/components/maps/mapbox.ts create mode 100644 src/lib/native-module-shims.web.ts create mode 100644 src/lib/platform.ts create mode 100644 src/services/push-notification.electron.ts create mode 100644 src/services/push-notification.web.ts create mode 100644 types/docker-env.d.ts diff --git a/Dockerfile b/Dockerfile index dc6e2693..51e8e71d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,59 @@ ### STAGE 1: Build ### -FROM node:18.16.0-alpine AS build -WORKDIR /usr/src/app -COPY package.json package-lock.json ./ -RUN npm ci +FROM node:20-alpine AS build + +# Install build dependencies +RUN apk add --no-cache python3 make g++ + +WORKDIR /app + +# Copy package files +COPY package.json yarn.lock ./ + +# Install dependencies +RUN yarn install --frozen-lockfile + +# Copy source files COPY . . -RUN npm run build -- --configuration=production + +# Build the web application +RUN yarn web:build ### STAGE 2: Run ### -FROM nginx:1.21.6-alpine +FROM nginx:1.25-alpine + +# Install sed for the entrypoint script +RUN apk add --no-cache sed + +# Copy nginx configuration COPY nginx.conf /etc/nginx/nginx.conf -COPY --from=build /usr/src/app/www /usr/share/nginx/html + +# Copy built web app from build stage +COPY --from=build /app/dist /usr/share/nginx/html + +# Copy entrypoint script +COPY docker/docker-entrypoint.sh /docker-entrypoint.sh +RUN chmod +x /docker-entrypoint.sh # Expose port 80 EXPOSE 80 -# When the container starts, replace the env.js with values from environment variables -CMD ["/bin/sh", "-c", "envsubst < /usr/share/nginx/html/assets/env.prod.js > /usr/share/nginx/html/assets/env.js && exec nginx -g 'daemon off;'"] \ No newline at end of file +# Set default environment variables +ENV APP_ENV=production \ + UNIT_NAME="Resgrid Unit" \ + UNIT_SCHEME="ResgridUnit" \ + UNIT_VERSION="0.0.1" \ + UNIT_BASE_API_URL="https://api.resgrid.com" \ + UNIT_API_VERSION="v4" \ + UNIT_RESGRID_API_URL="/api/v4" \ + UNIT_CHANNEL_HUB_NAME="eventingHub" \ + UNIT_REALTIME_GEO_HUB_NAME="geolocationHub" \ + UNIT_LOGGING_KEY="" \ + UNIT_APP_KEY="" \ + UNIT_MAPBOX_PUBKEY="" \ + UNIT_SENTRY_DSN="" \ + UNIT_COUNTLY_APP_KEY="" \ + UNIT_COUNTLY_SERVER_URL="" + +# Use entrypoint to inject environment variables at runtime +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..ba2e92f3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,33 @@ +# Docker Compose for local development and testing +# Run: docker-compose up --build + +services: + web: + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:80" + environment: + - APP_ENV=production + - UNIT_NAME=Resgrid Unit + - UNIT_SCHEME=ResgridUnit + - UNIT_VERSION=0.0.1 + - UNIT_BASE_API_URL=https://api.resgrid.com + - UNIT_API_VERSION=v4 + - UNIT_RESGRID_API_URL=/api/v4 + - UNIT_CHANNEL_HUB_NAME=eventingHub + - UNIT_REALTIME_GEO_HUB_NAME=geolocationHub + - UNIT_LOGGING_KEY= + - UNIT_APP_KEY= + - UNIT_MAPBOX_PUBKEY= + - UNIT_SENTRY_DSN= + - UNIT_COUNTLY_APP_KEY= + - UNIT_COUNTLY_SERVER_URL= + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh new file mode 100644 index 00000000..3548a1dd --- /dev/null +++ b/docker/docker-entrypoint.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -e + +# Directory where the built web app is served +HTML_DIR="/usr/share/nginx/html" + +# Create the env-config.js file with environment variables +cat > "${HTML_DIR}/env-config.js" << EOF +// Runtime environment configuration - generated by docker-entrypoint.sh +// This file is generated at container startup and injects environment variables +window.__ENV__ = { + APP_ENV: "${APP_ENV:-production}", + NAME: "${UNIT_NAME:-Resgrid Unit}", + SCHEME: "${UNIT_SCHEME:-ResgridUnit}", + VERSION: "${UNIT_VERSION:-0.0.1}", + BASE_API_URL: "${UNIT_BASE_API_URL:-https://api.resgrid.com}", + API_VERSION: "${UNIT_API_VERSION:-v4}", + RESGRID_API_URL: "${UNIT_RESGRID_API_URL:-/api/v4}", + CHANNEL_HUB_NAME: "${UNIT_CHANNEL_HUB_NAME:-eventingHub}", + REALTIME_GEO_HUB_NAME: "${UNIT_REALTIME_GEO_HUB_NAME:-geolocationHub}", + LOGGING_KEY: "${UNIT_LOGGING_KEY:-}", + APP_KEY: "${UNIT_APP_KEY:-}", + UNIT_MAPBOX_PUBKEY: "${UNIT_MAPBOX_PUBKEY:-}", + IS_MOBILE_APP: false, + SENTRY_DSN: "${UNIT_SENTRY_DSN:-}", + COUNTLY_APP_KEY: "${UNIT_COUNTLY_APP_KEY:-}", + COUNTLY_SERVER_URL: "${UNIT_COUNTLY_SERVER_URL:-}" +}; +EOF + +echo "Generated env-config.js with runtime environment variables" + +# Check if index.html exists +if [ ! -f "${HTML_DIR}/index.html" ]; then + echo "Error: index.html not found in ${HTML_DIR}" + exit 1 +fi + +# Inject the env-config.js script into index.html if not already present +if ! grep -q "env-config.js" "${HTML_DIR}/index.html"; then + # Insert script tag right after the opening tag + sed -i 's||\n |' "${HTML_DIR}/index.html" + echo "Injected env-config.js script tag into index.html" +else + echo "env-config.js already present in index.html" +fi + +echo "Starting nginx..." + +# Execute the CMD (nginx) +exec "$@" diff --git a/electron-builder.config.js b/electron-builder.config.js new file mode 100644 index 00000000..1606b184 --- /dev/null +++ b/electron-builder.config.js @@ -0,0 +1,94 @@ +/** + * Electron Builder Configuration + * https://www.electron.build/configuration/configuration + */ +module.exports = { + appId: 'com.resgrid.unit', + productName: 'Resgrid Unit', + copyright: 'Copyright © 2024 Resgrid', + + directories: { + output: 'electron-dist', + buildResources: 'assets', + }, + + files: [ + 'dist/**/*', + 'electron/**/*', + '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}', + '!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}', + '!**/node_modules/.bin', + ], + + // macOS configuration + mac: { + category: 'public.app-category.productivity', + target: [ + { target: 'dmg', arch: ['x64', 'arm64'] }, + { target: 'zip', arch: ['x64', 'arm64'] }, + ], + icon: 'assets/icon.icns', + hardenedRuntime: true, + gatekeeperAssess: false, + entitlements: 'electron/entitlements.mac.plist', + entitlementsInherit: 'electron/entitlements.mac.plist', + darkModeSupport: true, + }, + + dmg: { + contents: [ + { x: 130, y: 220 }, + { x: 410, y: 220, type: 'link', path: '/Applications' }, + ], + window: { + width: 540, + height: 380, + }, + }, + + // Windows configuration + win: { + target: [ + { target: 'nsis', arch: ['x64'] }, + { target: 'portable', arch: ['x64'] }, + ], + icon: 'assets/icon.ico', + publisherName: 'Resgrid', + }, + + nsis: { + oneClick: false, + allowToChangeInstallationDirectory: true, + installerIcon: 'assets/icon.ico', + uninstallerIcon: 'assets/icon.ico', + installerHeaderIcon: 'assets/icon.ico', + createDesktopShortcut: true, + createStartMenuShortcut: true, + shortcutName: 'Resgrid Unit', + license: 'LICENSE', + }, + + // Linux configuration + linux: { + target: [ + { target: 'AppImage', arch: ['x64'] }, + { target: 'deb', arch: ['x64'] }, + { target: 'rpm', arch: ['x64'] }, + ], + category: 'Office', + icon: 'assets/icon.png', + maintainer: 'Resgrid ', + vendor: 'Resgrid', + desktop: { + Name: 'Resgrid Unit', + Comment: 'Resgrid Unit Management Application', + Category: 'Office;Utility;', + StartupWMClass: 'resgrid-unit', + }, + }, + + // Extra metadata + extraMetadata: { + main: 'electron/main.js', + }, +}; diff --git a/electron/entitlements.mac.plist b/electron/entitlements.mac.plist new file mode 100644 index 00000000..e4d03a02 --- /dev/null +++ b/electron/entitlements.mac.plist @@ -0,0 +1,26 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + com.apple.security.automation.apple-events + + + com.apple.security.network.client + + + com.apple.security.device.audio-input + + + com.apple.security.personal-information.location + + + com.apple.security.device.camera + + + diff --git a/electron/main.js b/electron/main.js new file mode 100644 index 00000000..caba7985 --- /dev/null +++ b/electron/main.js @@ -0,0 +1,208 @@ +const { app, BrowserWindow, ipcMain, Notification, nativeTheme, Menu } = require('electron'); +const path = require('path'); + +// Handle creating/removing shortcuts on Windows when installing/uninstalling. +if (require('electron-squirrel-startup')) { + app.quit(); +} + +let mainWindow = null; +const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged; + +function createWindow() { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + minWidth: 800, + minHeight: 600, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js'), + }, + // MacOS: use hidden title bar with traffic lights + titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default', + // Windows/Linux: show frame + frame: process.platform !== 'darwin' || true, + // Set the background color to match the app theme + backgroundColor: nativeTheme.shouldUseDarkColors ? '#1a1a1a' : '#ffffff', + icon: path.join(__dirname, '../assets/icon.png'), + show: false, // Don't show until ready + }); + + // Load the app + const startUrl = isDev + ? 'http://localhost:8081' + : `file://${path.join(__dirname, '../dist/index.html')}`; + + mainWindow.loadURL(startUrl); + + // Show window when ready + mainWindow.once('ready-to-show', () => { + mainWindow.show(); + + // Open DevTools in development + if (isDev) { + mainWindow.webContents.openDevTools(); + } + }); + + // Handle window closed + mainWindow.on('closed', () => { + mainWindow = null; + }); + + // Handle external links + mainWindow.webContents.setWindowOpenHandler(({ url }) => { + require('electron').shell.openExternal(url); + return { action: 'deny' }; + }); +} + +// Build application menu +function createMenu() { + const isMac = process.platform === 'darwin'; + + const template = [ + // App Menu (macOS only) + ...(isMac ? [{ + label: app.name, + submenu: [ + { role: 'about' }, + { type: 'separator' }, + { role: 'services' }, + { type: 'separator' }, + { role: 'hide' }, + { role: 'hideOthers' }, + { role: 'unhide' }, + { type: 'separator' }, + { role: 'quit' }, + ], + }] : []), + // File Menu + { + label: 'File', + submenu: [ + isMac ? { role: 'close' } : { role: 'quit' }, + ], + }, + // Edit Menu + { + label: 'Edit', + submenu: [ + { role: 'undo' }, + { role: 'redo' }, + { type: 'separator' }, + { role: 'cut' }, + { role: 'copy' }, + { role: 'paste' }, + ...(isMac ? [ + { role: 'pasteAndMatchStyle' }, + { role: 'delete' }, + { role: 'selectAll' }, + ] : [ + { role: 'delete' }, + { type: 'separator' }, + { role: 'selectAll' }, + ]), + ], + }, + // View Menu + { + label: 'View', + submenu: [ + { role: 'reload' }, + { role: 'forceReload' }, + { role: 'toggleDevTools' }, + { type: 'separator' }, + { role: 'resetZoom' }, + { role: 'zoomIn' }, + { role: 'zoomOut' }, + { type: 'separator' }, + { role: 'togglefullscreen' }, + ], + }, + // Window Menu + { + label: 'Window', + submenu: [ + { role: 'minimize' }, + { role: 'zoom' }, + ...(isMac ? [ + { type: 'separator' }, + { role: 'front' }, + { type: 'separator' }, + { role: 'window' }, + ] : [ + { role: 'close' }, + ]), + ], + }, + ]; + + const menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); +} + +// IPC Handlers for notifications +ipcMain.handle('show-notification', async (event, { title, body, data }) => { + if (!Notification.isSupported()) { + console.warn('Notifications are not supported on this system'); + return false; + } + + const notification = new Notification({ + title: title || 'Resgrid Unit', + body: body || '', + silent: false, + icon: path.join(__dirname, '../assets/icon.png'), + }); + + notification.on('click', () => { + // Focus the window + if (mainWindow) { + if (mainWindow.isMinimized()) { + mainWindow.restore(); + } + mainWindow.focus(); + // Send notification data to renderer + mainWindow.webContents.send('notification-clicked', data); + } + }); + + notification.show(); + return true; +}); + +// Handle getting platform info +ipcMain.handle('get-platform', () => { + return process.platform; +}); + +// Handle app ready +app.whenReady().then(() => { + createMenu(); + createWindow(); + + // On macOS, re-create window when dock icon is clicked + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); +}); + +// Quit when all windows are closed, except on macOS +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +// Handle deep links (for future use) +app.on('open-url', (event, url) => { + event.preventDefault(); + // Handle the URL + console.log('Deep link received:', url); +}); diff --git a/electron/preload.js b/electron/preload.js new file mode 100644 index 00000000..2e7e561e --- /dev/null +++ b/electron/preload.js @@ -0,0 +1,37 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +// Expose protected methods that allow the renderer process to use +// the ipcRenderer without exposing the entire object +contextBridge.exposeInMainWorld('electronAPI', { + // Platform information + platform: process.platform, + isElectron: true, + + // Notification methods + showNotification: (options) => ipcRenderer.invoke('show-notification', options), + onNotificationClicked: (callback) => { + ipcRenderer.on('notification-clicked', (event, data) => callback(data)); + // Return a cleanup function + return () => { + ipcRenderer.removeAllListeners('notification-clicked'); + }; + }, + + // Platform queries + getPlatform: () => ipcRenderer.invoke('get-platform'), + + // Window controls (for custom title bar if needed) + minimizeWindow: () => ipcRenderer.send('minimize-window'), + maximizeWindow: () => ipcRenderer.send('maximize-window'), + closeWindow: () => ipcRenderer.send('close-window'), + + // Version info + versions: { + node: process.versions.node, + chrome: process.versions.chrome, + electron: process.versions.electron, + }, +}); + +// Log that preload script has been loaded +console.log('Electron preload script loaded'); diff --git a/expo-env.d.ts b/expo-env.d.ts index bf3c1693..5411fdde 100644 --- a/expo-env.d.ts +++ b/expo-env.d.ts @@ -1,3 +1,3 @@ /// -// NOTE: This file should not be edited and should be in your git ignore +// NOTE: This file should not be edited and should be in your git ignore \ No newline at end of file diff --git a/metro.config.js b/metro.config.js index 2a54ea80..da5c1bbd 100644 --- a/metro.config.js +++ b/metro.config.js @@ -2,7 +2,7 @@ const { getSentryExpoConfig } = require('@sentry/react-native/metro'); //const { getDefaultConfig } = require('expo/metro-config'); -//const path = require('path'); +const path = require('path'); const { withNativeWind } = require('nativewind/metro'); const config = getSentryExpoConfig(__dirname, { @@ -20,4 +20,38 @@ const config = getSentryExpoConfig(__dirname, { // '@assets': path.resolve(__dirname, 'assets'), //}; +// Web-specific module resolution - redirect native modules to web shims +const originalResolveRequest = config.resolver.resolveRequest; +config.resolver.resolveRequest = (context, moduleName, platform) => { + if (platform === 'web') { + // Native modules that need web shims + const nativeModules = [ + '@notifee/react-native', + '@react-native-firebase/messaging', + '@react-native-firebase/app', + 'react-native-callkeep', + 'react-native-ble-manager', + '@livekit/react-native', + '@livekit/react-native-webrtc', + '@livekit/react-native-expo-plugin', + ]; + + if (nativeModules.includes(moduleName)) { + return { + filePath: path.resolve(__dirname, 'src/lib/native-module-shims.web.ts'), + type: 'sourceFile', + }; + } + } + + // Use the original resolver for everything else + if (originalResolveRequest) { + return originalResolveRequest(context, moduleName, platform); + } + + // Default resolution + return context.resolveRequest(context, moduleName, platform); +}; + module.exports = withNativeWind(config, { input: './global.css', inlineRem: 16 }); + diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 00000000..c90cac95 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,62 @@ +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + + server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + + # Static assets with cache + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # SPA fallback - serve index.html for client-side routing + location / { + try_files $uri $uri/ /index.html; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} diff --git a/package.json b/package.json index 4b6817b1..3e65eecc 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,15 @@ "test:ci": "yarn run test --coverage", "test:watch": "yarn run test --watch", "install-maestro": "curl -Ls 'https://get.maestro.mobile.dev' | bash", - "e2e-test": "maestro test .maestro/ -e APP_ID=com.obytes.development" + "e2e-test": "maestro test .maestro/ -e APP_ID=com.obytes.development", + "web:build": "cross-env EXPO_NO_DOTENV=1 expo export --platform web", + "web:staging": "cross-env APP_ENV=staging yarn run web", + "web:production": "cross-env APP_ENV=production yarn run web", + "electron:dev": "concurrently \"yarn web\" \"wait-on http://localhost:8081 && electron .\"", + "electron:build": "yarn web:build && electron-builder --config electron-builder.config.js", + "electron:build:mac": "yarn web:build && electron-builder --config electron-builder.config.js --mac", + "electron:build:win": "yarn web:build && electron-builder --config electron-builder.config.js --win", + "electron:build:linux": "yarn web:build && electron-builder --config electron-builder.config.js --linux" }, "dependencies": { "@config-plugins/react-native-callkeep": "^11.0.0", @@ -98,6 +106,7 @@ "@sentry/react-native": "~6.14.0", "@shopify/flash-list": "1.7.6", "@tanstack/react-query": "~5.52.1", + "@types/mapbox-gl": "3.4.1", "app-icon-badge": "^0.1.2", "axios": "~1.12.0", "babel-plugin-module-resolver": "^5.0.2", @@ -137,6 +146,7 @@ "lodash": "^4.17.21", "lodash.memoize": "~4.1.2", "lucide-react-native": "~0.475.0", + "mapbox-gl": "3.18.1", "moti": "~0.29.0", "nativewind": "~4.1.21", "react": "19.0.0", @@ -182,8 +192,11 @@ "@typescript-eslint/eslint-plugin": "~5.62.0", "@typescript-eslint/parser": "~5.62.0", "babel-jest": "~30.0.0", + "concurrently": "9.2.1", "cross-env": "~7.0.3", "dotenv": "~16.4.5", + "electron": "40.0.0", + "electron-builder": "26.4.0", "eslint": "~8.57.0", "eslint-config-expo": "~7.1.2", "eslint-config-prettier": "~9.1.0", @@ -210,7 +223,8 @@ "tailwindcss": "3.4.4", "ts-jest": "~29.1.2", "ts-node": "~10.9.2", - "typescript": "~5.8.3" + "typescript": "~5.8.3", + "wait-on": "9.0.3" }, "repository": { "type": "git", diff --git a/public/service-worker.js b/public/service-worker.js new file mode 100644 index 00000000..8c5ea0b2 --- /dev/null +++ b/public/service-worker.js @@ -0,0 +1,122 @@ +/** + * Service Worker for Resgrid Unit Web Push Notifications + * This file handles background push notifications when the app is not in focus + */ + +// Cache name for offline support (optional) +const CACHE_NAME = 'resgrid-unit-v1'; + +// Handle push events +self.addEventListener('push', function (event) { + console.log('[Service Worker] Push received:', event); + + let data = {}; + if (event.data) { + try { + data = event.data.json(); + } catch (e) { + data = { + title: 'New Notification', + body: event.data.text(), + }; + } + } + + const title = data.title || 'Resgrid Unit'; + const options = { + body: data.body || data.message || 'You have a new notification', + icon: '/icon-192.png', + badge: '/badge-72.png', + vibrate: [100, 50, 100], + data: data, + requireInteraction: true, + tag: data.eventCode || `notification-${Date.now()}`, + actions: [ + { + action: 'open', + title: 'Open', + }, + { + action: 'dismiss', + title: 'Dismiss', + }, + ], + }; + + event.waitUntil(self.registration.showNotification(title, options)); +}); + +// Handle notification click +self.addEventListener('notificationclick', function (event) { + console.log('[Service Worker] Notification clicked:', event); + + event.notification.close(); + + const action = event.action; + const data = event.notification.data; + + // Handle dismiss action + if (action === 'dismiss') { + return; + } + + // Open the app and send message to main thread + event.waitUntil( + clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (clientList) { + // Try to focus an existing window + for (const client of clientList) { + if ('focus' in client) { + client.focus(); + client.postMessage({ + type: 'NOTIFICATION_CLICK', + data: data, + }); + return; + } + } + + // Open new window if no existing window found + if (clients.openWindow) { + return clients.openWindow('/').then(function (client) { + // Send message after a short delay to ensure the app is ready + setTimeout(function () { + if (client) { + client.postMessage({ + type: 'NOTIFICATION_CLICK', + data: data, + }); + } + }, 1000); + }); + } + }) + ); +}); + +// Handle notification close +self.addEventListener('notificationclose', function (event) { + console.log('[Service Worker] Notification closed:', event); +}); + +// Handle service worker installation +self.addEventListener('install', function (event) { + console.log('[Service Worker] Installing...'); + // Skip waiting to activate immediately + self.skipWaiting(); +}); + +// Handle service worker activation +self.addEventListener('activate', function (event) { + console.log('[Service Worker] Activating...'); + // Take control of all pages immediately + event.waitUntil(clients.claim()); +}); + +// Handle messages from the main thread +self.addEventListener('message', function (event) { + console.log('[Service Worker] Message received:', event.data); + + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); diff --git a/src/app/(app)/index.tsx b/src/app/(app)/index.tsx index 0d9589ef..d3940366 100644 --- a/src/app/(app)/index.tsx +++ b/src/app/(app)/index.tsx @@ -1,4 +1,4 @@ -import Mapbox from '@rnmapbox/maps'; +import Mapbox from '@/components/maps/mapbox'; import { Stack, useFocusEffect } from 'expo-router'; import { NavigationIcon } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx index fd53a52f..e3db628a 100644 --- a/src/app/_layout.tsx +++ b/src/app/_layout.tsx @@ -4,7 +4,6 @@ import '../lib/i18n'; import { Env } from '@env'; import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; -import { registerGlobals } from '@livekit/react-native'; import notifee from '@notifee/react-native'; import { createNavigationContainerRef, DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; import * as Sentry from '@sentry/react-native'; @@ -12,7 +11,7 @@ import { isRunningInExpoGo } from 'expo'; import { Stack, useNavigationContainerRef } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; import React, { useEffect } from 'react'; -import { LogBox, useColorScheme } from 'react-native'; +import { LogBox, Platform, useColorScheme } from 'react-native'; import FlashMessage from 'react-native-flash-message'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { KeyboardProvider } from 'react-native-keyboard-controller'; @@ -66,7 +65,12 @@ Sentry.init({ }, }); -registerGlobals(); +// Only register LiveKit globals on native platforms +// Web/Electron uses livekit-client which handles WebRTC natively +if (Platform.OS !== 'web') { + const { registerGlobals } = require('@livekit/react-native'); + registerGlobals(); +} // Load the selected theme from storage and apply it loadSelectedTheme(); diff --git a/src/components/maps/full-screen-location-picker.tsx b/src/components/maps/full-screen-location-picker.tsx index f0e5604b..d3ee83ca 100644 --- a/src/components/maps/full-screen-location-picker.tsx +++ b/src/components/maps/full-screen-location-picker.tsx @@ -1,4 +1,4 @@ -import Mapbox from '@rnmapbox/maps'; +import Mapbox from '@/components/maps/mapbox'; import * as Location from 'expo-location'; import { LocateIcon, MapPinIcon, XIcon } from 'lucide-react-native'; import React, { useEffect, useRef, useState } from 'react'; diff --git a/src/components/maps/location-picker.tsx b/src/components/maps/location-picker.tsx index ac9cd8ba..2a511334 100644 --- a/src/components/maps/location-picker.tsx +++ b/src/components/maps/location-picker.tsx @@ -1,4 +1,4 @@ -import Mapbox from '@rnmapbox/maps'; +import Mapbox from '@/components/maps/mapbox'; import * as Location from 'expo-location'; import { LocateIcon, MapPinIcon } from 'lucide-react-native'; import React, { useEffect, useRef, useState } from 'react'; diff --git a/src/components/maps/map-pins.tsx b/src/components/maps/map-pins.tsx index 64fd2521..d3f58cc9 100644 --- a/src/components/maps/map-pins.tsx +++ b/src/components/maps/map-pins.tsx @@ -1,4 +1,4 @@ -import Mapbox from '@rnmapbox/maps'; +import Mapbox from '@/components/maps/mapbox'; import React from 'react'; import { type MAP_ICONS } from '@/constants/map-icons'; diff --git a/src/components/maps/map-view.native.tsx b/src/components/maps/map-view.native.tsx new file mode 100644 index 00000000..5154b785 --- /dev/null +++ b/src/components/maps/map-view.native.tsx @@ -0,0 +1,31 @@ +/** + * Native implementation of map components using @rnmapbox/maps + * This file is used on iOS and Android platforms + */ +import Mapbox from '@rnmapbox/maps'; + +// Re-export all Mapbox components for native platforms +export const MapView = Mapbox.MapView; +export const Camera = Mapbox.Camera; +export const PointAnnotation = Mapbox.PointAnnotation; +export const UserLocation = Mapbox.UserLocation; +export const MarkerView = Mapbox.MarkerView; +export const ShapeSource = Mapbox.ShapeSource; +export const SymbolLayer = Mapbox.SymbolLayer; +export const CircleLayer = Mapbox.CircleLayer; +export const LineLayer = Mapbox.LineLayer; +export const FillLayer = Mapbox.FillLayer; +export const Images = Mapbox.Images; +export const Callout = Mapbox.Callout; + +// Export the setAccessToken method +export const setAccessToken = Mapbox.setAccessToken; + +// Export StyleURL constants +export const StyleURL = Mapbox.StyleURL; + +// Export UserTrackingMode +export const UserTrackingMode = Mapbox.UserTrackingMode; + +// Default export for backwards compatibility +export default Mapbox; diff --git a/src/components/maps/map-view.web.tsx b/src/components/maps/map-view.web.tsx new file mode 100644 index 00000000..6096a82e --- /dev/null +++ b/src/components/maps/map-view.web.tsx @@ -0,0 +1,386 @@ +/** + * Web implementation of map components using mapbox-gl + * This file is used on web and Electron platforms + */ +import mapboxgl from 'mapbox-gl'; +import 'mapbox-gl/dist/mapbox-gl.css'; +import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; + +import { Env } from '@/lib/env'; + +// Set the access token globally +mapboxgl.accessToken = Env.UNIT_MAPBOX_PUBKEY; + +// Context to share map instance with child components +export const MapContext = React.createContext(null); + +// StyleURL constants matching native Mapbox SDK +export const StyleURL = { + Street: 'mapbox://styles/mapbox/streets-v12', + Dark: 'mapbox://styles/mapbox/dark-v11', + Light: 'mapbox://styles/mapbox/light-v11', + Outdoors: 'mapbox://styles/mapbox/outdoors-v12', + Satellite: 'mapbox://styles/mapbox/satellite-v9', + SatelliteStreet: 'mapbox://styles/mapbox/satellite-streets-v12', +}; + +// UserTrackingMode enum matching native SDK +export const UserTrackingMode = { + Follow: 'follow', + FollowWithHeading: 'followWithHeading', + FollowWithCourse: 'followWithCourse', +}; + +// Access token setter for compatibility +export const setAccessToken = (token: string) => { + mapboxgl.accessToken = token; +}; + +// MapView Props interface +interface MapViewProps { + style?: React.CSSProperties; + styleURL?: string; + onDidFinishLoadingMap?: () => void; + onCameraChanged?: (event: { properties: { isUserInteraction: boolean } }) => void; + children?: React.ReactNode; + testID?: string; + logoEnabled?: boolean; + attributionEnabled?: boolean; + compassEnabled?: boolean; + zoomEnabled?: boolean; + rotateEnabled?: boolean; + scrollEnabled?: boolean; + pitchEnabled?: boolean; +} + +// MapView component +export const MapView = forwardRef(({ + style, + styleURL = StyleURL.Street, + onDidFinishLoadingMap, + onCameraChanged, + children, + testID, + logoEnabled = false, + attributionEnabled = false, + compassEnabled = true, + zoomEnabled = true, + rotateEnabled = true, + scrollEnabled = true, + pitchEnabled = true, +}, ref) => { + const mapContainer = useRef(null); + const map = useRef(null); + const [isLoaded, setIsLoaded] = useState(false); + + useImperativeHandle(ref, () => ({ + getMap: () => map.current, + })); + + useEffect(() => { + if (map.current || !mapContainer.current) return; + + const newMap = new mapboxgl.Map({ + container: mapContainer.current, + style: styleURL, + center: [-98.5795, 39.8283], // Default US center + zoom: 4, + attributionControl: attributionEnabled, + logoPosition: logoEnabled ? 'bottom-left' : undefined, + dragRotate: rotateEnabled, + scrollZoom: zoomEnabled, + dragPan: scrollEnabled, + pitchWithRotate: pitchEnabled, + }); + + if (!logoEnabled) { + // Hide logo via CSS if not enabled + newMap.on('load', () => { + const logoEl = mapContainer.current?.querySelector('.mapboxgl-ctrl-logo'); + if (logoEl) { + (logoEl as HTMLElement).style.display = 'none'; + } + }); + } + + if (compassEnabled) { + newMap.addControl(new mapboxgl.NavigationControl({ showCompass: true, showZoom: false }), 'top-right'); + } + + newMap.on('load', () => { + setIsLoaded(true); + onDidFinishLoadingMap?.(); + }); + + newMap.on('moveend', () => { + onCameraChanged?.({ properties: { isUserInteraction: true } }); + }); + + map.current = newMap; + + return () => { + map.current?.remove(); + map.current = null; + }; + }, []); + + // Update style when it changes + useEffect(() => { + if (map.current && styleURL) { + map.current.setStyle(styleURL); + } + }, [styleURL]); + + return ( +
+ {isLoaded && ( + + {children} + + )} +
+ ); +}); + +MapView.displayName = 'MapView'; + +// Camera Props interface +interface CameraProps { + ref?: React.Ref; + centerCoordinate?: [number, number]; + zoomLevel?: number; + heading?: number; + pitch?: number; + animationDuration?: number; + animationMode?: string; + followUserLocation?: boolean; + followUserMode?: string; + followZoomLevel?: number; + followPitch?: number; +} + +// Camera component +export const Camera = forwardRef(({ + centerCoordinate, + zoomLevel, + heading, + pitch, + animationDuration = 1000, + followUserLocation, + followZoomLevel, +}, ref) => { + const map = React.useContext(MapContext); + const geolocateControl = useRef(null); + + useImperativeHandle(ref, () => ({ + setCamera: (options: { + centerCoordinate?: [number, number]; + zoomLevel?: number; + heading?: number; + pitch?: number; + animationDuration?: number; + }) => { + if (!map) return; + + map.easeTo({ + center: options.centerCoordinate, + zoom: options.zoomLevel, + bearing: options.heading, + pitch: options.pitch, + duration: options.animationDuration || 1000, + }); + }, + flyTo: (options: any) => { + if (!map) return; + map.flyTo(options); + }, + })); + + useEffect(() => { + if (!map) return; + + if (centerCoordinate) { + map.easeTo({ + center: centerCoordinate, + zoom: zoomLevel, + bearing: heading, + pitch: pitch, + duration: animationDuration, + }); + } + }, [map, centerCoordinate, zoomLevel, heading, pitch, animationDuration]); + + useEffect(() => { + if (!map || !followUserLocation) return; + + // Add geolocate control for following user + if (!geolocateControl.current) { + geolocateControl.current = new mapboxgl.GeolocateControl({ + positionOptions: { + enableHighAccuracy: true, + }, + trackUserLocation: true, + showUserHeading: true, + }); + map.addControl(geolocateControl.current); + } + + // Trigger tracking after control is added + setTimeout(() => { + geolocateControl.current?.trigger(); + }, 100); + + return () => { + if (geolocateControl.current) { + map.removeControl(geolocateControl.current); + geolocateControl.current = null; + } + }; + }, [map, followUserLocation, followZoomLevel]); + + return null; +}); + +Camera.displayName = 'Camera'; + +// PointAnnotation Props interface +interface PointAnnotationProps { + id: string; + coordinate: [number, number]; + title?: string; + children?: React.ReactNode; + anchor?: { x: number; y: number }; + onSelected?: () => void; +} + +// PointAnnotation component +export const PointAnnotation: React.FC = ({ + id, + coordinate, + title, + children, + anchor = { x: 0.5, y: 0.5 }, + onSelected, +}) => { + const map = React.useContext(MapContext); + const markerRef = useRef(null); + const containerRef = useRef(null); + + useEffect(() => { + if (!map || !coordinate) return; + + // Create a container for React children + const container = document.createElement('div'); + container.style.cursor = 'pointer'; + containerRef.current = container; + + // If there are children, render them into the container + if (children) { + // Simple case - just show a marker + container.innerHTML = '
'; + } + + markerRef.current = new mapboxgl.Marker({ + element: container, + anchor: 'center', + }) + .setLngLat(coordinate) + .addTo(map); + + if (title) { + markerRef.current.setPopup(new mapboxgl.Popup().setText(title)); + } + + if (onSelected) { + container.addEventListener('click', onSelected); + } + + return () => { + markerRef.current?.remove(); + }; + }, [map, coordinate, id]); + + // Update position when coordinate changes + useEffect(() => { + if (markerRef.current && coordinate) { + markerRef.current.setLngLat(coordinate); + } + }, [coordinate]); + + return null; +}; + +// UserLocation Props interface +interface UserLocationProps { + visible?: boolean; + showsUserHeadingIndicator?: boolean; +} + +// UserLocation component - handled by GeolocateControl in Camera +export const UserLocation: React.FC = ({ + visible = true, + showsUserHeadingIndicator = true, +}) => { + const map = React.useContext(MapContext); + + useEffect(() => { + if (!map || !visible) return; + + const geolocate = new mapboxgl.GeolocateControl({ + positionOptions: { + enableHighAccuracy: true, + }, + trackUserLocation: true, + showUserHeading: showsUserHeadingIndicator, + }); + + map.addControl(geolocate); + + // Auto-trigger to show user location + map.on('load', () => { + geolocate.trigger(); + }); + + return () => { + map.removeControl(geolocate); + }; + }, [map, visible, showsUserHeadingIndicator]); + + return null; +}; + +// MarkerView component (simplified for web) +export const MarkerView: React.FC<{ + coordinate: [number, number]; + children?: React.ReactNode; +}> = ({ coordinate, children }) => { + return {children}; +}; + +// Placeholder components for compatibility +export const ShapeSource: React.FC = ({ children }) => <>{children}; +export const SymbolLayer: React.FC = () => null; +export const CircleLayer: React.FC = () => null; +export const LineLayer: React.FC = () => null; +export const FillLayer: React.FC = () => null; +export const Images: React.FC = () => null; +export const Callout: React.FC = ({ children }) => <>{children}; + +// Default export matching native structure +export default { + MapView, + Camera, + PointAnnotation, + UserLocation, + MarkerView, + ShapeSource, + SymbolLayer, + CircleLayer, + LineLayer, + FillLayer, + Images, + Callout, + StyleURL, + UserTrackingMode, + setAccessToken, +}; diff --git a/src/components/maps/mapbox.ts b/src/components/maps/mapbox.ts new file mode 100644 index 00000000..7916c951 --- /dev/null +++ b/src/components/maps/mapbox.ts @@ -0,0 +1,65 @@ +/** + * Platform-aware map components + * Automatically selects native (@rnmapbox/maps) or web (mapbox-gl) implementation + */ +import { Platform } from 'react-native'; + +// Import the platform-specific implementation +// Metro bundler will resolve to the correct file based on platform +const MapboxImpl = Platform.OS === 'web' + ? require('./map-view.web').default + : require('./map-view.native').default; + +// Re-export all components +export const MapView = MapboxImpl.MapView || MapboxImpl; +export const Camera = Platform.OS === 'web' + ? require('./map-view.web').Camera + : require('./map-view.native').Camera; +export const PointAnnotation = Platform.OS === 'web' + ? require('./map-view.web').PointAnnotation + : require('./map-view.native').PointAnnotation; +export const UserLocation = Platform.OS === 'web' + ? require('./map-view.web').UserLocation + : require('./map-view.native').UserLocation; +export const MarkerView = Platform.OS === 'web' + ? require('./map-view.web').MarkerView + : require('./map-view.native').MarkerView; +export const ShapeSource = Platform.OS === 'web' + ? require('./map-view.web').ShapeSource + : require('./map-view.native').ShapeSource; +export const SymbolLayer = Platform.OS === 'web' + ? require('./map-view.web').SymbolLayer + : require('./map-view.native').SymbolLayer; +export const CircleLayer = Platform.OS === 'web' + ? require('./map-view.web').CircleLayer + : require('./map-view.native').CircleLayer; +export const LineLayer = Platform.OS === 'web' + ? require('./map-view.web').LineLayer + : require('./map-view.native').LineLayer; +export const FillLayer = Platform.OS === 'web' + ? require('./map-view.web').FillLayer + : require('./map-view.native').FillLayer; +export const Images = Platform.OS === 'web' + ? require('./map-view.web').Images + : require('./map-view.native').Images; +export const Callout = Platform.OS === 'web' + ? require('./map-view.web').Callout + : require('./map-view.native').Callout; + +// Export style URL constants +export const StyleURL = Platform.OS === 'web' + ? require('./map-view.web').StyleURL + : require('./map-view.native').StyleURL; + +// Export UserTrackingMode +export const UserTrackingMode = Platform.OS === 'web' + ? require('./map-view.web').UserTrackingMode + : require('./map-view.native').UserTrackingMode; + +// Export setAccessToken +export const setAccessToken = Platform.OS === 'web' + ? require('./map-view.web').setAccessToken + : require('./map-view.native').setAccessToken; + +// Default export matching Mapbox structure +export default MapboxImpl; diff --git a/src/components/maps/pin-marker.tsx b/src/components/maps/pin-marker.tsx index 17a05b52..e92ec2ad 100644 --- a/src/components/maps/pin-marker.tsx +++ b/src/components/maps/pin-marker.tsx @@ -1,4 +1,4 @@ -import type Mapbox from '@rnmapbox/maps'; +import type Mapbox from '@/components/maps/mapbox'; import { useColorScheme } from 'nativewind'; import React from 'react'; import { Image, StyleSheet, Text, TouchableOpacity } from 'react-native'; diff --git a/src/components/maps/static-map.tsx b/src/components/maps/static-map.tsx index e3b19bd9..6ff869e2 100644 --- a/src/components/maps/static-map.tsx +++ b/src/components/maps/static-map.tsx @@ -1,4 +1,4 @@ -import Mapbox from '@rnmapbox/maps'; +import Mapbox from '@/components/maps/mapbox'; import { useColorScheme } from 'nativewind'; import React from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/src/lib/env.js b/src/lib/env.js index 2c3559bc..7209e39b 100644 --- a/src/lib/env.js +++ b/src/lib/env.js @@ -1,13 +1,42 @@ /* - * This file should not be modified; use `env.js` in the project root to add your client environment variables. + * This file should not be modified directly; use `env.js` in the project root to add your client environment variables. * If you import `Env` from `@env`, this is the file that will be loaded. * You can only access the client environment variables here. - * NOTE: We use js file so we can load the client env types + * + * For Docker deployments, environment variables are injected at runtime via window.__ENV__ + * For native/dev builds, environment variables come from expo-constants */ import Constants from 'expo-constants'; +import { Platform } from 'react-native'; + /** - * @type {typeof import('../../env.js').ClientEnv} + * Check if we're running in a Docker container (web with runtime env injection) + * @returns {boolean} */ -//@ts-ignore // Don't worry about TypeScript here; we know we're passing the correct environment variables to `extra` in `app.config.ts`. -export const Env = Constants.expoConfig?.extra ?? {}; +const isDockerRuntime = () => { + if (Platform.OS !== 'web') { + return false; + } + // Check if window.__ENV__ exists (injected by docker-entrypoint.sh) + return typeof window !== 'undefined' && window.__ENV__ !== undefined; +}; + +/** + * Get environment configuration + * - For Docker web deployments: uses window.__ENV__ (runtime injection) + * - For native/dev builds: uses expo-constants (build-time) + * + * @type {typeof import('../../env.js').ClientEnv} + */ +const getEnvConfig = () => { + if (isDockerRuntime()) { + // Docker runtime - use injected environment variables + return window.__ENV__; + } + // Native/dev build - use expo-constants + //@ts-ignore // Don't worry about TypeScript here; we know we're passing the correct environment variables to `extra` in `app.config.ts` + return Constants.expoConfig?.extra ?? {}; +}; + +export const Env = getEnvConfig(); diff --git a/src/lib/native-module-shims.web.ts b/src/lib/native-module-shims.web.ts new file mode 100644 index 00000000..ed62d434 --- /dev/null +++ b/src/lib/native-module-shims.web.ts @@ -0,0 +1,162 @@ +/** + * Web-safe shims for native modules that don't work on web platform. + * These provide no-op implementations to prevent import errors. + */ + +// Notifee shim +export const AndroidImportance = { + DEFAULT: 'default', + HIGH: 'high', + LOW: 'low', + MIN: 'min', + NONE: 'none', + UNSPECIFIED: 'unspecified', +}; + +export const AndroidVisibility = { + PRIVATE: 'private', + PUBLIC: 'public', + SECRET: 'secret', +}; + +export const AuthorizationStatus = { + NOT_DETERMINED: -1, + DENIED: 0, + AUTHORIZED: 1, + PROVISIONAL: 2, +}; + +export const EventType = { + DISMISSED: 0, + PRESS: 1, + ACTION_PRESS: 2, + DELIVERED: 3, + APP_BLOCKED: 4, + CHANNEL_BLOCKED: 5, + CHANNEL_GROUP_BLOCKED: 6, + TRIGGER_NOTIFICATION_CREATED: 7, +}; + +export const notifee = { + createChannel: async () => 'web-channel', + displayNotification: async () => 'web-notification', + setBadgeCount: async () => {}, + getBadgeCount: async () => 0, + incrementBadgeCount: async () => {}, + decrementBadgeCount: async () => {}, + requestPermission: async () => ({ authorizationStatus: AuthorizationStatus.AUTHORIZED }), + getPermissionSettings: async () => ({ authorizationStatus: AuthorizationStatus.AUTHORIZED }), + onForegroundEvent: () => () => {}, + onBackgroundEvent: () => {}, + registerForegroundService: async () => {}, + stopForegroundService: async () => {}, + cancelNotification: async () => {}, + cancelAllNotifications: async () => {}, + getInitialNotification: async () => null, + getDisplayedNotifications: async () => [], + getTriggerNotifications: async () => [], + setNotificationCategories: async () => {}, +}; + +export default notifee; + +// Firebase Messaging shim +export const messaging = () => ({ + getToken: async () => 'web-token', + deleteToken: async () => {}, + hasPermission: async () => 1, + requestPermission: async () => 1, + onMessage: () => () => {}, + onNotificationOpenedApp: () => () => {}, + getInitialNotification: async () => null, + setBackgroundMessageHandler: () => {}, + subscribeToTopic: async () => {}, + unsubscribeFromTopic: async () => {}, +}); + +messaging.AuthorizationStatus = { + NOT_DETERMINED: -1, + DENIED: 0, + AUTHORIZED: 1, + PROVISIONAL: 2, +}; + +// Firebase App shim +export const firebaseApp = { + initializeApp: () => ({}), + getApp: () => ({}), + getApps: () => [], +}; + +// CallKeep shim +export const callKeepService = { + startCall: async () => 'web-call-uuid', + endCall: async () => {}, + setMuteStateCallback: () => {}, + initialize: async () => {}, + cleanup: () => {}, +}; + +// RNCallKeep shim (for direct imports) +export const RNCallKeep = { + setup: async () => {}, + hasDefaultPhoneAccount: async () => true, + answerIncomingCall: () => {}, + rejectCall: () => {}, + endCall: () => {}, + endAllCalls: () => {}, + setMutedCall: () => {}, + checkIfBusy: async () => false, + checkSpeaker: async () => false, + setAvailable: () => {}, + setCurrentCallActive: () => {}, + displayIncomingCall: () => {}, + startCall: () => {}, + updateDisplay: () => {}, + reportConnectedOutgoingCallWithUUID: () => {}, + reportEndCallWithUUID: () => {}, + addEventListener: () => ({ remove: () => {} }), + removeEventListener: () => {}, + getAudioRoutes: async () => [], + setAudioRoute: async () => {}, +}; + +// BLE Manager shim +export const BleManager = { + start: async () => {}, + scan: () => {}, + stopScan: () => {}, + connect: async () => {}, + disconnect: async () => {}, + read: async () => null, + write: async () => {}, + retrieveServices: async () => ({}), + startNotification: async () => {}, + stopNotification: async () => {}, + isPeripheralConnected: async () => false, + getConnectedPeripherals: async () => [], + getBondedPeripherals: async () => [], + createBond: async () => {}, + removeBond: async () => {}, + enableBluetooth: async () => {}, + checkState: () => {}, +}; + +// LiveKit React Native shim +export const registerGlobals = () => { + // No-op on web - livekit-client handles WebRTC natively +}; + +export const RTCAudioSession = { + configure: async () => {}, + setCategory: async () => {}, + setMode: async () => {}, + getActiveAudioSession: () => null, + setActive: async () => {}, +}; + +// Expo modules that may have issues on web +export const expoAudioShim = { + getRecordingPermissionsAsync: async () => ({ granted: true, status: 'granted' }), + requestRecordingPermissionsAsync: async () => ({ granted: true, status: 'granted' }), +}; diff --git a/src/lib/platform.ts b/src/lib/platform.ts new file mode 100644 index 00000000..6555f0b6 --- /dev/null +++ b/src/lib/platform.ts @@ -0,0 +1,64 @@ +import { Platform } from 'react-native'; + +/** + * Platform detection utilities for cross-platform development + */ + +// Check if running in a web browser +export const isWeb = Platform.OS === 'web'; + +// Check if running in Electron (desktop app wrapped around web) +export const isElectron = + typeof window !== 'undefined' && + window.process?.type === 'renderer'; + +// Check if running on a desktop platform (Electron or native desktop) +export const isDesktop = + isElectron || + Platform.OS === 'macos' || + Platform.OS === 'windows'; + +// Check if running on native mobile platforms +export const isNative = Platform.OS === 'ios' || Platform.OS === 'android'; + +// Check if running on iOS +export const isIOS = Platform.OS === 'ios'; + +// Check if running on Android +export const isAndroid = Platform.OS === 'android'; + +// Get the current platform name +export const platformName = (): string => { + if (isElectron) { + // Detect Electron's underlying OS + const electronPlatform = (window as any).electronAPI?.platform; + if (electronPlatform === 'darwin') return 'macOS (Electron)'; + if (electronPlatform === 'win32') return 'Windows (Electron)'; + if (electronPlatform === 'linux') return 'Linux (Electron)'; + return 'Electron'; + } + + switch (Platform.OS) { + case 'ios': return 'iOS'; + case 'android': return 'Android'; + case 'web': return 'Web'; + case 'macos': return 'macOS'; + case 'windows': return 'Windows'; + default: return Platform.OS; + } +}; + +// Augment Window interface for Electron API +declare global { + interface Window { + process?: { + type?: string; + }; + electronAPI?: { + showNotification: (options: { title: string; body: string; data: any }) => Promise; + onNotificationClicked: (callback: (data: any) => void) => void; + platform: string; + isElectron: boolean; + }; + } +} diff --git a/src/services/push-notification.electron.ts b/src/services/push-notification.electron.ts new file mode 100644 index 00000000..77595f45 --- /dev/null +++ b/src/services/push-notification.electron.ts @@ -0,0 +1,163 @@ +/** + * Electron Push Notification Service + * Handles system notifications for Electron desktop applications + */ +import { logger } from '@/lib/logging'; +import { isElectron } from '@/lib/platform'; +import { usePushNotificationModalStore } from '@/stores/push-notification/store'; + +// Type declaration for Electron API exposed via preload +declare global { + interface Window { + electronAPI?: { + showNotification: (options: { title: string; body: string; data: any }) => Promise; + onNotificationClicked: (callback: (data: any) => void) => () => void; + platform: string; + isElectron: boolean; + }; + } +} + +class ElectronPushNotificationService { + private static instance: ElectronPushNotificationService; + private isInitialized = false; + private cleanupListener: (() => void) | null = null; + + static getInstance(): ElectronPushNotificationService { + if (!ElectronPushNotificationService.instance) { + ElectronPushNotificationService.instance = new ElectronPushNotificationService(); + } + return ElectronPushNotificationService.instance; + } + + /** + * Initialize the Electron notification service + */ + async initialize(): Promise { + if (this.isInitialized) { + return; + } + + if (!isElectron || !window.electronAPI) { + logger.warn({ + message: 'Electron API not available - skipping Electron notification service initialization', + }); + return; + } + + // Listen for notification clicks from the main process + this.cleanupListener = window.electronAPI.onNotificationClicked(this.handleNotificationClick); + + this.isInitialized = true; + logger.info({ + message: 'Electron notification service initialized', + context: { platform: window.electronAPI.platform }, + }); + } + + /** + * Handle notification click events from main process + */ + private handleNotificationClick = (data: any): void => { + logger.info({ + message: 'Electron notification clicked', + context: { data }, + }); + + // Show the notification modal + if (data?.eventCode) { + usePushNotificationModalStore.getState().showNotificationModal({ + eventCode: data.eventCode, + title: data.title, + body: data.body || data.message, + data, + }); + } + }; + + /** + * Show a native system notification + */ + async showNotification(title: string, body: string, data?: any): Promise { + if (!window.electronAPI) { + logger.warn({ + message: 'Cannot show notification: Electron API not available', + }); + return false; + } + + try { + const result = await window.electronAPI.showNotification({ + title, + body, + data: data || {}, + }); + + logger.info({ + message: 'Electron notification shown', + context: { title, success: result }, + }); + + return result; + } catch (error) { + logger.error({ + message: 'Failed to show Electron notification', + context: { error, title }, + }); + return false; + } + } + + /** + * Request notification permission + * Note: Electron has different permission handling than web + * Desktop notifications typically don't require explicit permission + */ + async requestPermission(): Promise<'granted' | 'denied' | 'default'> { + // Electron desktop notifications don't require explicit permission like web + // They use the system's built-in notification system + return 'granted'; + } + + /** + * Subscribe to push notifications + * For Electron, we rely on SignalR for real-time updates + * which then triggers local system notifications + */ + async subscribe(_vapidPublicKey?: string): Promise { + // Electron apps don't use web push subscriptions + // They receive updates via SignalR and show local system notifications + logger.info({ + message: 'Electron apps use SignalR for notifications, not web push subscriptions', + }); + return 'electron-signalr'; + } + + /** + * Unsubscribe from push notifications + */ + async unsubscribe(): Promise { + // No actual subscription to unsubscribe from in Electron + return true; + } + + /** + * Cleanup listeners on unmount + */ + cleanup(): void { + if (this.cleanupListener) { + this.cleanupListener(); + this.cleanupListener = null; + } + this.isInitialized = false; + } + + /** + * Check if Electron notifications are supported + */ + static isSupported(): boolean { + return isElectron && !!window.electronAPI; + } +} + +export const electronPushNotificationService = ElectronPushNotificationService.getInstance(); diff --git a/src/services/push-notification.web.ts b/src/services/push-notification.web.ts new file mode 100644 index 00000000..ace87b48 --- /dev/null +++ b/src/services/push-notification.web.ts @@ -0,0 +1,236 @@ +/** + * Web Push Notification Service + * Handles push notifications for web browsers using the Web Push API + */ +import { logger } from '@/lib/logging'; +import { usePushNotificationModalStore } from '@/stores/push-notification/store'; + +class WebPushNotificationService { + private static instance: WebPushNotificationService; + private registration: ServiceWorkerRegistration | null = null; + private pushSubscription: PushSubscription | null = null; + private isInitialized = false; + + static getInstance(): WebPushNotificationService { + if (!WebPushNotificationService.instance) { + WebPushNotificationService.instance = new WebPushNotificationService(); + } + return WebPushNotificationService.instance; + } + + /** + * Initialize the web push notification service + */ + async initialize(): Promise { + if (this.isInitialized) { + return; + } + + // Check if push notifications are supported + if (!('serviceWorker' in navigator) || !('PushManager' in window)) { + logger.warn({ + message: 'Web Push notifications are not supported in this browser', + }); + return; + } + + try { + // Register service worker + this.registration = await navigator.serviceWorker.register('/service-worker.js'); + logger.info({ + message: 'Service worker registered for push notifications', + context: { scope: this.registration.scope }, + }); + + // Listen for messages from service worker + navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerMessage); + + this.isInitialized = true; + } catch (error) { + logger.error({ + message: 'Failed to register service worker', + context: { error }, + }); + } + } + + /** + * Handle messages from the service worker + */ + private handleServiceWorkerMessage = (event: MessageEvent): void => { + if (event.data?.type === 'NOTIFICATION_CLICK') { + const data = event.data.data; + logger.info({ + message: 'Notification clicked from service worker', + context: { data }, + }); + + // Show the notification modal + if (data.eventCode) { + usePushNotificationModalStore.getState().showNotificationModal({ + eventCode: data.eventCode, + title: data.title, + body: data.body || data.message, + data, + }); + } + } + }; + + /** + * Request permission and subscribe to push notifications + */ + async requestPermission(): Promise { + if (!('Notification' in window)) { + logger.warn({ + message: 'Notifications not supported in this browser', + }); + return 'denied'; + } + + const permission = await Notification.requestPermission(); + logger.info({ + message: 'Notification permission result', + context: { permission }, + }); + + return permission; + } + + /** + * Subscribe to push notifications with VAPID key + */ + async subscribe(vapidPublicKey: string): Promise { + if (!this.registration) { + await this.initialize(); + } + + if (!this.registration) { + logger.error({ + message: 'Cannot subscribe: service worker not registered', + }); + return null; + } + + try { + // Check permission first + const permission = await this.requestPermission(); + if (permission !== 'granted') { + logger.warn({ + message: 'Notification permission not granted', + context: { permission }, + }); + return null; + } + + // Subscribe to push manager + this.pushSubscription = await this.registration.pushManager.subscribe({ + userVisibleOnly: true, + applicationServerKey: this.urlBase64ToUint8Array(vapidPublicKey), + }); + + logger.info({ + message: 'Successfully subscribed to push notifications', + context: { + endpoint: this.pushSubscription.endpoint, + }, + }); + + return this.pushSubscription; + } catch (error) { + logger.error({ + message: 'Failed to subscribe to push notifications', + context: { error }, + }); + return null; + } + } + + /** + * Unsubscribe from push notifications + */ + async unsubscribe(): Promise { + if (!this.pushSubscription) { + return true; + } + + try { + await this.pushSubscription.unsubscribe(); + this.pushSubscription = null; + logger.info({ + message: 'Successfully unsubscribed from push notifications', + }); + return true; + } catch (error) { + logger.error({ + message: 'Failed to unsubscribe from push notifications', + context: { error }, + }); + return false; + } + } + + /** + * Get the current push subscription + */ + getSubscription(): PushSubscription | null { + return this.pushSubscription; + } + + /** + * Show a local notification (for testing or immediate notifications) + */ + async showLocalNotification(title: string, body: string, data?: any): Promise { + const permission = await this.requestPermission(); + if (permission !== 'granted') { + return; + } + + // Use the Notification API directly + const notification = new Notification(title, { + body, + icon: '/icon-192.png', + badge: '/badge-72.png', + data, + requireInteraction: true, + tag: data?.eventCode || 'notification', + }); + + notification.onclick = () => { + window.focus(); + notification.close(); + + if (data?.eventCode) { + usePushNotificationModalStore.getState().showNotificationModal({ + eventCode: data.eventCode, + title, + body, + data, + }); + } + }; + } + + /** + * Convert VAPID key from base64 to Uint8Array + */ + private urlBase64ToUint8Array(base64String: string): Uint8Array { + const padding = '='.repeat((4 - (base64String.length % 4)) % 4); + const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/'); + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; + } + + /** + * Check if push notifications are supported + */ + static isSupported(): boolean { + return 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window; + } +} + +export const webPushNotificationService = WebPushNotificationService.getInstance(); diff --git a/types/docker-env.d.ts b/types/docker-env.d.ts new file mode 100644 index 00000000..13292d6f --- /dev/null +++ b/types/docker-env.d.ts @@ -0,0 +1,31 @@ +/** + * Type declarations for Docker runtime environment injection + * When running in Docker, the entrypoint script injects window.__ENV__ + */ + +interface DockerEnvConfig { + APP_ENV: string; + NAME: string; + SCHEME: string; + VERSION: string; + BASE_API_URL: string; + API_VERSION: string; + RESGRID_API_URL: string; + CHANNEL_HUB_NAME: string; + REALTIME_GEO_HUB_NAME: string; + LOGGING_KEY: string; + APP_KEY: string; + UNIT_MAPBOX_PUBKEY: string; + IS_MOBILE_APP: boolean; + SENTRY_DSN: string; + COUNTLY_APP_KEY: string; + COUNTLY_SERVER_URL: string; +} + +declare global { + interface Window { + __ENV__?: DockerEnvConfig; + } +} + +export {}; diff --git a/yarn.lock b/yarn.lock index 9438dd19..54ab3c3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"7zip-bin@~5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/7zip-bin/-/7zip-bin-5.2.0.tgz#7a03314684dd6572b7dfa89e68ce31d60286854d" + integrity sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A== + "@0no-co/graphql.web@^1.0.13", "@0no-co/graphql.web@^1.0.8": version "1.2.0" resolved "https://registry.yarnpkg.com/@0no-co/graphql.web/-/graphql.web-1.2.0.tgz#296d00581bfaaabfda1e976849d927824aaea81b" @@ -1013,6 +1018,14 @@ dependencies: flatted "^3.3.1" +"@develar/schema-utils@~2.6.5": + version "2.6.5" + resolved "https://registry.yarnpkg.com/@develar/schema-utils/-/schema-utils-2.6.5.tgz#3ece22c5838402419a6e0425f85742b961d9b6c6" + integrity sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig== + dependencies: + ajv "^6.12.0" + ajv-keywords "^3.4.1" + "@egjs/hammerjs@^2.0.17": version "2.0.17" resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124" @@ -1020,6 +1033,93 @@ dependencies: "@types/hammerjs" "^2.0.36" +"@electron/asar@3.4.1", "@electron/asar@^3.3.1": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.4.1.tgz#4e9196a4b54fba18c56cd8d5cac67c5bdc588065" + integrity sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA== + dependencies: + commander "^5.0.0" + glob "^7.1.6" + minimatch "^3.0.4" + +"@electron/fuses@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@electron/fuses/-/fuses-1.8.0.tgz#ad34d3cc4703b1258b83f6989917052cfc1490a0" + integrity sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw== + dependencies: + chalk "^4.1.1" + fs-extra "^9.0.1" + minimist "^1.2.5" + +"@electron/get@^2.0.0": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" + integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== + dependencies: + debug "^4.1.1" + env-paths "^2.2.0" + fs-extra "^8.1.0" + got "^11.8.5" + progress "^2.0.3" + semver "^6.2.0" + sumchecker "^3.0.1" + optionalDependencies: + global-agent "^3.0.0" + +"@electron/notarize@2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-2.5.0.tgz#d4d25356adfa29df4a76bd64a8bd347237cd251e" + integrity sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A== + dependencies: + debug "^4.1.1" + fs-extra "^9.0.1" + promise-retry "^2.0.1" + +"@electron/osx-sign@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@electron/osx-sign/-/osx-sign-1.3.3.tgz#af751510488318d9f7663694af85819690d75583" + integrity sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg== + dependencies: + compare-version "^0.1.2" + debug "^4.3.4" + fs-extra "^10.0.0" + isbinaryfile "^4.0.8" + minimist "^1.2.6" + plist "^3.0.5" + +"@electron/rebuild@4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@electron/rebuild/-/rebuild-4.0.1.tgz#0620d5bb71a0b8b09a86fb9fa979244e1fcc10bf" + integrity sha512-iMGXb6Ib7H/Q3v+BKZJoETgF9g6KMNZVbsO4b7Dmpgb5qTFqyFTzqW9F3TOSHdybv2vKYKzSS9OiZL+dcJb+1Q== + dependencies: + "@malept/cross-spawn-promise" "^2.0.0" + chalk "^4.0.0" + debug "^4.1.1" + detect-libc "^2.0.1" + got "^11.7.0" + graceful-fs "^4.2.11" + node-abi "^4.2.0" + node-api-version "^0.2.1" + node-gyp "^11.2.0" + ora "^5.1.0" + read-binary-file-arch "^1.0.6" + semver "^7.3.5" + tar "^6.0.5" + yargs "^17.0.1" + +"@electron/universal@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-2.0.3.tgz#1680df6ced8f128ca0ff24e29c2165d41d78b3ce" + integrity sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g== + dependencies: + "@electron/asar" "^3.3.1" + "@malept/cross-spawn-promise" "^2.0.0" + debug "^4.3.1" + dir-compare "^4.2.0" + fs-extra "^11.1.1" + minimatch "^9.0.3" + plist "^3.1.0" + "@emnapi/core@^1.4.3": version "1.5.0" resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.5.0.tgz#85cd84537ec989cebb2343606a1ee663ce4edaf0" @@ -2229,6 +2329,40 @@ protobufjs "^7.2.5" yargs "^17.7.2" +"@hapi/address@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@hapi/address/-/address-5.1.1.tgz#e9925fc1b65f5cc3fbea821f2b980e4652e84cb6" + integrity sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA== + dependencies: + "@hapi/hoek" "^11.0.2" + +"@hapi/formula@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@hapi/formula/-/formula-3.0.2.tgz#81b538060ee079481c906f599906d163c4badeaf" + integrity sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw== + +"@hapi/hoek@^11.0.2", "@hapi/hoek@^11.0.7": + version "11.0.7" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-11.0.7.tgz#56a920793e0a42d10e530da9a64cc0d3919c4002" + integrity sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ== + +"@hapi/pinpoint@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@hapi/pinpoint/-/pinpoint-2.0.1.tgz#32077e715655fc00ab8df74b6b416114287d6513" + integrity sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q== + +"@hapi/tlds@^1.1.1": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@hapi/tlds/-/tlds-1.1.4.tgz#df4a7b59082b54ba4f3b7b38f781e2ac3cbc359a" + integrity sha512-Fq+20dxsxLaUn5jSSWrdtSRcIUba2JquuorF9UW1wIJS5cSUwxIsO2GIhaWynPRflvxSzFN+gxKte2HEW1OuoA== + +"@hapi/topo@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-6.0.2.tgz#f219c1c60da8430228af4c1f2e40c32a0d84bbb4" + integrity sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg== + dependencies: + "@hapi/hoek" "^11.0.2" + "@hookform/resolvers@~3.9.0": version "3.9.1" resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.9.1.tgz#a23883c40bfd449cb6c6ab5a0fa0729184c950ff" @@ -2302,6 +2436,18 @@ dependencies: "@swc/helpers" "^0.5.0" +"@isaacs/balanced-match@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" + integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== + +"@isaacs/brace-expansion@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" + integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== + dependencies: + "@isaacs/balanced-match" "^4.0.1" + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -2997,6 +3143,62 @@ web-streams-polyfill "^4.1.0" well-known-symbols "^4.1.0" +"@malept/cross-spawn-promise@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz#d0772de1aa680a0bfb9ba2f32b4c828c7857cb9d" + integrity sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg== + dependencies: + cross-spawn "^7.0.1" + +"@malept/flatpak-bundler@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz#e8a32c30a95d20c2b1bb635cc580981a06389858" + integrity sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q== + dependencies: + debug "^4.1.1" + fs-extra "^9.0.0" + lodash "^4.17.15" + tmp-promise "^3.0.2" + +"@mapbox/jsonlint-lines-primitives@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234" + integrity sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ== + +"@mapbox/mapbox-gl-supported@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-3.0.0.tgz#bebd3d5da3c1fd988011bb79718a39f63f5e16ac" + integrity sha512-2XghOwu16ZwPJLOFVuIOaLbN0iKMn867evzXFyf0P22dqugezfJwLmdanAgU25ITvz1TvOfVP4jsDImlDJzcWg== + +"@mapbox/point-geometry@^1.1.0", "@mapbox/point-geometry@~1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-1.1.0.tgz#3328fb54b3a1273bc619bf0a6baad8de37181749" + integrity sha512-YGcBz1cg4ATXDCM/71L9xveh4dynfGmcLDqufR+nQQy3fKwsAZsWd/x4621/6uJaeB9mwOHE6hPeDgXz9uViUQ== + +"@mapbox/tiny-sdf@^2.0.6": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-2.0.7.tgz#0d67d65a43195003b282764f2297c619736bbc6e" + integrity sha512-25gQLQMcpivjOSA40g3gO6qgiFPDpWRoMfd+G/GoppPIeP6JDaMMkMrEJnMZhKyyS6iKwVt5YKu02vCUyJM3Ug== + +"@mapbox/unitbezier@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz#d32deb66c7177e9e9dfc3bbd697083e2e657ff01" + integrity sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw== + +"@mapbox/vector-tile@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-2.0.4.tgz#59c5ca80a84c210e61226367b0f9c8fd1737a437" + integrity sha512-AkOLcbgGTdXScosBWwmmD7cDlvOjkg/DetGva26pIRiZPdeJYjYKarIlb4uxVzi6bwHO6EWH82eZ5Nuv4T5DUg== + dependencies: + "@mapbox/point-geometry" "~1.1.0" + "@types/geojson" "^7946.0.16" + pbf "^4.0.1" + +"@mapbox/whoots-js@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" + integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== + "@microsoft/signalr@~8.0.7": version "8.0.17" resolved "https://registry.yarnpkg.com/@microsoft/signalr/-/signalr-8.0.17.tgz#6eac218beac38e4e4ba4a3577ed39ac33711b2ed" @@ -3145,6 +3347,24 @@ dependencies: "@novu/js" "3.11.0" +"@npmcli/agent@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-3.0.0.tgz#1685b1fbd4a1b7bb4f930cbb68ce801edfe7aa44" + integrity sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q== + dependencies: + agent-base "^7.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + lru-cache "^10.0.1" + socks-proxy-agent "^8.0.3" + +"@npmcli/fs@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-4.0.0.tgz#a1eb1aeddefd2a4a347eca0fab30bc62c0e1c0f2" + integrity sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q== + dependencies: + semver "^7.3.5" + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -4419,6 +4639,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.41.tgz#aa51a6c1946df2c5a11494a2cdb9318e026db16c" integrity sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g== +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@sinonjs/commons@^3.0.0": version "3.0.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd" @@ -4522,6 +4747,11 @@ resolved "https://registry.yarnpkg.com/@solid-primitives/utils/-/utils-6.3.2.tgz#13d6126fc5a498965d7c45dd41c052e42dcfd7e1" integrity sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ== +"@standard-schema/spec@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + "@svgr/babel-plugin-add-jsx-attribute@8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" @@ -4621,6 +4851,13 @@ dependencies: tslib "^2.8.0" +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + "@tanstack/query-core@5.52.3": version "5.52.3" resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.52.3.tgz#4d66b3e7a6d77513cb4d3cb661b2c03009103f5a" @@ -4852,6 +5089,16 @@ dependencies: "@babel/types" "^7.28.2" +"@types/cacheable-request@^6.0.1": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + "@types/conventional-commits-parser@^5.0.0": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz#8cb81cf170853496cbc501a3b32dcf5e46ffb61a" @@ -4859,16 +5106,37 @@ dependencies: "@types/node" "*" -"@types/geojson@7946.0.8": - version "7946.0.8" - resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" - integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== +"@types/debug@^4.1.6": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + +"@types/fs-extra@9.0.13", "@types/fs-extra@^9.0.11": + version "9.0.13" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45" + integrity sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== + dependencies: + "@types/node" "*" + +"@types/geojson-vt@^3.2.5": + version "3.2.5" + resolved "https://registry.yarnpkg.com/@types/geojson-vt/-/geojson-vt-3.2.5.tgz#b6c356874991d9ab4207533476dfbcdb21e38408" + integrity sha512-qDO7wqtprzlpe8FfQ//ClPV9xiuoh2nkIgiouIptON9w5jvD/fA4szvP9GBlDVdJ5dldAl0kX/sy3URbWwLx0g== + dependencies: + "@types/geojson" "*" -"@types/geojson@^7946.0.10", "@types/geojson@^7946.0.7", "@types/geojson@~7946.0.16": +"@types/geojson@*", "@types/geojson@^7946.0.10", "@types/geojson@^7946.0.16", "@types/geojson@^7946.0.7", "@types/geojson@~7946.0.16": version "7946.0.16" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a" integrity sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== +"@types/geojson@7946.0.8": + version "7946.0.8" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" + integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4" @@ -4881,6 +5149,11 @@ resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.46.tgz#381daaca1360ff8a7c8dff63f32e69745b9fb1e1" integrity sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw== +"@types/http-cache-semantics@*": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz#b979ebad3919799c979b17c72621c0bc0a31c6c4" + integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== + "@types/i18n-js@~3.8.9": version "3.8.9" resolved "https://registry.yarnpkg.com/@types/i18n-js/-/i18n-js-3.8.9.tgz#074d1389539d2db992e6afd7eb379aa02929ef93" @@ -4932,6 +5205,13 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/lodash.memoize@~4.1.9": version "4.1.9" resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.9.tgz#9f8912d39b6e450c0d342a2b74c99d331bf2016b" @@ -4944,6 +5224,23 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.20.tgz#1ca77361d7363432d29f5e55950d9ec1e1c6ea93" integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA== +"@types/mapbox-gl@3.4.1": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-3.4.1.tgz#b6a1b81b802e62fb7e1d1d13a97ccece63b232a3" + integrity sha512-NsGKKtgW93B+UaLPti6B7NwlxYlES5DpV5Gzj9F75rK5ALKsqSk15CiEHbOnTr09RGbr6ZYiCdI+59NNNcAImg== + dependencies: + "@types/geojson" "*" + +"@types/mapbox__point-geometry@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz#0ef017b75eedce02ff6243b4189210e2e6d5e56d" + integrity sha512-mUWlSxAmYLfwnRBmgYV86tgYmMIICX4kza8YnE/eIlywGe2XoOxlpVnXWwir92xRLjwyarqwpu2EJKD2pk0IUA== + +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + "@types/node@*": version "24.5.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-24.5.2.tgz#52ceb83f50fe0fcfdfbd2a9fab6db2e9e7ef6446" @@ -4963,11 +5260,31 @@ dependencies: undici-types "~7.16.0" +"@types/node@^24.9.0": + version "24.10.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-24.10.9.tgz#1aeb5142e4a92957489cac12b07f9c7fe26057d0" + integrity sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw== + 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" integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== +"@types/pbf@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.5.tgz#a9495a58d8c75be4ffe9a0bd749a307715c07404" + integrity sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA== + +"@types/plist@^3.0.1": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/plist/-/plist-3.0.5.tgz#9a0c49c0f9886c8c8696a7904dd703f6284036e0" + integrity sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA== + dependencies: + "@types/node" "*" + xmlbuilder ">=11.0.1" + "@types/react-native-base64@~0.2.2": version "0.2.2" resolved "https://registry.yarnpkg.com/@types/react-native-base64/-/react-native-base64-0.2.2.tgz#d4e1d537e6d547d23d96a1e64627acc13587ae6b" @@ -4980,6 +5297,13 @@ dependencies: csstype "^3.0.2" +"@types/responselike@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.3.tgz#cc29706f0a397cfe6df89debfe4bf5cea159db50" + integrity sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== + dependencies: + "@types/node" "*" + "@types/semver@^7.3.12": version "7.7.1" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" @@ -5000,11 +5324,23 @@ resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== +"@types/supercluster@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@types/supercluster/-/supercluster-7.1.3.tgz#1a1bc2401b09174d9c9e44124931ec7874a72b27" + integrity sha512-Z0pOY34GDFl3Q6hUFYf3HkTwKEE02e7QgtJppBt+beEAxnyOpJua+voGFvxINBHa06GwLFFym7gRPY2SiKIfIA== + dependencies: + "@types/geojson" "*" + "@types/tough-cookie@*": version "4.0.5" resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz#cb6e2a691b70cb177c6e3ae9c1d2e8b2ea8cd304" integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA== +"@types/verror@^1.10.3": + version "1.10.11" + resolved "https://registry.yarnpkg.com/@types/verror/-/verror-1.10.11.tgz#d3d6b418978c8aa202d41e5bb3483227b6ecc1bb" + integrity sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg== + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -5017,6 +5353,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.10.3" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.3.tgz#e9b2808b4f109504a03cda958259876f61017999" + integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@^7.4.0": version "7.18.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz#b16d3cf3ee76bf572fdf511e79c248bdec619ea3" @@ -5323,6 +5666,11 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== +abbrev@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-3.0.1.tgz#8ac8b3b5024d31464fe2a5feeea9f4536bf44025" + integrity sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -5377,7 +5725,7 @@ agent-base@6: dependencies: debug "4" -agent-base@^7.1.2: +agent-base@^7.1.0, agent-base@^7.1.2: version "7.1.4" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== @@ -5398,6 +5746,11 @@ aggregate-error@^4.0.0: clean-stack "^4.0.0" indent-string "^5.0.0" +ajv-keywords@^3.4.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + ajv@8.11.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" @@ -5408,7 +5761,7 @@ ajv@8.11.0: require-from-string "^2.0.2" uri-js "^4.2.2" -ajv@^6.12.4: +ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -5548,6 +5901,51 @@ anymatch@^3.0.3, anymatch@^3.1.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +app-builder-bin@5.0.0-alpha.12: + version "5.0.0-alpha.12" + resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz#2daf82f8badc698e0adcc95ba36af4ff0650dc80" + integrity sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w== + +app-builder-lib@26.4.0: + version "26.4.0" + resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-26.4.0.tgz#649b4a98b51a90141b73e4f12a74ac5bc0f2eff4" + integrity sha512-Uas6hNe99KzP3xPWxh5LGlH8kWIVjZixzmMJHNB9+6hPyDpjc7NQMkVgi16rQDdpCFy22ZU5sp8ow7tvjeMgYQ== + dependencies: + "@develar/schema-utils" "~2.6.5" + "@electron/asar" "3.4.1" + "@electron/fuses" "^1.8.0" + "@electron/notarize" "2.5.0" + "@electron/osx-sign" "1.3.3" + "@electron/rebuild" "4.0.1" + "@electron/universal" "2.0.3" + "@malept/flatpak-bundler" "^0.4.0" + "@types/fs-extra" "9.0.13" + async-exit-hook "^2.0.1" + builder-util "26.3.4" + builder-util-runtime "9.5.1" + chromium-pickle-js "^0.2.0" + ci-info "4.3.1" + debug "^4.3.4" + dotenv "^16.4.5" + dotenv-expand "^11.0.6" + ejs "^3.1.8" + electron-publish "26.3.4" + fs-extra "^10.1.0" + hosted-git-info "^4.1.0" + isbinaryfile "^5.0.0" + jiti "^2.4.2" + js-yaml "^4.1.0" + json5 "^2.2.3" + lazy-val "^1.0.5" + minimatch "^10.0.3" + plist "3.1.0" + resedit "^1.7.0" + semver "~7.7.3" + tar "^6.1.12" + temp-file "^3.4.0" + tiny-async-pool "1.3.0" + which "^5.0.0" + app-icon-badge@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/app-icon-badge/-/app-icon-badge-0.1.2.tgz#4df187989429fc282de4ee3fe0c3ee683a9a3f22" @@ -5720,6 +6118,21 @@ asap@~2.0.3, asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-exit-hook@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/async-exit-hook/-/async-exit-hook-2.0.1.tgz#8bd8b024b0ec9b1c01cccb9af9db29bd717dfaf3" + integrity sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw== + async-function@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" @@ -5730,6 +6143,11 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async@^3.2.6: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -5755,6 +6173,15 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" +axios@^1.13.2: + version "1.13.3" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.3.tgz#f123e77356630a22b0163920662556944f2be1a1" + integrity sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.4" + proxy-from-env "^1.1.0" + axios@~1.12.0: version "1.12.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.12.2.tgz#6c307390136cf7a2278d09cec63b136dfc6e6da7" @@ -6000,6 +6427,11 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +boolean@^3.0.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" + integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== + boxen@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/boxen/-/boxen-8.0.1.tgz#7e9fcbb45e11a2d7e6daa8fdcebfc3242fc19fe3" @@ -6082,6 +6514,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-equal@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-0.0.1.tgz#91bc74b11ea405bc916bc6aa908faafa5b4aac4b" @@ -6092,7 +6529,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^5.2.0, buffer@^5.4.3, buffer@^5.5.0: +buffer@^5.1.0, buffer@^5.2.0, buffer@^5.4.3, buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -6108,6 +6545,36 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +builder-util-runtime@9.5.1: + version "9.5.1" + resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz#74125fb374d1ecbf472ae1787485485ff7619702" + integrity sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ== + dependencies: + debug "^4.3.4" + sax "^1.2.4" + +builder-util@26.3.4: + version "26.3.4" + resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-26.3.4.tgz#eb20e2e2895fe360360eddded5d8cf12ad2aad60" + integrity sha512-aRn88mYMktHxzdqDMF6Ayj0rKoX+ZogJ75Ck7RrIqbY/ad0HBvnS2xA4uHfzrGr5D2aLL3vU6OBEH4p0KMV2XQ== + dependencies: + "7zip-bin" "~5.2.0" + "@types/debug" "^4.1.6" + app-builder-bin "5.0.0-alpha.12" + builder-util-runtime "9.5.1" + chalk "^4.1.2" + cross-spawn "^7.0.6" + debug "^4.3.4" + fs-extra "^10.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.0" + js-yaml "^4.1.0" + sanitize-filename "^1.6.3" + source-map-support "^0.5.19" + stat-mode "^1.0.0" + temp-file "^3.4.0" + tiny-async-pool "1.3.0" + builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" @@ -6125,6 +6592,42 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cacache@^19.0.1: + version "19.0.1" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-19.0.1.tgz#3370cc28a758434c85c2585008bd5bdcff17d6cd" + integrity sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ== + dependencies: + "@npmcli/fs" "^4.0.0" + fs-minipass "^3.0.0" + glob "^10.2.2" + lru-cache "^10.0.1" + minipass "^7.0.3" + minipass-collect "^2.0.1" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^7.0.2" + ssri "^12.0.0" + tar "^7.4.3" + unique-filename "^4.0.0" + +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" @@ -6214,6 +6717,14 @@ chalk-template@^1.1.0: dependencies: chalk "^5.2.0" +chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -6242,14 +6753,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^5.2.0, chalk@^5.3.0: version "5.6.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" @@ -6280,6 +6783,11 @@ chardet@^2.1.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-2.1.0.tgz#1007f441a1ae9f9199a4a67f6e978fb0aa9aa3fe" integrity sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA== +cheap-ruler@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cheap-ruler/-/cheap-ruler-4.0.0.tgz#bdc984de7e0e3f748bdfd2dbe23ec6b9dc820a09" + integrity sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw== + chokidar@^3.5.3: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -6295,6 +6803,11 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chownr@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" @@ -6322,6 +6835,16 @@ chromium-edge-launcher@^0.2.0: mkdirp "^1.0.4" rimraf "^3.0.2" +chromium-pickle-js@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" + integrity sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw== + +ci-info@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.3.1.tgz#355ad571920810b5623e11d40232f443f16f1daa" + integrity sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA== + ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -6407,6 +6930,14 @@ cli-truncate@^0.2.1: slice-ansi "0.0.4" string-width "^1.0.1" +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + cli-truncate@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a" @@ -6444,6 +6975,13 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + clone@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" @@ -6541,6 +7079,11 @@ commander@^4.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +commander@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + commander@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" @@ -6563,6 +7106,11 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" +compare-version@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" + integrity sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A== + compressible@~2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -6588,6 +7136,18 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concurrently@9.2.1: + version "9.2.1" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-9.2.1.tgz#248ea21b95754947be2dad9c3e4b60f18ca4e44f" + integrity sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng== + dependencies: + chalk "4.1.2" + rxjs "7.8.2" + shell-quote "1.8.3" + supports-color "8.1.1" + tree-kill "1.2.2" + yargs "17.7.2" + config-chain@^1.1.11: version "1.1.13" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" @@ -6652,6 +7212,11 @@ core-js-compat@^3.43.0: dependencies: browserslist "^4.25.3" +core-util-is@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== + core-util-is@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -6699,6 +7264,13 @@ countly-sdk-react-native-bridge@^25.4.0: resolved "https://registry.yarnpkg.com/countly-sdk-react-native-bridge/-/countly-sdk-react-native-bridge-25.4.0.tgz#dd04086142becf41b4312c8fe361db87b235e04d" integrity sha512-MIkQtb5UfWW7FhC7pB6luudlfdTk0YA42YCKtnAwH+0gcm4jkMMuqq0HLytqFWki9fcCzfyatz+HGIu5s5mKvA== +crc@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -6797,6 +7369,11 @@ css.escape@^1.5.1: resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== +csscolorparser@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b" + integrity sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -6932,6 +7509,13 @@ decode-uri-component@^0.2.2: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + dedent@^1.0.0: version "1.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.7.0.tgz#c1f9445335f0175a96587be245a282ff451446ca" @@ -6972,6 +7556,11 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -7039,6 +7628,11 @@ detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== +detect-libc@^2.0.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + detect-libc@^2.0.3: version "2.1.1" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.1.tgz#9f1e511ace6bb525efea4651345beac424dac7b9" @@ -7049,6 +7643,11 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" @@ -7069,6 +7668,14 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +dir-compare@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-4.2.0.tgz#d1d4999c14fbf55281071fdae4293b3b9ce86f19" + integrity sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ== + dependencies: + minimatch "^3.0.5" + p-limit "^3.1.0 " + dir-glob@^3.0.0, dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -7081,6 +7688,33 @@ dlv@^1.1.3: resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== +dmg-builder@26.4.0: + version "26.4.0" + resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-26.4.0.tgz#dc7edec167b06b1542804e6e4220d1bcaf952b71" + integrity sha512-ce4Ogns4VMeisIuCSK0C62umG0lFy012jd8LMZ6w/veHUeX4fqfDrGe+HTWALAEwK6JwKP+dhPvizhArSOsFbg== + dependencies: + app-builder-lib "26.4.0" + builder-util "26.3.4" + fs-extra "^10.1.0" + iconv-lite "^0.6.2" + js-yaml "^4.1.0" + optionalDependencies: + dmg-license "^1.0.11" + +dmg-license@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/dmg-license/-/dmg-license-1.0.11.tgz#7b3bc3745d1b52be7506b4ee80cb61df6e4cd79a" + integrity sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q== + dependencies: + "@types/plist" "^3.0.1" + "@types/verror" "^1.10.3" + ajv "^6.10.0" + crc "^3.8.0" + iconv-corefoundation "^1.1.7" + plist "^3.0.4" + smart-buffer "^4.0.2" + verror "^1.10.0" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -7172,7 +7806,7 @@ dot-prop@^9.0.0: dependencies: type-fest "^4.18.2" -dotenv-expand@~11.0.6: +dotenv-expand@^11.0.6, dotenv-expand@~11.0.6: version "11.0.7" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-11.0.7.tgz#af695aea007d6fdc84c86cd8d0ad7beb40a0bd08" integrity sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA== @@ -7198,6 +7832,11 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" +earcut@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/earcut/-/earcut-3.0.2.tgz#d478a29aaf99acf418151493048aa197d0512248" + integrity sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ== + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -7208,12 +7847,58 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== +ejs@^3.1.8: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + +electron-builder@26.4.0: + version "26.4.0" + resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-26.4.0.tgz#f9a8598f045ded4cbaf41efb7bb18a89619c5dd2" + integrity sha512-FCUqvdq2AULL+Db2SUGgjOYTbrgkPxZtCjqIZGnjH9p29pTWyesQqBIfvQBKa6ewqde87aWl49n/WyI/NyUBog== + dependencies: + app-builder-lib "26.4.0" + builder-util "26.3.4" + builder-util-runtime "9.5.1" + chalk "^4.1.2" + ci-info "^4.2.0" + dmg-builder "26.4.0" + fs-extra "^10.1.0" + lazy-val "^1.0.5" + simple-update-notifier "2.0.0" + yargs "^17.6.2" + +electron-publish@26.3.4: + version "26.3.4" + resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-26.3.4.tgz#ed05f1ccbb7ee1e53b4140d92735e26fa4bfefd7" + integrity sha512-5/ouDPb73SkKuay2EXisPG60LTFTMNHWo2WLrK5GDphnWK9UC+yzYrzVeydj078Yk4WUXi0+TaaZsNd6Zt5k/A== + dependencies: + "@types/fs-extra" "^9.0.11" + builder-util "26.3.4" + builder-util-runtime "9.5.1" + chalk "^4.1.2" + form-data "^4.0.0" + fs-extra "^10.1.0" + lazy-val "^1.0.5" + mime "^2.5.2" + electron-to-chromium@^1.5.218: version "1.5.227" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.227.tgz#c81b6af045b0d6098faed261f0bd611dc282d3a7" integrity sha512-ITxuoPfJu3lsNWUi2lBM2PaBPYgH3uqmxut5vmBxgYvyI4AlJ6P3Cai1O76mOrkJCBzq0IxWg/NtqOrpu/0gKA== -elegant-spinner@^1.0.1: +electron@40.0.0: + version "40.0.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-40.0.0.tgz#01b1589ce2619dc7e792b1ca78d3ff1ae2ce05e7" + integrity sha512-UyBy5yJ0/wm4gNugCtNPjvddjAknMTuXR2aCHioXicH7aKRKGDBPp4xqTEi/doVcB3R+MN3wfU9o8d/9pwgK2A== + dependencies: + "@electron/get" "^2.0.0" + "@types/node" "^24.9.0" + extract-zip "^2.0.1" + +elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" integrity sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ== @@ -7248,6 +7933,20 @@ encodeurl@~2.0.0: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== +encoding@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0: + version "1.4.5" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.5.tgz#7344d711dea40e0b74abc2ed49778743ccedb08c" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== + dependencies: + once "^1.4.0" + engine.io-client@~6.5.2: version "6.5.4" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-6.5.4.tgz#b8bc71ed3f25d0d51d587729262486b4b33bd0d0" @@ -7287,7 +7986,7 @@ env-editor@^0.4.1: resolved "https://registry.yarnpkg.com/env-editor/-/env-editor-0.4.2.tgz#4e76568d0bd8f5c2b6d314a9412c8fe9aa3ae861" integrity sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA== -env-paths@^2.2.1: +env-paths@^2.2.0, env-paths@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== @@ -7297,6 +7996,11 @@ environment@^1.0.0: resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + error-ex@^1.3.1: version "1.3.4" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" @@ -7456,6 +8160,11 @@ es-to-primitive@^1.3.0: is-date-object "^1.0.5" is-symbol "^1.0.4" +es6-error@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -8233,6 +8942,22 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + +extsprintf@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" + integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -8308,6 +9033,13 @@ fbjs@^3.0.4: setimmediate "^1.0.5" ua-parser-js "^1.0.35" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" @@ -8359,6 +9091,13 @@ file-type@^16.5.4: strtok3 "^6.2.4" token-types "^4.1.1" +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -8555,7 +9294,7 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -fs-extra@^10.0.0: +fs-extra@^10.0.0, fs-extra@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== @@ -8564,7 +9303,25 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^9.0.0: +fs-extra@^11.1.1: + version "11.3.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.3.tgz#a27da23b72524e81ac6c3815cc0179b8c74c59ee" + integrity sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + +fs-extra@^9.0.0, fs-extra@^9.0.1: 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== @@ -8574,6 +9331,20 @@ fs-extra@^9.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-minipass@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-3.0.3.tgz#79a85981c4dc120065e96f62086bf6f9dc26cc54" + integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw== + dependencies: + minipass "^7.0.3" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -8627,6 +9398,11 @@ geojson-rbush@3.x: "@types/geojson" "7946.0.8" rbush "^3.0.1" +geojson-vt@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-4.0.2.tgz#1162f6c7d61a0ba305b1030621e6e111f847828a" + integrity sha512-AV9ROqlNqoZEIJGfm1ncNjEXfkz2hdFlZf0qkVfmkwdKa8vj7H16YUOT81rJw1rdFhyEDlN2Tds91p/glzbl5A== + geojson@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/geojson/-/geojson-0.5.0.tgz#3cd6c96399be65b56ee55596116fe9191ce701c0" @@ -8671,6 +9447,13 @@ get-proto@^1.0.0, get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -8724,6 +9507,11 @@ github-url-from-git@^1.5.0: resolved "https://registry.yarnpkg.com/github-url-from-git/-/github-url-from-git-1.5.0.tgz#f985fedcc0a9aa579dc88d7aff068d55cc6251a0" integrity sha512-WWOec4aRI7YAykQ9+BHmzjyNlkfJFG8QLXnDTsLz/kZefq7qkzdfo4p6fkYYMIq1aj+gZcQs/1HQhQh3DPPxlQ== +gl-matrix@^3.4.4: + version "3.4.4" + resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.4.tgz#7789ee4982f62c7a7af447ee488f3bd6b0c77003" + integrity sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -8738,6 +9526,18 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@^10.2.2: + version "10.5.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@^10.3.10, glob@^10.4.2: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" @@ -8750,7 +9550,7 @@ glob@^10.3.10, glob@^10.4.2: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.1.1, glob@^7.1.3, glob@^7.1.4: +glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -8772,6 +9572,18 @@ glob@^9.3.3: minipass "^4.2.4" path-scurry "^1.6.1" +global-agent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" + integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== + dependencies: + boolean "^3.0.1" + es6-error "^4.1.1" + matcher "^3.0.0" + roarr "^2.15.3" + semver "^7.3.2" + serialize-error "^7.0.1" + global-directory@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/global-directory/-/global-directory-4.0.1.tgz#4d7ac7cfd2cb73f304c53b8810891748df5e361e" @@ -8794,7 +9606,7 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.4: +globalthis@^1.0.1, globalthis@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== @@ -8830,12 +9642,29 @@ gopd@^1.0.1, gopd@^1.2.0: resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== +got@^11.7.0, got@^11.8.5: + version "11.8.6" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + graceful-fs@4.2.10: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -8845,6 +9674,11 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +grid-index@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" + integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA== + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -8953,6 +9787,13 @@ hosted-git-info@^2.1.4: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== +hosted-git-info@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" + integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== + dependencies: + lru-cache "^6.0.0" + hosted-git-info@^7.0.0, hosted-git-info@^7.0.1: version "7.0.2" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.2.tgz#9b751acac097757667f30114607ef7b661ff4f17" @@ -8979,6 +9820,11 @@ html-parse-stringify@^3.0.1: dependencies: void-elements "3.1.0" +http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" + integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== + http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -9004,6 +9850,22 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" @@ -9012,7 +9874,7 @@ https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: agent-base "6" debug "4" -https-proxy-agent@^7.0.5: +https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.1, https-proxy-agent@^7.0.5: version "7.0.6" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== @@ -9042,7 +9904,15 @@ i18next@~23.14.0: dependencies: "@babel/runtime" "^7.23.2" -iconv-lite@0.6.3: +iconv-corefoundation@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz#31065e6ab2c9272154c8b0821151e2c88f1b002a" + integrity sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ== + dependencies: + cli-truncate "^2.1.0" + node-addon-api "^1.6.3" + +iconv-lite@0.6.3, iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -9274,6 +10144,11 @@ invariant@2.2.4, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +ip-address@^10.0.1: + version "10.1.0" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.1.0.tgz#d8dcffb34d0e02eb241427444a6e23f5b0595aa4" + integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q== + irregular-plurals@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766" @@ -9697,11 +10572,26 @@ isarray@^2.0.5: resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== +isbinaryfile@^4.0.8: + version "4.0.10" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" + integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== + +isbinaryfile@^5.0.0: + version "5.0.7" + resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-5.0.7.tgz#19a73f2281b7368dca9d3b3ac8a0434074670979" + integrity sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isexe@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" + integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== + isomorphic-fetch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" @@ -9802,6 +10692,15 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jake@^10.8.5: + version "10.9.4" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.4.tgz#d626da108c63d5cfb00ab5c25fadc7e0084af8e6" + integrity sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA== + dependencies: + async "^3.2.6" + filelist "^1.0.4" + picocolors "^1.1.1" + jest-changed-files@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.7.0.tgz#1c06d07e77c78e1585d020424dedc10d6e17ac3a" @@ -10314,6 +11213,24 @@ jiti@^2.4.1: resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.0.tgz#b831fdc4440c0a4944c34456643c555afe09d36d" integrity sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ== +jiti@^2.4.2: + version "2.6.1" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.6.1.tgz#178ef2fc9a1a594248c20627cd820187a4d78d92" + integrity sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ== + +joi@^18.0.1: + version "18.0.2" + resolved "https://registry.yarnpkg.com/joi/-/joi-18.0.2.tgz#30ced6aed00a7848cc11f92859515258301dc3a4" + integrity sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA== + dependencies: + "@hapi/address" "^5.1.1" + "@hapi/formula" "^3.0.2" + "@hapi/hoek" "^11.0.7" + "@hapi/pinpoint" "^2.0.1" + "@hapi/tlds" "^1.1.1" + "@hapi/topo" "^6.0.2" + "@standard-schema/spec" "^1.0.0" + jpeg-js@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" @@ -10427,6 +11344,11 @@ json-stable-stringify@^1.0.2: jsonify "^0.0.1" object-keys "^1.1.1" +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -10439,6 +11361,13 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" @@ -10468,7 +11397,12 @@ jsonparse@^1.2.0: object.assign "^4.1.4" object.values "^1.1.6" -keyv@^4.5.3: +kdbush@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-4.0.2.tgz#2f7b7246328b4657dd122b6c7f025fbc2c868e39" + integrity sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA== + +keyv@^4.0.0, keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -10504,6 +11438,11 @@ latest-version@^9.0.0: dependencies: package-json "^10.0.0" +lazy-val@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.5.tgz#6cf3b9f5bc31cee7ee3e369c0832b7583dcd923d" + integrity sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -10892,6 +11831,11 @@ lodash@^4.17.12, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.17.15: + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -10971,6 +11915,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^10.0.1, lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" @@ -10983,6 +11932,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + lucide-react-native@~0.475.0: version "0.475.0" resolved "https://registry.yarnpkg.com/lucide-react-native/-/lucide-react-native-0.475.0.tgz#a96bbcaa8d953c845cf3038d136aba408318c484" @@ -11000,6 +11956,23 @@ make-error@1.x, make-error@^1.1.1: resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +make-fetch-happen@^14.0.3: + version "14.0.3" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz#d74c3ecb0028f08ab604011e0bc6baed483fcdcd" + integrity sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ== + dependencies: + "@npmcli/agent" "^3.0.0" + cacache "^19.0.1" + http-cache-semantics "^4.1.1" + minipass "^7.0.2" + minipass-fetch "^4.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^1.0.0" + proc-log "^5.0.0" + promise-retry "^2.0.1" + ssri "^12.0.0" + makeerror@1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -11007,11 +11980,59 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +mapbox-gl@3.18.1: + version "3.18.1" + resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-3.18.1.tgz#d577410229083595bd1c994d925b902f79df3b8f" + integrity sha512-Izc8dee2zkmb6Pn9hXFbVioPRLXJz1OFUcrvri69MhFACPU4bhLyVmhEsD9AyW1qOAP0Yvhzm60v63xdMIHPPw== + dependencies: + "@mapbox/jsonlint-lines-primitives" "^2.0.2" + "@mapbox/mapbox-gl-supported" "^3.0.0" + "@mapbox/point-geometry" "^1.1.0" + "@mapbox/tiny-sdf" "^2.0.6" + "@mapbox/unitbezier" "^0.0.1" + "@mapbox/vector-tile" "^2.0.4" + "@mapbox/whoots-js" "^3.1.0" + "@types/geojson" "^7946.0.16" + "@types/geojson-vt" "^3.2.5" + "@types/mapbox__point-geometry" "^0.1.4" + "@types/pbf" "^3.0.5" + "@types/supercluster" "^7.1.3" + cheap-ruler "^4.0.0" + csscolorparser "~1.0.3" + earcut "^3.0.1" + geojson-vt "^4.0.2" + gl-matrix "^3.4.4" + grid-index "^1.1.0" + kdbush "^4.0.2" + martinez-polygon-clipping "^0.8.1" + murmurhash-js "^1.0.0" + pbf "^4.0.1" + potpack "^2.0.0" + quickselect "^3.0.0" + supercluster "^8.0.1" + tinyqueue "^3.0.0" + marky@^1.2.2: version "1.3.0" resolved "https://registry.yarnpkg.com/marky/-/marky-1.3.0.tgz#422b63b0baf65022f02eda61a238eccdbbc14997" integrity sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ== +martinez-polygon-clipping@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/martinez-polygon-clipping/-/martinez-polygon-clipping-0.8.1.tgz#72e5cfd7ebf6abc3f8a1bde85692822fe198d5c7" + integrity sha512-9PLLMzMPI6ihHox4Ns6LpVBLpRc7sbhULybZ/wyaY8sY3ECNe2+hxm1hA2/9bEEpRrdpjoeduBuZLg2aq1cSIQ== + dependencies: + robust-predicates "^2.0.4" + splaytree "^0.1.4" + tinyqueue "3.0.0" + +matcher@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" + integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== + dependencies: + escape-string-regexp "^4.0.0" + math-intrinsics@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" @@ -11285,6 +12306,11 @@ mime@1.6.0, mime@^1.3.4: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mime@^2.5.2: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" @@ -11305,6 +12331,16 @@ mimic-function@^5.0.0: resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -11317,6 +12353,13 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== +minimatch@^10.0.3: + version "10.1.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" + integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== + dependencies: + "@isaacs/brace-expansion" "^5.0.0" + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -11324,6 +12367,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimatch@^8.0.2: version "8.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" @@ -11331,29 +12381,88 @@ minimatch@^8.0.2: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.0, minimatch@^9.0.4: +minimatch@^9.0.0, minimatch@^9.0.3, minimatch@^9.0.4: version "9.0.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass-collect@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-2.0.1.tgz#1621bc77e12258a12c60d34e2276ec5c20680863" + integrity sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw== + dependencies: + minipass "^7.0.3" + +minipass-fetch@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-4.0.1.tgz#f2d717d5a418ad0b1a7274f9b913515d3e78f9e5" + integrity sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ== + dependencies: + minipass "^7.0.3" + minipass-sized "^1.0.3" + minizlib "^3.0.1" + optionalDependencies: + encoding "^0.1.13" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/minipass-sized/-/minipass-sized-1.0.3.tgz#70ee5a7c5052070afacfbc22977ea79def353b70" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + minipass@^4.2.4: version "4.2.8" resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4, minipass@^7.1.2: +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== -minizlib@^3.1.0: +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +minizlib@^3.0.1, minizlib@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c" integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== @@ -11365,7 +12474,7 @@ mitt@^3.0.1: resolved "https://registry.yarnpkg.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== -mkdirp@^1.0.4: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -11392,6 +12501,11 @@ ms@2.1.3, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +murmurhash-js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51" + integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw== + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -11450,6 +12564,11 @@ negotiator@0.6.3: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-1.0.0.tgz#b6c91bb47172d69f93cfd7c357bbb529019b5f6a" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + negotiator@~0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" @@ -11480,6 +12599,25 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-abi@^4.2.0: + version "4.26.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-4.26.0.tgz#7068474b3a945c08372995d823b71053e3ed85af" + integrity sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw== + dependencies: + semver "^7.6.3" + +node-addon-api@^1.6.3: + version "1.7.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" + integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== + +node-api-version@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-api-version/-/node-api-version-0.2.1.tgz#19bad54f6d65628cbee4e607a325e4488ace2de9" + integrity sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q== + dependencies: + semver "^7.3.5" + 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" @@ -11492,6 +12630,22 @@ node-forge@^1.2.1, node-forge@^1.3.1: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== +node-gyp@^11.2.0: + version "11.5.0" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-11.5.0.tgz#82661b5f40647a7361efe918e3cea76d297fcc56" + integrity sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ== + dependencies: + env-paths "^2.2.0" + exponential-backoff "^3.1.1" + graceful-fs "^4.2.6" + make-fetch-happen "^14.0.3" + nopt "^8.0.0" + proc-log "^5.0.0" + semver "^7.3.5" + tar "^7.4.3" + tinyglobby "^0.2.12" + which "^5.0.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -11502,6 +12656,13 @@ node-releases@^2.0.21: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.21.tgz#f59b018bc0048044be2d4c4c04e4c8b18160894c" integrity sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw== +nopt@^8.0.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-8.1.0.tgz#b11d38caf0f8643ce885818518064127f602eae3" + integrity sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A== + dependencies: + abbrev "^3.0.0" + normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -11526,6 +12687,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + np@~10.0.7: version "10.0.7" resolved "https://registry.yarnpkg.com/np/-/np-10.0.7.tgz#6f0dc3c7440c8ac95d55a2fa5d488c3d0848b7a5" @@ -11731,7 +12897,7 @@ on-headers@~1.1.0: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -11817,7 +12983,7 @@ ora@^3.4.0: strip-ansi "^5.2.0" wcwidth "^1.0.1" -ora@^5.4.1: +ora@^5.1.0, ora@^5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== @@ -11851,6 +13017,11 @@ own-keys@^1.0.1: object-keys "^1.1.1" safe-push-apply "^1.0.0" +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -11858,7 +13029,7 @@ p-limit@^2.0.0, p-limit@^2.2.0: dependencies: p-try "^2.0.0" -p-limit@^3.0.2, p-limit@^3.1.0: +p-limit@^3.0.2, p-limit@^3.1.0, "p-limit@^3.1.0 ": version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== @@ -11917,6 +13088,11 @@ p-map@^7.0.1: resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.3.tgz#7ac210a2d36f81ec28b736134810f7ba4418cdb6" integrity sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA== +p-map@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.4.tgz#b81814255f542e252d5729dca4d66e5ec14935b8" + integrity sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ== + p-memoize@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/p-memoize/-/p-memoize-7.1.1.tgz#53b1d0e6007288f7261cfa11a7603b84c9261bfa" @@ -12137,11 +13313,28 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pbf@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pbf/-/pbf-4.0.1.tgz#ad9015e022b235dcdbe05fc468a9acadf483f0d4" + integrity sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA== + dependencies: + resolve-protobuf-schema "^2.1.0" + +pe-library@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/pe-library/-/pe-library-0.4.1.tgz#e269be0340dcb13aa6949d743da7d658c3e2fbea" + integrity sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw== + peek-readable@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + phin@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/phin/-/phin-3.7.1.tgz#bf841da75ee91286691b10e41522a662aa628fd6" @@ -12212,7 +13405,7 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -plist@^3.0.5: +plist@3.1.0, plist@^3.0.4, plist@^3.0.5, plist@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/plist/-/plist-3.1.0.tgz#797a516a93e62f5bde55e0b9cc9c967f860893c9" integrity sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ== @@ -12325,6 +13518,11 @@ postinstall-postinstall@^2.1.0: resolved "https://registry.yarnpkg.com/postinstall-postinstall/-/postinstall-postinstall-2.1.0.tgz#4f7f77441ef539d1512c40bd04c71b06a4704ca3" integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== +potpack@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/potpack/-/potpack-2.1.0.tgz#fe548e2f9061e9937f17191c1ab6dd98ca30e02f" + integrity sha512-pcaShQc1Shq0y+E7GqJqvZj8DTthWV1KeHGdi0Z6IAin2Oi3JnLCOfwnCo84qc+HAp52wT9nK9H7FAJp5a44GQ== + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -12369,6 +13567,11 @@ proc-log@^4.0.0: resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-4.2.0.tgz#b6f461e4026e75fdfe228b265e9f7a00779d7034" integrity sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA== +proc-log@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/proc-log/-/proc-log-5.0.0.tgz#e6c93cf37aef33f835c53485f314f50ea906a9d8" + integrity sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ== + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -12379,6 +13582,14 @@ progress@^2.0.3: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + promise.allsettled@^1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.7.tgz#b9dd51e9cffe496243f5271515652c468865f2d8" @@ -12445,6 +13656,11 @@ protobufjs@^7.2.5: "@types/node" ">=13.7.0" long "^5.0.0" +protocol-buffers-schema@^3.3.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03" + integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw== + 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" @@ -12457,6 +13673,14 @@ psl@^1.1.33: dependencies: punycode "^2.3.1" +pump@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.3.tgz#151d979f1a29668dc0025ec589a455b53282268d" + integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -12506,11 +13730,21 @@ queue@6.0.2: dependencies: inherits "~2.0.3" +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + quickselect@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018" integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw== +quickselect@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603" + integrity sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g== + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -12871,6 +14105,13 @@ react@19.0.0: resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd" integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ== +read-binary-file-arch@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz#959c4637daa932280a9b911b1a6766a7e44288fc" + integrity sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg== + dependencies: + debug "^4.3.4" + read-cache@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" @@ -13085,11 +14326,23 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +resedit@^1.7.0: + version "1.7.2" + resolved "https://registry.yarnpkg.com/resedit/-/resedit-1.7.2.tgz#b1041170b99811710c13f949c7d225871de4cc78" + integrity sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA== + dependencies: + pe-library "^0.4.1" + reselect@^4.1.7: version "4.1.8" resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.8.tgz#3f5dc671ea168dccdeb3e141236f69f02eaec524" integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ== +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -13117,6 +14370,13 @@ resolve-pkg-maps@^1.0.0: resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== +resolve-protobuf-schema@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz#9ca9a9e69cf192bbdaf1006ec1973948aa4a3758" + integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ== + dependencies: + protocol-buffers-schema "^3.3.1" + resolve-workspace-root@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz#a0098daa0067cd0efa6eb525c57c8fb4a61e78f8" @@ -13152,6 +14412,13 @@ resolve@~1.7.1: dependencies: path-parse "^1.0.5" +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -13176,6 +14443,11 @@ restore-cursor@^5.0.0: onetime "^7.0.0" signal-exit "^4.1.0" +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + reusify@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" @@ -13200,6 +14472,23 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +roarr@^2.15.3: + version "2.15.4" + resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" + integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== + dependencies: + boolean "^3.0.1" + detect-node "^2.0.4" + globalthis "^1.0.1" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + +robust-predicates@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-2.0.4.tgz#0a2367a93abd99676d075981707f29cfb402248b" + integrity sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg== + rtl-detect@^1.0.2: version "1.1.2" resolved "https://registry.yarnpkg.com/rtl-detect/-/rtl-detect-1.1.2.tgz#ca7f0330af5c6bb626c15675c642ba85ad6273c6" @@ -13227,7 +14516,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@7.8.2, rxjs@^7.5.2, rxjs@^7.8.1: +rxjs@7.8.2, rxjs@^7.5.2, rxjs@^7.8.1, rxjs@^7.8.2: version "7.8.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== @@ -13286,11 +14575,23 @@ safe-regex@^2.1.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sanitize-filename@^1.6.3: + version "1.6.3" + resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" + integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== + dependencies: + truncate-utf8-bytes "^1.0.0" + sax@>=0.6.0: version "1.4.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== +sax@^1.2.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.4.tgz#f29c2bba80ce5b86f4343b4c2be9f2b96627cf8b" + integrity sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw== + saxes@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" @@ -13318,12 +14619,17 @@ sdp@^3.2.0: resolved "https://registry.yarnpkg.com/sdp/-/sdp-3.2.1.tgz#a2f79eecd7c5adb90d54e1bc9812775d80f3c06c" integrity sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw== -"semver@2 || 3 || 4 || 5": +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== + +"semver@2 || 3 || 4 || 5", semver@^5.5.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.3.0, semver@^6.3.1: +semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -13333,6 +14639,11 @@ semver@^7.1.3, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semve resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== +semver@^7.3.2, semver@~7.7.3: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + semver@~7.6.3: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" @@ -13381,6 +14692,13 @@ serialize-error@^2.1.0: resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" integrity sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw== +serialize-error@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" + integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== + dependencies: + type-fest "^0.13.1" + seroval-plugins@~1.3.0: version "1.3.3" resolved "https://registry.yarnpkg.com/seroval-plugins/-/seroval-plugins-1.3.3.tgz#51bcacf09e5384080d7ea4002b08fd9f6166daf5" @@ -13469,7 +14787,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.6.1: +shell-quote@1.8.3, shell-quote@^1.6.1: version "1.8.3" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== @@ -13540,6 +14858,13 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +simple-update-notifier@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz#d70b92bdab7d6d90dfd73931195a30b6e3d7cebb" + integrity sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== + dependencies: + semver "^7.5.3" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -13570,6 +14895,15 @@ slice-ansi@0.0.4: resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" integrity sha512-up04hB2hR92PgjpyU3y/eg91yIBILyjVY26NvvciY3EVVPjybkMszMpXQ9QAkcS3I5rtJBDLoTxxg+qvW8c7rw== +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slice-ansi@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" @@ -13591,6 +14925,11 @@ slugify@^1.3.4, slugify@^1.6.6: resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.6.tgz#2d4ac0eacb47add6af9e04d3be79319cbcc7924b" integrity sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw== +smart-buffer@^4.0.2, smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + snake-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" @@ -13617,6 +14956,23 @@ socket.io-parser@~4.2.4: "@socket.io/component-emitter" "~3.1.0" debug "~4.3.1" +socks-proxy-agent@^8.0.3: + version "8.0.5" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz#b9cdb4e7e998509d7659d689ce7697ac21645bee" + integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== + dependencies: + agent-base "^7.1.2" + debug "^4.3.4" + socks "^2.8.3" + +socks@^2.8.3: + version "2.8.7" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea" + integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== + dependencies: + ip-address "^10.0.1" + smart-buffer "^4.2.0" + solid-floating-ui@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/solid-floating-ui/-/solid-floating-ui-0.3.1.tgz#82999c67777d0f6db6c739641b6868885f595e0e" @@ -13670,7 +15026,7 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@~0.5.20, source-map-support@~0.5.21: +source-map-support@^0.5.19, source-map-support@~0.5.20, source-map-support@~0.5.21: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -13719,6 +15075,11 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz#abf5a08a6f5d7279559b669f47f0a43e8f3464ef" integrity sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ== +splaytree@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/splaytree/-/splaytree-0.1.4.tgz#fc35475248edcc29d4938c9b67e43c6b7e55a659" + integrity sha512-D50hKrjZgBzqD3FT2Ek53f2dcDLAQT8SSGrzj3vidNH5ISRgceeGVJ2dQIthKOuayqFXfFjXheHNo4bbt9LhRQ== + split-on-first@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" @@ -13729,11 +15090,23 @@ split2@^4.0.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== +sprintf-js@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +ssri@^12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-12.0.0.tgz#bcb4258417c702472f8191981d3c8a771fee6832" + integrity sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ== + dependencies: + minipass "^7.0.3" + stable-hash@^0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stable-hash/-/stable-hash-0.0.5.tgz#94e8837aaeac5b4d0f631d2972adef2924b40269" @@ -13782,6 +15155,11 @@ stacktrace-parser@^0.1.10: dependencies: type-fest "^0.7.1" +stat-mode@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465" + integrity sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -14073,6 +15451,27 @@ sucrase@3.35.0, sucrase@^3.32.0: pirates "^4.0.1" ts-interface-checker "^0.1.9" +sumchecker@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" + integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== + dependencies: + debug "^4.1.0" + +supercluster@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-8.0.1.tgz#9946ba123538e9e9ab15de472531f604e7372df5" + integrity sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ== + dependencies: + kdbush "^4.0.2" + +supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -14092,13 +15491,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0, supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-hyperlinks@^2.0.0, supports-hyperlinks@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" @@ -14209,6 +15601,18 @@ tapable@^2.2.0: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.3.tgz#4b67b635b2d97578a06a2713d2f04800c237e99b" integrity sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg== +tar@^6.0.5, tar@^6.1.12: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + tar@^7.4.3: version "7.5.1" resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.1.tgz#750a8bd63b7c44c1848e7bf982260a083cf747c9" @@ -14225,6 +15629,14 @@ temp-dir@~2.0.0: resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" integrity sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg== +temp-file@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/temp-file/-/temp-file-3.4.0.tgz#766ea28911c683996c248ef1a20eea04d51652c7" + integrity sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg== + dependencies: + async-exit-hook "^2.0.1" + fs-extra "^10.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" @@ -14299,6 +15711,13 @@ timm@^1.6.1: resolved "https://registry.yarnpkg.com/timm/-/timm-1.7.1.tgz#96bab60c7d45b5a10a8a4d0f0117c6b7e5aff76f" integrity sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw== +tiny-async-pool@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz#c013e1b369095e7005db5595f95e646cca6ef8a5" + integrity sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA== + dependencies: + semver "^5.5.0" + tinycolor2@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" @@ -14309,7 +15728,7 @@ tinyexec@^1.0.0: resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.1.tgz#70c31ab7abbb4aea0a24f55d120e5990bfa1e0b1" integrity sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw== -tinyglobby@^0.2.13: +tinyglobby@^0.2.12, tinyglobby@^0.2.13: version "0.2.15" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== @@ -14317,6 +15736,18 @@ tinyglobby@^0.2.13: fdir "^6.5.0" picomatch "^4.0.3" +tinyqueue@3.0.0, tinyqueue@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-3.0.0.tgz#101ea761ccc81f979e29200929e78f1556e3661e" + integrity sha512-gRa9gwYU3ECmQYv3lslts5hxuIa90veaEcxDYuu3QGOIAEM2mOZkVHp48ANJuu1CURtRdHKUBY5Lm1tHV+sD4g== + +tmp-promise@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" + integrity sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ== + dependencies: + tmp "^0.2.0" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -14324,7 +15755,7 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -tmp@^0.2.4: +tmp@^0.2.0, tmp@^0.2.4: version "0.2.5" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.5.tgz#b06bcd23f0f3c8357b426891726d16015abfd8f8" integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== @@ -14376,6 +15807,18 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tree-kill@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +truncate-utf8-bytes@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" + integrity sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ== + dependencies: + utf8-byte-length "^1.0.1" + ts-api-utils@^1.3.0: version "1.4.3" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" @@ -14478,6 +15921,11 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -14658,6 +16106,20 @@ 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-filename@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-4.0.0.tgz#a06534d370e7c977a939cd1d11f7f0ab8f1fed13" + integrity sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ== + dependencies: + unique-slug "^5.0.0" + +unique-slug@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-5.0.0.tgz#ca72af03ad0dbab4dad8aa683f633878b1accda8" + integrity sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg== + dependencies: + imurmurhash "^0.1.4" + unique-string@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -14665,6 +16127,11 @@ unique-string@~2.0.0: dependencies: crypto-random-string "^2.0.0" +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" @@ -14763,6 +16230,11 @@ usehooks-ts@3.1.1: dependencies: lodash.debounce "^4.0.8" +utf8-byte-length@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e" + integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== + utif2@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/utif2/-/utif2-4.1.0.tgz#e768d37bd619b995d56d9780b5d2b4611a3d932b" @@ -14822,6 +16294,15 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +verror@^1.10.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.1.tgz#4bf09eeccf4563b109ed4b3d458380c972b0cdeb" + integrity sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg== + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + vlq@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/vlq/-/vlq-1.0.1.tgz#c003f6e7c0b4c1edd623fd6ee50bbc0d6a1de468" @@ -14839,6 +16320,17 @@ w3c-xmlserializer@^4.0.0: dependencies: xml-name-validator "^4.0.0" +wait-on@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-9.0.3.tgz#3ea858db0b854039e6aff5f323885aaef25e32bf" + integrity sha512-13zBnyYvFDW1rBvWiJ6Av3ymAaq8EDQuvxZnPIw3g04UqGi4TyoIJABmfJ6zrvKo9yeFQExNkOk7idQbDJcuKA== + dependencies: + axios "^1.13.2" + joi "^18.0.1" + lodash "^4.17.21" + minimist "^1.2.8" + rxjs "^7.8.2" + walker@^1.0.7, walker@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" @@ -15024,6 +16516,13 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +which@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/which/-/which-5.0.0.tgz#d93f2d93f79834d4363c7d0c23e00d07c466c8d6" + integrity sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ== + dependencies: + isexe "^3.1.1" + widest-line@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-5.0.0.tgz#b74826a1e480783345f0cd9061b49753c9da70d0" @@ -15198,7 +16697,7 @@ xml@^1.0.1: resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== -xmlbuilder@^15.1.1: +xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== @@ -15233,6 +16732,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yallist@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" @@ -15253,7 +16757,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.7.2: +yargs@17.7.2, yargs@^17.0.0, yargs@^17.0.1, 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== @@ -15266,6 +16770,14 @@ yargs@^17.0.0, yargs@^17.3.1, yargs@^17.6.2, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 88517a76011419f43539ec5592c888db4e4e4164 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Sun, 25 Jan 2026 10:44:02 -0800 Subject: [PATCH 2/5] RU-T46 Working on other builds, fixing Android PTT crashes --- .dockerignore | 70 ++++ .github/workflows/react-native-cicd.yml | 364 +++++++++++++++++- Dockerfile | 3 +- README.md | 36 ++ docker/README.md | 303 +++++++++++++++ docs/android-ptt-foreground-service-fix.md | 59 +++ docs/build-quick-reference.md | 185 +++++++++ docs/cicd-build-system.md | 242 ++++++++++++ electron-builder.config.js | 8 +- electron/main.js | 56 +-- expo-env.d.ts | 2 +- metro.config.js | 3 +- package.json | 2 +- public/service-worker.js | 2 + src/app/(app)/index.tsx | 6 +- .../maps/full-screen-location-picker.tsx | 6 +- src/components/maps/location-picker.tsx | 6 +- src/components/maps/map-pins.tsx | 2 +- src/components/maps/map-view.web.tsx | 198 +++++----- src/components/maps/mapbox.ts | 60 +-- src/components/maps/pin-marker.tsx | 4 +- src/components/maps/static-map.tsx | 2 +- src/hooks/use-map-signalr-updates.ts | 2 +- src/hooks/use-signalr-lifecycle.ts | 4 +- src/lib/env.js | 6 +- src/lib/platform.ts | 32 +- src/services/bluetooth-audio.service.ts | 58 ++- src/services/media-button.service.ts | 6 +- src/services/offline-event-manager.service.ts | 2 +- src/services/push-notification.electron.ts | 5 +- src/stores/app/bluetooth-audio-store.ts | 7 +- src/stores/app/livekit-store.ts | 3 +- yarn.lock | 18 +- 33 files changed, 1479 insertions(+), 283 deletions(-) create mode 100644 .dockerignore create mode 100644 docker/README.md create mode 100644 docs/android-ptt-foreground-service-fix.md create mode 100644 docs/build-quick-reference.md create mode 100644 docs/cicd-build-system.md diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..8162d425 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,70 @@ +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Testing +coverage/ +.nyc_output/ + +# Production builds +dist/ +build/ +electron-dist/ +.expo/ +.expo-shared/ + +# Mobile +android/ +ios/ +.gradle/ + +# Misc +.DS_Store +*.pem +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Editor directories +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Git +.git/ +.gitignore + +# Documentation +docs/ +*.md +!README.md + +# CI/CD +.github/ + +# Secrets and keys +*.p8 +*.pem +*.key +*.p12 +google-services.json +GoogleService-Info.plist + +# Temporary files +tmp/ +temp/ +*.tmp + +# OS files +Thumbs.db diff --git a/.github/workflows/react-native-cicd.yml b/.github/workflows/react-native-cicd.yml index dca52ae4..4e0af743 100644 --- a/.github/workflows/react-native-cicd.yml +++ b/.github/workflows/react-native-cicd.yml @@ -112,7 +112,7 @@ jobs: - name: 🧪 Run Checks and Tests run: yarn check-all - build-and-deploy: + build-mobile: needs: test if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch' strategy: @@ -474,3 +474,365 @@ jobs: name: '7.${{ github.run_number }}' artifacts: './ResgridUnit-prod.apk' bodyFile: 'RELEASE_NOTES.md' + build-web: + needs: test + if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + environment: RNBuild + steps: + - name: 🏗 Checkout repository + uses: actions/checkout@v4 + + - name: 🏗 Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'yarn' + + - name: 📦 Setup yarn cache + uses: actions/cache@v3 + with: + path: | + ~/.cache/yarn + node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: 📦 Install dependencies + run: yarn install --frozen-lockfile + + - name: 🌐 Build Web Application + run: | + export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096" + yarn web:build + env: + NODE_ENV: production + + - name: 📦 Upload web build artifacts + uses: actions/upload-artifact@v4 + with: + name: web-build + path: dist/ + retention-days: 7 + + build-docker: + needs: build-web + if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + environment: RNBuild + steps: + - name: 🏗 Checkout repository + uses: actions/checkout@v4 + + - name: 🐳 Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: 🐳 Log in to Docker Hub + if: ${{ secrets.DOCKER_USERNAME != '' && secrets.DOCKER_PASSWORD != '' }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: 🐳 Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: 📋 Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ secrets.DOCKER_USERNAME != '' && format('{0}/resgrid-unit', secrets.DOCKER_USERNAME) || '' }} + ghcr.io/${{ github.repository }} + tags: | + type=raw,value=7.${{ github.run_number }} + type=raw,value=latest + type=sha,prefix={{branch}}- + + - name: 🐳 Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + VERSION=7.${{ github.run_number }} + platforms: linux/amd64,linux/arm64 + + build-electron: + needs: test + if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch' + strategy: + matrix: + os: [windows-latest, macos-15, ubuntu-latest] + runs-on: ${{ matrix.os }} + environment: RNBuild + steps: + - name: 🏗 Checkout repository + uses: actions/checkout@v4 + + - name: 🏗 Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'yarn' + + - name: 📦 Setup yarn cache + uses: actions/cache@v3 + with: + path: | + ~/.cache/yarn + node_modules + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: 📦 Install dependencies + run: yarn install --frozen-lockfile + + - name: 📋 Update package.json Versions + shell: bash + run: | + # Ensure jq exists on all platforms + if ! command -v jq >/dev/null 2>&1; then + echo "Installing jq..." + if [[ "$RUNNER_OS" == "Linux" ]]; then + sudo apt-get update && sudo apt-get install -y jq + elif [[ "$RUNNER_OS" == "macOS" ]]; then + brew update && brew install jq + elif [[ "$RUNNER_OS" == "Windows" ]]; then + choco install jq -y + else + echo "Unsupported OS for auto-install of jq" >&2; exit 1 + fi + fi + + # Update the package.json + if [ -f ./package.json ]; then + cp package.json package.json.bak + jq '.version = "7.${{ github.run_number }}"' package.json > package.json.tmp && mv package.json.tmp package.json + echo "Updated package.json version" + cat package.json | grep "version" + else + echo "package.json not found" + exit 1 + fi + + - name: 🖥️ Build Electron (Windows) + if: matrix.os == 'windows-latest' + run: | + $env:NODE_OPTIONS = "--openssl-legacy-provider --max_old_space_size=4096" + yarn electron:build:win + env: + NODE_ENV: production + + - name: 🖥️ Build Electron (macOS) + if: matrix.os == 'macos-15' + run: | + export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096" + yarn electron:build:mac + env: + NODE_ENV: production + + - name: 🖥️ Build Electron (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + export NODE_OPTIONS="--openssl-legacy-provider --max_old_space_size=4096" + yarn electron:build:linux + env: + NODE_ENV: production + + - name: 📦 Upload Electron artifacts (Windows) + if: matrix.os == 'windows-latest' + uses: actions/upload-artifact@v4 + with: + name: electron-windows + path: | + electron-dist/*.exe + electron-dist/*.msi + retention-days: 7 + + - name: 📦 Upload Electron artifacts (macOS) + if: matrix.os == 'macos-15' + uses: actions/upload-artifact@v4 + with: + name: electron-macos + path: | + electron-dist/*.dmg + electron-dist/*.zip + retention-days: 7 + + - name: 📦 Upload Electron artifacts (Linux) + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v4 + with: + name: electron-linux + path: | + electron-dist/*.AppImage + electron-dist/*.deb + electron-dist/*.rpm + retention-days: 7 + + - name: 📋 Prepare Release Notes file + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + set -eo pipefail + + # Function to extract release notes from PR body + extract_release_notes() { + local body="$1" + + # First pass: Remove everything between CodeRabbit comment markers using sed + # This handles multi-line auto-generated content + local cleaned_body="$(printf '%s\n' "$body" \ + | sed '//,//d')" + + # Second pass: Remove the "Summary by CodeRabbit" section + cleaned_body="$(printf '%s\n' "$cleaned_body" \ + | awk ' + BEGIN { skip=0 } + /^## Summary by CodeRabbit/ { skip=1; next } + /^## / && skip==1 { skip=0 } + skip==0 { print } + ')" + + # Third pass: Remove any remaining HTML comment lines + cleaned_body="$(printf '%s\n' "$cleaned_body" | sed '/^$/d' | sed '/^$/d')" + + # Fourth pass: Remove specific CodeRabbit lines + cleaned_body="$(printf '%s\n' "$cleaned_body" \ + | (grep -v '✏️ Tip: You can customize this high-level summary in your review settings\.' || true) \ + | (grep -v '' || true) \ + | (grep -v '' || true))" + + # Fifth pass: Trim leading and trailing whitespace/empty lines + cleaned_body="$(printf '%s\n' "$cleaned_body" | sed '/^$/d' | awk 'NF {p=1} p')" + + # Try to extract content under "## Release Notes" heading if it exists + local notes="$(printf '%s\n' "$cleaned_body" \ + | awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')" + + # If no specific "Release Notes" section found, use the entire cleaned body + if [ -z "$notes" ]; then + notes="$cleaned_body" + fi + + # Final trim + notes="$(printf '%s\n' "$notes" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + + 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 + echo "PR body length: ${#PR_BODY}" + NOTES="$(extract_release_notes "$PR_BODY")" + echo "Extracted notes length: ${#NOTES}" + else + echo "Warning: PR body is empty" + 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 + echo "PR body length: ${#PR_BODY}" + NOTES="$(extract_release_notes "$PR_BODY")" + echo "Extracted notes length: ${#NOTES}" + else + echo "Warning: PR body is empty" + fi + else + echo "No associated PR found for this commit" + fi + fi + fi + + # Fallback to recent commits if no PR body found (skip merge commits) + if [ -z "$NOTES" ]; then + echo "No PR body found, using recent commits (excluding merge commits)..." + NOTES="$(git log -n 10 --pretty=format:'- %s' --no-merges | head -n 5)" + 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: � Download all Electron artifacts + if: ${{ matrix.os == 'ubuntu-latest' }} + uses: actions/download-artifact@v4 + with: + path: electron-artifacts/ + pattern: electron-* + merge-multiple: false + + - name: 📦 Prepare Electron release artifacts + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + # Create release-artifacts directory + mkdir -p release-artifacts + + # Copy all Electron artifacts to a flat directory for release + find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) -exec cp {} release-artifacts/ \; + + echo "Release artifacts prepared:" + ls -lh release-artifacts/ + + - name: 📦 Create GitHub Release with Electron builds + if: ${{ matrix.os == 'ubuntu-latest' }} + uses: ncipollo/release-action@v1 + with: + tag: '7.${{ github.run_number }}' + commit: ${{ github.sha }} + makeLatest: true + allowUpdates: true + name: '7.${{ github.run_number }}' + artifacts: 'release-artifacts/*' + bodyFile: 'RELEASE_NOTES.md' diff --git a/Dockerfile b/Dockerfile index 51e8e71d..48fe4d72 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,8 @@ RUN yarn install --frozen-lockfile # Copy source files COPY . . -# Build the web application +# Build the web application without environment variables +# Environment variables will be injected at runtime via docker-entrypoint.sh RUN yarn web:build ### STAGE 2: Run ### diff --git a/README.md b/README.md index 3c6b7517..fd308c18 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,42 @@ - Realtime Geolocation - Automatic Vehicle Location (AVL) +### :package: Deployment Options + +Resgrid Unit can be deployed in multiple ways: + +#### 📱 Mobile Applications +- **Android**: APK and AAB builds for Google Play Store and direct distribution +- **iOS**: IPA builds for App Store and enterprise distribution +- Download from [GitHub Releases](https://github.com/Resgrid/Unit/releases) or Firebase App Distribution + +#### 🌐 Web Application +- Static web build that can be hosted on any web server or CDN +- Fully responsive and works on tablets and desktops +- See [Build Quick Reference](docs/build-quick-reference.md) for building + +#### 🐳 Docker Container +- Pre-built multi-architecture Docker images (amd64, arm64) +- All configuration via environment variables at runtime +- Suitable for Kubernetes, Docker Compose, or standalone deployment +- Pull from GitHub Container Registry: + ```bash + docker pull ghcr.io/resgrid/unit:latest + docker run -p 8080:80 -e UNIT_BASE_API_URL="..." ghcr.io/resgrid/unit:latest + ``` +- See [Docker Deployment Guide](docker/README.md) for details + +#### 🖥️ Desktop Applications (Electron) +- **Windows**: Portable exe and NSIS installer +- **macOS**: DMG and ZIP (Universal: x64 + arm64) +- **Linux**: AppImage, deb, and rpm packages +- Download from [GitHub Releases](https://github.com/Resgrid/Unit/releases) + +For detailed deployment instructions, see: +- [CI/CD Build System Documentation](docs/cicd-build-system.md) +- [Docker Deployment Guide](docker/README.md) +- [Build Quick Reference](docs/build-quick-reference.md) + ### :key: Environment Variables To run this project, you will need to add the following environment variables to your .env file diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..d672e2f4 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,303 @@ +# Docker Deployment Guide + +This guide explains how to deploy the Resgrid Unit application using Docker. + +## Quick Start + +### Using Docker Hub or GitHub Container Registry + +```bash +# Pull from GitHub Container Registry +docker pull ghcr.io/resgrid/unit:latest + +# Or pull from Docker Hub (if configured) +docker pull /resgrid-unit:latest + +# Run the container +docker run -d \ + -p 8080:80 \ + -e UNIT_BASE_API_URL="https://api.example.com" \ + -e UNIT_APP_KEY="your-app-key" \ + --name resgrid-unit \ + ghcr.io/resgrid/unit:latest +``` + +### Building Locally + +```bash +# Build the Docker image +docker build -t resgrid-unit:latest . + +# Run the container +docker run -d \ + -p 8080:80 \ + -e UNIT_BASE_API_URL="https://api.example.com" \ + --name resgrid-unit \ + resgrid-unit:latest +``` + +## Environment Variables + +All configuration is done via environment variables at runtime. The Docker image does **not** contain any hardcoded secrets or API keys. + +### Required Variables + +- `UNIT_BASE_API_URL` - Base URL for the API (e.g., `https://api.resgrid.com`) + +### Optional Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `APP_ENV` | `production` | Application environment | +| `UNIT_NAME` | `Resgrid Unit` | Application name | +| `UNIT_SCHEME` | `ResgridUnit` | URL scheme | +| `UNIT_VERSION` | `0.0.1` | Application version | +| `UNIT_API_VERSION` | `v4` | API version | +| `UNIT_RESGRID_API_URL` | `/api/v4` | Resgrid API URL path | +| `UNIT_CHANNEL_HUB_NAME` | `eventingHub` | SignalR channel hub name | +| `UNIT_REALTIME_GEO_HUB_NAME` | `geolocationHub` | SignalR geolocation hub name | +| `UNIT_LOGGING_KEY` | `""` | Logging service key | +| `UNIT_APP_KEY` | `""` | Application key | +| `UNIT_MAPBOX_PUBKEY` | `""` | Mapbox public key | +| `UNIT_SENTRY_DSN` | `""` | Sentry DSN for error tracking | +| `UNIT_COUNTLY_APP_KEY` | `""` | Countly app key for analytics | +| `UNIT_COUNTLY_SERVER_URL` | `""` | Countly server URL | + +## Docker Compose + +Create a `docker-compose.yml` file: + +```yaml +version: '3.8' + +services: + resgrid-unit: + image: ghcr.io/resgrid/unit:latest + ports: + - "8080:80" + environment: + - APP_ENV=production + - UNIT_NAME=Resgrid Unit + - UNIT_SCHEME=ResgridUnit + - UNIT_VERSION=7.1 + - UNIT_BASE_API_URL=https://api.resgrid.com + - UNIT_API_VERSION=v4 + - UNIT_RESGRID_API_URL=/api/v4 + - UNIT_CHANNEL_HUB_NAME=eventingHub + - UNIT_REALTIME_GEO_HUB_NAME=geolocationHub + - UNIT_LOGGING_KEY=${UNIT_LOGGING_KEY} + - UNIT_APP_KEY=${UNIT_APP_KEY} + - UNIT_MAPBOX_PUBKEY=${UNIT_MAPBOX_PUBKEY} + - UNIT_SENTRY_DSN=${UNIT_SENTRY_DSN} + - UNIT_COUNTLY_APP_KEY=${UNIT_COUNTLY_APP_KEY} + - UNIT_COUNTLY_SERVER_URL=${UNIT_COUNTLY_SERVER_URL} + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s +``` + +Then run: + +```bash +docker-compose up -d +``` + +## Using Environment Files + +Create a `.env` file (never commit this to version control): + +```env +UNIT_BASE_API_URL=https://api.resgrid.com +UNIT_APP_KEY=your-secret-app-key +UNIT_LOGGING_KEY=your-logging-key +UNIT_MAPBOX_PUBKEY=your-mapbox-public-key +UNIT_SENTRY_DSN=your-sentry-dsn +UNIT_COUNTLY_APP_KEY=your-countly-app-key +UNIT_COUNTLY_SERVER_URL=https://countly.example.com +``` + +Run with the environment file: + +```bash +docker run -d \ + -p 8080:80 \ + --env-file .env \ + --name resgrid-unit \ + ghcr.io/resgrid/unit:latest +``` + +## Kubernetes Deployment + +Create a `deployment.yaml`: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: resgrid-unit-config +data: + UNIT_BASE_API_URL: "https://api.resgrid.com" + UNIT_API_VERSION: "v4" + UNIT_NAME: "Resgrid Unit" + +--- +apiVersion: v1 +kind: Secret +metadata: + name: resgrid-unit-secrets +type: Opaque +stringData: + UNIT_APP_KEY: "your-secret-app-key" + UNIT_LOGGING_KEY: "your-logging-key" + UNIT_MAPBOX_PUBKEY: "your-mapbox-public-key" + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: resgrid-unit +spec: + replicas: 3 + selector: + matchLabels: + app: resgrid-unit + template: + metadata: + labels: + app: resgrid-unit + spec: + containers: + - name: resgrid-unit + image: ghcr.io/resgrid/unit:latest + ports: + - containerPort: 80 + envFrom: + - configMapRef: + name: resgrid-unit-config + - secretRef: + name: resgrid-unit-secrets + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "200m" + livenessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 30 + periodSeconds: 10 + readinessProbe: + httpGet: + path: / + port: 80 + initialDelaySeconds: 5 + periodSeconds: 5 + +--- +apiVersion: v1 +kind: Service +metadata: + name: resgrid-unit +spec: + type: LoadBalancer + ports: + - port: 80 + targetPort: 80 + selector: + app: resgrid-unit +``` + +Deploy: + +```bash +kubectl apply -f deployment.yaml +``` + +## How It Works + +The Docker image uses a two-stage build: + +1. **Build Stage**: Compiles the web application without any environment variables +2. **Runtime Stage**: Uses nginx to serve the application + +At container startup, the `docker-entrypoint.sh` script: +1. Generates an `env-config.js` file with all environment variables +2. Injects the script tag into `index.html` +3. Starts nginx + +This approach allows the same Docker image to be used across multiple environments (dev, staging, production) by simply changing environment variables. + +## Security Best Practices + +1. **Never commit secrets**: Keep sensitive environment variables in secure storage (e.g., Kubernetes Secrets, AWS Secrets Manager) +2. **Use read-only containers**: Run containers in read-only mode where possible +3. **Scan for vulnerabilities**: Regularly scan the Docker image for security issues +4. **Use non-root user**: The nginx base image already uses a non-root user +5. **Limit resources**: Set appropriate CPU and memory limits + +## Troubleshooting + +### View container logs + +```bash +docker logs resgrid-unit +``` + +### Verify environment variables are injected + +```bash +docker exec resgrid-unit cat /usr/share/nginx/html/env-config.js +``` + +### Access container shell + +```bash +docker exec -it resgrid-unit sh +``` + +### Check nginx configuration + +```bash +docker exec resgrid-unit cat /etc/nginx/nginx.conf +``` + +## Multi-Architecture Support + +The CI/CD pipeline builds images for both `linux/amd64` and `linux/arm64` architectures, ensuring compatibility with: +- x86-64 servers +- ARM-based servers (AWS Graviton, Raspberry Pi, etc.) +- Apple Silicon (M1/M2) development machines + +## Updating the Application + +To update to a new version: + +```bash +# Pull the latest image +docker pull ghcr.io/resgrid/unit:latest + +# Stop and remove the old container +docker stop resgrid-unit +docker rm resgrid-unit + +# Start a new container with the updated image +docker run -d \ + -p 8080:80 \ + --env-file .env \ + --name resgrid-unit \ + ghcr.io/resgrid/unit:latest +``` + +Or with Docker Compose: + +```bash +docker-compose pull +docker-compose up -d +``` diff --git a/docs/android-ptt-foreground-service-fix.md b/docs/android-ptt-foreground-service-fix.md new file mode 100644 index 00000000..6603a602 --- /dev/null +++ b/docs/android-ptt-foreground-service-fix.md @@ -0,0 +1,59 @@ +# Android PTT Foreground Service Type Fix + +## Issue +When attempting to join a PTT (Push-to-Talk) call on Android, the app crashed with a `SecurityException`: + +``` +java.lang.SecurityException: Starting FGS with type microphone callerApp=ProcessRecord{...} targetSDK=35 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_MICROPHONE] any of the permissions allOf=false [android.permission.RECORD_AUDIO, ...] +``` + +## Root Cause +Starting with Android 14 (API 34), and strictly enforced in Android 15 (SDK 35), foreground services must explicitly declare their service type when started. The app's `AndroidManifest.xml` correctly declared: +- `android.permission.FOREGROUND_SERVICE_MICROPHONE` permission +- The `ForegroundService` with `android:foregroundServiceType="microphone|mediaPlayback|connectedDevice"` +- `android.permission.RECORD_AUDIO` permission + +However, when displaying the notification to start the foreground service, the notification configuration did not specify the `foregroundServiceTypes` parameter. + +## Solution +Added the `foregroundServiceTypes` parameter to the Notifee notification configuration when starting the PTT call foreground service. + +### Changes Made + +#### 1. Updated livekit-store.ts imports +Added `AndroidForegroundServiceType` to the Notifee imports: + +```typescript +import notifee, { AndroidForegroundServiceType, AndroidImportance } from '@notifee/react-native'; +``` + +#### 2. Updated Notification Configuration +Modified the foreground service notification to include the service type: + +```typescript +await notifee.displayNotification({ + title: 'Active PTT Call', + body: 'There is an active PTT call in progress.', + android: { + channelId: 'notif', + asForegroundService: true, + foregroundServiceTypes: [AndroidForegroundServiceType.MICROPHONE], + smallIcon: 'ic_launcher', + }, +}); +``` + +## Files Modified +- `src/stores/app/livekit-store.ts` + +## Testing +To verify the fix: +1. Rebuild the Android app (`yarn prebuild --clean` then build) +2. Join a PTT call +3. Verify the foreground service notification appears +4. Ensure no crash occurs when joining the call + +## References +- [Android Foreground Services Documentation](https://developer.android.com/develop/background-work/services/foreground-services) +- [Notifee Android Foreground Service Types](https://notifee.app/react-native/docs/android/foreground-service) +- Android 14+ requires explicit foreground service type declarations diff --git a/docs/build-quick-reference.md b/docs/build-quick-reference.md new file mode 100644 index 00000000..6554a41f --- /dev/null +++ b/docs/build-quick-reference.md @@ -0,0 +1,185 @@ +# Quick Build Reference + +## Manual Build Trigger + +Go to: **Actions** → **React Native CI/CD** → **Run workflow** + +### Build Type Options + +| Build Type | Description | Platform | +|------------|-------------|----------| +| `dev` | Development Android APK | Android | +| `prod-apk` | Production Android APK | Android | +| `prod-aab` | Production Android AAB (for Play Store) | Android | +| `ios-dev` | Development iOS IPA | iOS | +| `ios-adhoc` | Ad-Hoc iOS IPA (for testing) | iOS | +| `ios-prod` | Production iOS IPA (for App Store) | iOS | +| `all` | All build types above | Both | + +**Note**: Web, Docker, and Electron builds run automatically on push to main/master. + +## Automatic Builds + +Triggered on push to `main` or `master` branch: + +1. ✅ **Tests** run first (lint, type-check, unit tests) +2. ✅ **Mobile Builds** (Android & iOS) + - Development, Production APK/AAB/IPA + - Uploaded to Firebase App Distribution + - APK added to GitHub Release +3. ✅ **Web Build** + - Static export for hosting + - Artifact uploaded to GitHub Actions +4. ✅ **Docker Build** + - Multi-architecture (amd64, arm64) + - Published to GitHub Container Registry + - Published to Docker Hub (if configured) +5. ✅ **Electron Builds** + - Windows: .exe, .msi + - macOS: .dmg, .zip (Universal) + - Linux: .AppImage, .deb, .rpm + - All added to GitHub Release + +## Skip CI + +Add `[skip ci]` to commit message to skip all builds: + +```bash +git commit -m "docs: update README [skip ci]" +``` + +## Build Artifacts + +### GitHub Actions Artifacts (7-day retention) +- `app-builds-android` - Android APK/AAB files +- `app-builds-ios` - iOS IPA files +- `web-build` - Web dist folder +- `electron-windows` - Windows installers +- `electron-macos` - macOS packages +- `electron-linux` - Linux packages + +### GitHub Container Registry +```bash +docker pull ghcr.io/resgrid/unit:latest +docker pull ghcr.io/resgrid/unit:7.{build-number} +``` + +### Docker Hub (optional) +```bash +docker pull {username}/resgrid-unit:latest +``` + +### GitHub Releases +Each release includes: +- Android APK +- All Electron builds (Windows, macOS, Linux) +- Release notes from PR + +### Firebase App Distribution +- Android: Production APK → testers group +- iOS: Ad-Hoc IPA → testers group + +## Version Numbering + +Format: `7.{github.run_number}` + +Example: `7.123` where 123 is the GitHub Actions run number + +Updated in: +- package.json (`version` and `versionCode`) +- Docker image tags +- GitHub Release tag +- Electron app version + +## Environment Configuration + +### Mobile & Web Builds +Use UNIT_* secrets from GitHub Secrets during build time. + +### Docker Deployment +Set UNIT_* variables at container runtime: + +```bash +docker run \ + -e UNIT_BASE_API_URL="https://api.resgrid.com" \ + -e UNIT_APP_KEY="your-key" \ + -e UNIT_MAPBOX_PUBKEY="your-key" \ + ghcr.io/resgrid/unit:latest +``` + +See [docker/README.md](../docker/README.md) for complete Docker deployment guide. + +## Required Secrets + +### Already Configured (for mobile) +- EXPO_TOKEN, FIREBASE_TOKEN +- UNIT_* environment variables +- Apple/Google signing credentials + +### Optional (for Docker Hub) +- DOCKER_USERNAME +- DOCKER_PASSWORD + +If not set, Docker images will only be pushed to GitHub Container Registry. + +## Troubleshooting + +### Build Failed +1. Check GitHub Actions logs +2. Look for error in specific build step +3. Verify all required secrets are set + +### Docker Image Not Found +- Check GitHub Container Registry permissions +- Verify image was pushed: `docker pull ghcr.io/resgrid/unit:latest` + +### Electron Build Issues +- Ensure platform-specific dependencies installed +- Check `electron-builder.config.js` for configuration + +### Missing Release +- Only created on Android build completion +- Updated by Electron Linux build +- Check build logs for errors + +## Local Testing + +### Test Web Build +```bash +yarn web:build +# Output in dist/ +``` + +### Test Docker Build +```bash +docker build -t resgrid-unit:test . +docker run -p 8080:80 \ + -e UNIT_BASE_API_URL="https://api.resgrid.com" \ + resgrid-unit:test +# Access at http://localhost:8080 +``` + +### Test Electron Build +```bash +# macOS +yarn electron:build:mac + +# Windows (on Windows) +yarn electron:build:win + +# Linux +yarn electron:build:linux +``` + +## Performance Tips + +- Builds run in parallel where possible +- Caching enabled for yarn, EAS, and Docker +- Multi-stage Docker builds for smaller images +- Artifacts automatically cleaned up after 7 days + +## Getting Help + +- Workflow file: [.github/workflows/react-native-cicd.yml](../.github/workflows/react-native-cicd.yml) +- Full docs: [docs/cicd-build-system.md](cicd-build-system.md) +- Docker guide: [docker/README.md](../docker/README.md) diff --git a/docs/cicd-build-system.md b/docs/cicd-build-system.md new file mode 100644 index 00000000..7cebd9c9 --- /dev/null +++ b/docs/cicd-build-system.md @@ -0,0 +1,242 @@ +# CI/CD Build System Extensions + +This document describes the extensions made to the GitHub Actions CI/CD workflow to support web, Docker, and Electron builds. + +## Overview + +The build system now supports: +- ✅ Mobile builds (Android & iOS) - existing +- ✅ Web builds +- ✅ Docker builds (multi-architecture) +- ✅ Electron builds (Windows, macOS, Linux) + +## Build Jobs + +### 1. Mobile Builds (`build-mobile`) + +**Platforms**: Android, iOS +**Runners**: ubuntu-latest (Android), macos-15 (iOS) +**Outputs**: +- Android: APK (dev/prod) and AAB (prod) +- iOS: IPA (dev/adhoc/prod) + +**Distribution**: +- Firebase App Distribution +- GitHub Releases + +### 2. Web Build (`build-web`) + +**Platform**: Web +**Runner**: ubuntu-latest +**Build Command**: `yarn web:build` +**Output**: `dist/` directory + +**Artifacts**: Uploaded to GitHub Actions artifacts + +### 3. Docker Build (`build-docker`) + +**Platform**: Linux (multi-arch) +**Runner**: ubuntu-latest +**Architectures**: linux/amd64, linux/arm64 + +**Key Features**: +- ✅ No hardcoded UNIT_ environment variables +- ✅ All configuration via runtime environment variables +- ✅ Multi-architecture support +- ✅ Automated tagging (version, latest, SHA) + +**Registries**: +- GitHub Container Registry (ghcr.io) - always enabled +- Docker Hub - optional (if credentials configured) + +**Tags Generated**: +- `7.{build_number}` - version tag +- `latest` - latest build +- `{branch}-{sha}` - git reference + +**Environment Variables** (set at runtime): +All UNIT_* variables are configured via Docker environment variables at container startup, not baked into the image. + +### 4. Electron Builds (`build-electron`) + +**Platforms**: Windows, macOS, Linux +**Runners**: +- windows-latest (Windows) +- macos-15 (macOS) +- ubuntu-latest (Linux) + +**Build Commands**: +- Windows: `yarn electron:build:win` +- macOS: `yarn electron:build:mac` +- Linux: `yarn electron:build:linux` + +**Outputs**: +- Windows: .exe, .msi (NSIS installer) +- macOS: .dmg, .zip (x64 and arm64) +- Linux: .AppImage, .deb, .rpm + +**Artifacts**: All builds uploaded to GitHub Actions artifacts and GitHub Releases + +## Build Matrix + +```yaml +build-mobile: + matrix: + platform: [android, ios] + +build-electron: + matrix: + os: [windows-latest, macos-15, ubuntu-latest] +``` + +## Environment Variables + +### Build-Time Variables (for mobile/web/electron) +All UNIT_* secrets are used during build for mobile platforms. + +### Runtime Variables (for Docker only) +Docker images do NOT contain UNIT_* variables. These must be set when running containers: + +```bash +docker run -e UNIT_BASE_API_URL="..." -e UNIT_APP_KEY="..." ... +``` + +See [docker/README.md](../docker/README.md) for complete Docker deployment guide. + +## Secrets Required + +### Existing Secrets (for mobile builds) +- EXPO_TOKEN +- FIREBASE_TOKEN +- FIREBASE_ANDROID_APP_ID +- FIREBASE_IOS_APP_ID +- All UNIT_* secrets +- Apple/Google signing credentials + +### New Secrets (for Docker registry) +- `DOCKER_USERNAME` (optional) - Docker Hub username +- `DOCKER_PASSWORD` (optional) - Docker Hub password +- `GITHUB_TOKEN` (automatic) - GitHub Container Registry + +### No Additional Secrets Needed +- Web builds: use existing secrets +- Electron builds: use existing configuration + +## Workflow Triggers + +All jobs run on: +- Push to `main` or `master` branch +- Manual workflow dispatch + +Skip with: `[skip ci]` in commit message + +## Artifact Retention + +All artifacts are retained for **7 days**. + +## Release Strategy + +### GitHub Releases +- **Mobile**: Created by Android build with APK +- **Electron**: Updated by Linux build with all Electron artifacts +- **Tag**: `7.{github.run_number}` +- **Release Notes**: Extracted from PR body or recent commits + +### Container Registries +- **GitHub Container Registry**: Always published +- **Docker Hub**: Published if credentials configured + +### Firebase App Distribution +- **Android**: Production APK +- **iOS**: Ad-hoc IPA + +## Cache Strategy + +1. **Yarn Cache**: Shared across all jobs +2. **EAS Build Cache**: For mobile builds +3. **Docker Build Cache**: GitHub Actions cache for multi-arch builds + +## Build Optimization + +- Node.js 24 with legacy OpenSSL provider +- Increased memory: `--max_old_space_size=4096` +- Frozen lockfile: `--frozen-lockfile` +- Multi-stage Docker builds +- Parallel matrix jobs + +## Platform-Specific Notes + +### Docker +- Uses nginx to serve static files +- Runtime environment variable injection via `docker-entrypoint.sh` +- Health checks supported +- Suitable for Kubernetes deployment + +### Electron +- Separate builds for each platform +- macOS: Universal binaries (x64 + arm64) +- Windows: NSIS installer with customization +- Linux: Multiple formats (AppImage, deb, rpm) + +### Web +- Static export via Expo +- Can be deployed to any static hosting +- Same build used as base for Docker image + +## Build Flow + +``` +test (all platforms) + ├─→ build-mobile (android, ios) + ├─→ build-web + │ └─→ build-docker + └─→ build-electron (windows, macos, linux) +``` + +## Monitoring and Debugging + +### View Logs +- GitHub Actions UI: Real-time build logs +- Docker logs: `docker logs ` +- Electron: Check build output in `electron-dist/` + +### Common Issues +1. **Docker build fails**: Check Dockerfile and build context +2. **Electron build fails**: Ensure all dependencies installed +3. **Missing artifacts**: Check job conditions and file paths + +## Future Enhancements + +Potential improvements: +- [ ] Code signing for Electron apps +- [ ] Auto-update mechanism for Electron +- [ ] Deploy web build to CDN +- [ ] E2E testing in Docker container +- [ ] Performance benchmarking +- [ ] Security scanning (Snyk, Trivy) + +## Testing Locally + +### Web +```bash +yarn web:build +``` + +### Docker +```bash +docker build -t resgrid-unit:test . +docker run -p 8080:80 -e UNIT_BASE_API_URL="..." resgrid-unit:test +``` + +### Electron +```bash +yarn electron:build:mac # macOS +yarn electron:build:win # Windows +yarn electron:build:linux # Linux +``` + +## Additional Documentation + +- Docker deployment: [docker/README.md](../docker/README.md) +- Electron configuration: [electron-builder.config.js](../electron-builder.config.js) +- Workflow file: [.github/workflows/react-native-cicd.yml](../.github/workflows/react-native-cicd.yml) diff --git a/electron-builder.config.js b/electron-builder.config.js index 1606b184..5125a0ae 100644 --- a/electron-builder.config.js +++ b/electron-builder.config.js @@ -12,13 +12,7 @@ module.exports = { buildResources: 'assets', }, - files: [ - 'dist/**/*', - 'electron/**/*', - '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}', - '!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}', - '!**/node_modules/.bin', - ], + files: ['dist/**/*', 'electron/**/*', '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}', '!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples}', '!**/node_modules/.bin'], // macOS configuration mac: { diff --git a/electron/main.js b/electron/main.js index caba7985..ac30f095 100644 --- a/electron/main.js +++ b/electron/main.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ const { app, BrowserWindow, ipcMain, Notification, nativeTheme, Menu } = require('electron'); const path = require('path'); @@ -32,16 +33,14 @@ function createWindow() { }); // Load the app - const startUrl = isDev - ? 'http://localhost:8081' - : `file://${path.join(__dirname, '../dist/index.html')}`; + const startUrl = isDev ? 'http://localhost:8081' : `file://${path.join(__dirname, '../dist/index.html')}`; mainWindow.loadURL(startUrl); // Show window when ready mainWindow.once('ready-to-show', () => { mainWindow.show(); - + // Open DevTools in development if (isDev) { mainWindow.webContents.openDevTools(); @@ -66,26 +65,18 @@ function createMenu() { const template = [ // App Menu (macOS only) - ...(isMac ? [{ - label: app.name, - submenu: [ - { role: 'about' }, - { type: 'separator' }, - { role: 'services' }, - { type: 'separator' }, - { role: 'hide' }, - { role: 'hideOthers' }, - { role: 'unhide' }, - { type: 'separator' }, - { role: 'quit' }, - ], - }] : []), + ...(isMac + ? [ + { + label: app.name, + submenu: [{ role: 'about' }, { type: 'separator' }, { role: 'services' }, { type: 'separator' }, { role: 'hide' }, { role: 'hideOthers' }, { role: 'unhide' }, { type: 'separator' }, { role: 'quit' }], + }, + ] + : []), // File Menu { label: 'File', - submenu: [ - isMac ? { role: 'close' } : { role: 'quit' }, - ], + submenu: [isMac ? { role: 'close' } : { role: 'quit' }], }, // Edit Menu { @@ -97,15 +88,7 @@ function createMenu() { { role: 'cut' }, { role: 'copy' }, { role: 'paste' }, - ...(isMac ? [ - { role: 'pasteAndMatchStyle' }, - { role: 'delete' }, - { role: 'selectAll' }, - ] : [ - { role: 'delete' }, - { type: 'separator' }, - { role: 'selectAll' }, - ]), + ...(isMac ? [{ role: 'pasteAndMatchStyle' }, { role: 'delete' }, { role: 'selectAll' }] : [{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }]), ], }, // View Menu @@ -126,18 +109,7 @@ function createMenu() { // Window Menu { label: 'Window', - submenu: [ - { role: 'minimize' }, - { role: 'zoom' }, - ...(isMac ? [ - { type: 'separator' }, - { role: 'front' }, - { type: 'separator' }, - { role: 'window' }, - ] : [ - { role: 'close' }, - ]), - ], + submenu: [{ role: 'minimize' }, { role: 'zoom' }, ...(isMac ? [{ type: 'separator' }, { role: 'front' }, { type: 'separator' }, { role: 'window' }] : [{ role: 'close' }])], }, ]; diff --git a/expo-env.d.ts b/expo-env.d.ts index 5411fdde..bf3c1693 100644 --- a/expo-env.d.ts +++ b/expo-env.d.ts @@ -1,3 +1,3 @@ /// -// NOTE: This file should not be edited and should be in your git ignore \ No newline at end of file +// NOTE: This file should not be edited and should be in your git ignore diff --git a/metro.config.js b/metro.config.js index da5c1bbd..12a6bf86 100644 --- a/metro.config.js +++ b/metro.config.js @@ -48,10 +48,9 @@ config.resolver.resolveRequest = (context, moduleName, platform) => { if (originalResolveRequest) { return originalResolveRequest(context, moduleName, platform); } - + // Default resolution return context.resolveRequest(context, moduleName, platform); }; module.exports = withNativeWind(config, { input: './global.css', inlineRem: 16 }); - diff --git a/package.json b/package.json index 3e65eecc..45debb0d 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@react-native-community/netinfo": "^11.4.1", "@react-native-firebase/app": "^23.5.0", "@react-native-firebase/messaging": "^23.5.0", - "@rnmapbox/maps": "10.1.42-rc.0", + "@rnmapbox/maps": "10.2.10", "@semantic-release/git": "^10.0.1", "@sentry/react-native": "~6.14.0", "@shopify/flash-list": "1.7.6", diff --git a/public/service-worker.js b/public/service-worker.js index 8c5ea0b2..06484deb 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,3 +1,5 @@ +/* eslint-disable no-undef */ +/* eslint-disable no-unused-vars */ /** * Service Worker for Resgrid Unit Web Push Notifications * This file handles background push notifications when the app is not in focus diff --git a/src/app/(app)/index.tsx b/src/app/(app)/index.tsx index d3940366..928f2203 100644 --- a/src/app/(app)/index.tsx +++ b/src/app/(app)/index.tsx @@ -1,4 +1,3 @@ -import Mapbox from '@/components/maps/mapbox'; import { Stack, useFocusEffect } from 'expo-router'; import { NavigationIcon } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; @@ -9,6 +8,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { getMapDataAndMarkers } from '@/api/mapping/mapping'; import MapPins from '@/components/maps/map-pins'; +import Mapbox from '@/components/maps/mapbox'; import PinDetailModal from '@/components/maps/pin-detail-modal'; import { FocusAwareStatusBar } from '@/components/ui/focus-aware-status-bar'; import { useAnalytics } from '@/hooks/use-analytics'; @@ -30,8 +30,8 @@ export default function Map() { const { trackEvent } = useAnalytics(); const { colorScheme } = useColorScheme(); const insets = useSafeAreaInsets(); - const mapRef = useRef(null); - const cameraRef = useRef(null); + const mapRef = useRef>(null); + const cameraRef = useRef(null); // Using any due to imperative handle const [isMapReady, setIsMapReady] = useState(false); const [hasUserMovedMap, setHasUserMovedMap] = useState(false); const [mapPins, setMapPins] = useState([]); diff --git a/src/components/maps/full-screen-location-picker.tsx b/src/components/maps/full-screen-location-picker.tsx index d3ee83ca..4d1ca4da 100644 --- a/src/components/maps/full-screen-location-picker.tsx +++ b/src/components/maps/full-screen-location-picker.tsx @@ -1,4 +1,3 @@ -import Mapbox from '@/components/maps/mapbox'; import * as Location from 'expo-location'; import { LocateIcon, MapPinIcon, XIcon } from 'lucide-react-native'; import React, { useEffect, useRef, useState } from 'react'; @@ -6,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import { ActivityIndicator, Dimensions, StyleSheet, TouchableOpacity } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import Mapbox from '@/components/maps/mapbox'; import { Box } from '@/components/ui/box'; import { Button, ButtonText } from '@/components/ui/button'; import { Text } from '@/components/ui/text'; @@ -35,8 +35,8 @@ interface FullScreenLocationPickerProps { const FullScreenLocationPicker: React.FC = ({ initialLocation, onLocationSelected, onClose }) => { const { t } = useTranslation(); const insets = useSafeAreaInsets(); - const mapRef = useRef(null); - const cameraRef = useRef(null); + const mapRef = useRef>(null); + const cameraRef = useRef(null); // Using any due to imperative handle // Always start with a location - either initial, or default const [currentLocation, setCurrentLocation] = useState<{ latitude: number; diff --git a/src/components/maps/location-picker.tsx b/src/components/maps/location-picker.tsx index 2a511334..ef2f23e4 100644 --- a/src/components/maps/location-picker.tsx +++ b/src/components/maps/location-picker.tsx @@ -1,10 +1,10 @@ -import Mapbox from '@/components/maps/mapbox'; import * as Location from 'expo-location'; import { LocateIcon, MapPinIcon } from 'lucide-react-native'; import React, { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ActivityIndicator, StyleSheet, TouchableOpacity } from 'react-native'; +import Mapbox from '@/components/maps/mapbox'; import { Box } from '@/components/ui/box'; import { Button, ButtonText } from '@/components/ui/button'; import { Text } from '@/components/ui/text'; @@ -37,8 +37,8 @@ interface LocationPickerProps { const LocationPicker: React.FC = ({ initialLocation, onLocationSelected, height = 200 }) => { const { t } = useTranslation(); - const mapRef = useRef(null); - const cameraRef = useRef(null); + const mapRef = useRef>(null); + const cameraRef = useRef(null); // Using any due to imperative handle const isMountedRef = useRef(true); // Always start with a location - either initial, or default const [currentLocation, setCurrentLocation] = useState<{ diff --git a/src/components/maps/map-pins.tsx b/src/components/maps/map-pins.tsx index d3f58cc9..c01588b7 100644 --- a/src/components/maps/map-pins.tsx +++ b/src/components/maps/map-pins.tsx @@ -1,6 +1,6 @@ -import Mapbox from '@/components/maps/mapbox'; import React from 'react'; +import Mapbox from '@/components/maps/mapbox'; import { type MAP_ICONS } from '@/constants/map-icons'; import { type MapMakerInfoData } from '@/models/v4/mapping/getMapDataAndMarkersData'; diff --git a/src/components/maps/map-view.web.tsx b/src/components/maps/map-view.web.tsx index 6096a82e..a8caf6e3 100644 --- a/src/components/maps/map-view.web.tsx +++ b/src/components/maps/map-view.web.tsx @@ -2,8 +2,9 @@ * Web implementation of map components using mapbox-gl * This file is used on web and Electron platforms */ -import mapboxgl from 'mapbox-gl'; import 'mapbox-gl/dist/mapbox-gl.css'; + +import mapboxgl from 'mapbox-gl'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { Env } from '@/lib/env'; @@ -54,93 +55,95 @@ interface MapViewProps { } // MapView component -export const MapView = forwardRef(({ - style, - styleURL = StyleURL.Street, - onDidFinishLoadingMap, - onCameraChanged, - children, - testID, - logoEnabled = false, - attributionEnabled = false, - compassEnabled = true, - zoomEnabled = true, - rotateEnabled = true, - scrollEnabled = true, - pitchEnabled = true, -}, ref) => { - const mapContainer = useRef(null); - const map = useRef(null); - const [isLoaded, setIsLoaded] = useState(false); +export const MapView = forwardRef( + ( + { + style, + styleURL = StyleURL.Street, + onDidFinishLoadingMap, + onCameraChanged, + children, + testID, + logoEnabled = false, + attributionEnabled = false, + compassEnabled = true, + zoomEnabled = true, + rotateEnabled = true, + scrollEnabled = true, + pitchEnabled = true, + }, + ref + ) => { + const mapContainer = useRef(null); + const map = useRef(null); + const [isLoaded, setIsLoaded] = useState(false); + + useImperativeHandle(ref, () => ({ + getMap: () => map.current, + })); + + useEffect(() => { + if (map.current || !mapContainer.current) return; + + const newMap = new mapboxgl.Map({ + container: mapContainer.current, + style: styleURL, + center: [-98.5795, 39.8283], // Default US center + zoom: 4, + attributionControl: attributionEnabled, + logoPosition: logoEnabled ? 'bottom-left' : undefined, + dragRotate: rotateEnabled, + scrollZoom: zoomEnabled, + dragPan: scrollEnabled, + pitchWithRotate: pitchEnabled, + }); - useImperativeHandle(ref, () => ({ - getMap: () => map.current, - })); + if (!logoEnabled) { + // Hide logo via CSS if not enabled + newMap.on('load', () => { + const logoEl = mapContainer.current?.querySelector('.mapboxgl-ctrl-logo'); + if (logoEl) { + (logoEl as HTMLElement).style.display = 'none'; + } + }); + } - useEffect(() => { - if (map.current || !mapContainer.current) return; - - const newMap = new mapboxgl.Map({ - container: mapContainer.current, - style: styleURL, - center: [-98.5795, 39.8283], // Default US center - zoom: 4, - attributionControl: attributionEnabled, - logoPosition: logoEnabled ? 'bottom-left' : undefined, - dragRotate: rotateEnabled, - scrollZoom: zoomEnabled, - dragPan: scrollEnabled, - pitchWithRotate: pitchEnabled, - }); + if (compassEnabled) { + newMap.addControl(new mapboxgl.NavigationControl({ showCompass: true, showZoom: false }), 'top-right'); + } - if (!logoEnabled) { - // Hide logo via CSS if not enabled newMap.on('load', () => { - const logoEl = mapContainer.current?.querySelector('.mapboxgl-ctrl-logo'); - if (logoEl) { - (logoEl as HTMLElement).style.display = 'none'; - } + setIsLoaded(true); + onDidFinishLoadingMap?.(); }); - } - - if (compassEnabled) { - newMap.addControl(new mapboxgl.NavigationControl({ showCompass: true, showZoom: false }), 'top-right'); - } - newMap.on('load', () => { - setIsLoaded(true); - onDidFinishLoadingMap?.(); - }); + newMap.on('moveend', () => { + onCameraChanged?.({ properties: { isUserInteraction: true } }); + }); - newMap.on('moveend', () => { - onCameraChanged?.({ properties: { isUserInteraction: true } }); - }); + map.current = newMap; - map.current = newMap; + return () => { + map.current?.remove(); + map.current = null; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); - return () => { - map.current?.remove(); - map.current = null; - }; - }, []); - - // Update style when it changes - useEffect(() => { - if (map.current && styleURL) { - map.current.setStyle(styleURL); - } - }, [styleURL]); + // Update style when it changes + useEffect(() => { + if (map.current && styleURL) { + map.current.setStyle(styleURL); + } + }, [styleURL]); - return ( -
- {isLoaded && ( - - {children} - - )} -
- ); -}); + return ( +
+ {isLoaded && {children}} +
+ ); + } +); MapView.displayName = 'MapView'; @@ -160,26 +163,12 @@ interface CameraProps { } // Camera component -export const Camera = forwardRef(({ - centerCoordinate, - zoomLevel, - heading, - pitch, - animationDuration = 1000, - followUserLocation, - followZoomLevel, -}, ref) => { +export const Camera = forwardRef(({ centerCoordinate, zoomLevel, heading, pitch, animationDuration = 1000, followUserLocation, followZoomLevel }, ref) => { const map = React.useContext(MapContext); const geolocateControl = useRef(null); useImperativeHandle(ref, () => ({ - setCamera: (options: { - centerCoordinate?: [number, number]; - zoomLevel?: number; - heading?: number; - pitch?: number; - animationDuration?: number; - }) => { + setCamera: (options: { centerCoordinate?: [number, number]; zoomLevel?: number; heading?: number; pitch?: number; animationDuration?: number }) => { if (!map) return; map.easeTo({ @@ -254,14 +243,7 @@ interface PointAnnotationProps { } // PointAnnotation component -export const PointAnnotation: React.FC = ({ - id, - coordinate, - title, - children, - anchor = { x: 0.5, y: 0.5 }, - onSelected, -}) => { +export const PointAnnotation: React.FC = ({ id, coordinate, title, children, anchor = { x: 0.5, y: 0.5 }, onSelected }) => { const map = React.useContext(MapContext); const markerRef = useRef(null); const containerRef = useRef(null); @@ -298,6 +280,7 @@ export const PointAnnotation: React.FC = ({ return () => { markerRef.current?.remove(); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [map, coordinate, id]); // Update position when coordinate changes @@ -317,10 +300,7 @@ interface UserLocationProps { } // UserLocation component - handled by GeolocateControl in Camera -export const UserLocation: React.FC = ({ - visible = true, - showsUserHeadingIndicator = true, -}) => { +export const UserLocation: React.FC = ({ visible = true, showsUserHeadingIndicator = true }) => { const map = React.useContext(MapContext); useEffect(() => { @@ -354,7 +334,11 @@ export const MarkerView: React.FC<{ coordinate: [number, number]; children?: React.ReactNode; }> = ({ coordinate, children }) => { - return {children}; + return ( + + {children} + + ); }; // Placeholder components for compatibility diff --git a/src/components/maps/mapbox.ts b/src/components/maps/mapbox.ts index 7916c951..5e90bc2e 100644 --- a/src/components/maps/mapbox.ts +++ b/src/components/maps/mapbox.ts @@ -6,60 +6,30 @@ import { Platform } from 'react-native'; // Import the platform-specific implementation // Metro bundler will resolve to the correct file based on platform -const MapboxImpl = Platform.OS === 'web' - ? require('./map-view.web').default - : require('./map-view.native').default; +const MapboxImpl = Platform.OS === 'web' ? require('./map-view.web').default : require('./map-view.native').default; // Re-export all components export const MapView = MapboxImpl.MapView || MapboxImpl; -export const Camera = Platform.OS === 'web' - ? require('./map-view.web').Camera - : require('./map-view.native').Camera; -export const PointAnnotation = Platform.OS === 'web' - ? require('./map-view.web').PointAnnotation - : require('./map-view.native').PointAnnotation; -export const UserLocation = Platform.OS === 'web' - ? require('./map-view.web').UserLocation - : require('./map-view.native').UserLocation; -export const MarkerView = Platform.OS === 'web' - ? require('./map-view.web').MarkerView - : require('./map-view.native').MarkerView; -export const ShapeSource = Platform.OS === 'web' - ? require('./map-view.web').ShapeSource - : require('./map-view.native').ShapeSource; -export const SymbolLayer = Platform.OS === 'web' - ? require('./map-view.web').SymbolLayer - : require('./map-view.native').SymbolLayer; -export const CircleLayer = Platform.OS === 'web' - ? require('./map-view.web').CircleLayer - : require('./map-view.native').CircleLayer; -export const LineLayer = Platform.OS === 'web' - ? require('./map-view.web').LineLayer - : require('./map-view.native').LineLayer; -export const FillLayer = Platform.OS === 'web' - ? require('./map-view.web').FillLayer - : require('./map-view.native').FillLayer; -export const Images = Platform.OS === 'web' - ? require('./map-view.web').Images - : require('./map-view.native').Images; -export const Callout = Platform.OS === 'web' - ? require('./map-view.web').Callout - : require('./map-view.native').Callout; +export const Camera = Platform.OS === 'web' ? require('./map-view.web').Camera : require('./map-view.native').Camera; +export const PointAnnotation = Platform.OS === 'web' ? require('./map-view.web').PointAnnotation : require('./map-view.native').PointAnnotation; +export const UserLocation = Platform.OS === 'web' ? require('./map-view.web').UserLocation : require('./map-view.native').UserLocation; +export const MarkerView = Platform.OS === 'web' ? require('./map-view.web').MarkerView : require('./map-view.native').MarkerView; +export const ShapeSource = Platform.OS === 'web' ? require('./map-view.web').ShapeSource : require('./map-view.native').ShapeSource; +export const SymbolLayer = Platform.OS === 'web' ? require('./map-view.web').SymbolLayer : require('./map-view.native').SymbolLayer; +export const CircleLayer = Platform.OS === 'web' ? require('./map-view.web').CircleLayer : require('./map-view.native').CircleLayer; +export const LineLayer = Platform.OS === 'web' ? require('./map-view.web').LineLayer : require('./map-view.native').LineLayer; +export const FillLayer = Platform.OS === 'web' ? require('./map-view.web').FillLayer : require('./map-view.native').FillLayer; +export const Images = Platform.OS === 'web' ? require('./map-view.web').Images : require('./map-view.native').Images; +export const Callout = Platform.OS === 'web' ? require('./map-view.web').Callout : require('./map-view.native').Callout; // Export style URL constants -export const StyleURL = Platform.OS === 'web' - ? require('./map-view.web').StyleURL - : require('./map-view.native').StyleURL; +export const StyleURL = Platform.OS === 'web' ? require('./map-view.web').StyleURL : require('./map-view.native').StyleURL; // Export UserTrackingMode -export const UserTrackingMode = Platform.OS === 'web' - ? require('./map-view.web').UserTrackingMode - : require('./map-view.native').UserTrackingMode; +export const UserTrackingMode = Platform.OS === 'web' ? require('./map-view.web').UserTrackingMode : require('./map-view.native').UserTrackingMode; // Export setAccessToken -export const setAccessToken = Platform.OS === 'web' - ? require('./map-view.web').setAccessToken - : require('./map-view.native').setAccessToken; +export const setAccessToken = Platform.OS === 'web' ? require('./map-view.web').setAccessToken : require('./map-view.native').setAccessToken; // Default export matching Mapbox structure export default MapboxImpl; diff --git a/src/components/maps/pin-marker.tsx b/src/components/maps/pin-marker.tsx index e92ec2ad..5f931fa9 100644 --- a/src/components/maps/pin-marker.tsx +++ b/src/components/maps/pin-marker.tsx @@ -1,8 +1,8 @@ -import type Mapbox from '@/components/maps/mapbox'; import { useColorScheme } from 'nativewind'; import React from 'react'; import { Image, StyleSheet, Text, TouchableOpacity } from 'react-native'; +import type Mapbox from '@/components/maps/mapbox'; import { MAP_ICONS } from '@/constants/map-icons'; type MapIconKey = keyof typeof MAP_ICONS; @@ -11,7 +11,7 @@ interface PinMarkerProps { imagePath?: MapIconKey; title: string; size?: number; - markerRef?: Mapbox.PointAnnotation | null; + markerRef?: React.ElementRef | null; onPress?: () => void; } diff --git a/src/components/maps/static-map.tsx b/src/components/maps/static-map.tsx index 6ff869e2..13a90374 100644 --- a/src/components/maps/static-map.tsx +++ b/src/components/maps/static-map.tsx @@ -1,9 +1,9 @@ -import Mapbox from '@/components/maps/mapbox'; import { useColorScheme } from 'nativewind'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { StyleSheet } from 'react-native'; +import Mapbox from '@/components/maps/mapbox'; import { Box } from '@/components/ui/box'; import { Text } from '@/components/ui/text'; import { Env } from '@/lib/env'; diff --git a/src/hooks/use-map-signalr-updates.ts b/src/hooks/use-map-signalr-updates.ts index 5529b569..72764b0d 100644 --- a/src/hooks/use-map-signalr-updates.ts +++ b/src/hooks/use-map-signalr-updates.ts @@ -12,7 +12,7 @@ export const useMapSignalRUpdates = (onMarkersUpdate: (markers: MapMakerInfoData const lastProcessedTimestamp = useRef(0); const isUpdating = useRef(false); const pendingTimestamp = useRef(null); - const debounceTimer = useRef(null); + const debounceTimer = useRef | null>(null); const abortController = useRef(null); const lastUpdateTimestamp = useSignalRStore((state) => state.lastUpdateTimestamp); diff --git a/src/hooks/use-signalr-lifecycle.ts b/src/hooks/use-signalr-lifecycle.ts index 8a4882c6..0c055efb 100644 --- a/src/hooks/use-signalr-lifecycle.ts +++ b/src/hooks/use-signalr-lifecycle.ts @@ -31,8 +31,8 @@ export function useSignalRLifecycle({ isSignedIn, hasInitialized }: UseSignalRLi const lastAppState = useRef(null); const isProcessing = useRef(false); const pendingOperations = useRef(null); - const backgroundTimer = useRef(null); - const resumeTimer = useRef(null); + const backgroundTimer = useRef | null>(null); + const resumeTimer = useRef | null>(null); const handleAppBackground = useCallback(async () => { logger.debug({ diff --git a/src/lib/env.js b/src/lib/env.js index 7209e39b..aca71a1a 100644 --- a/src/lib/env.js +++ b/src/lib/env.js @@ -2,7 +2,7 @@ * This file should not be modified directly; use `env.js` in the project root to add your client environment variables. * If you import `Env` from `@env`, this is the file that will be loaded. * You can only access the client environment variables here. - * + * * For Docker deployments, environment variables are injected at runtime via window.__ENV__ * For native/dev builds, environment variables come from expo-constants */ @@ -26,9 +26,10 @@ const isDockerRuntime = () => { * Get environment configuration * - For Docker web deployments: uses window.__ENV__ (runtime injection) * - For native/dev builds: uses expo-constants (build-time) - * + * * @type {typeof import('../../env.js').ClientEnv} */ +// @ts-ignore - Function type inference issue with mixed Docker/native config const getEnvConfig = () => { if (isDockerRuntime()) { // Docker runtime - use injected environment variables @@ -39,4 +40,5 @@ const getEnvConfig = () => { return Constants.expoConfig?.extra ?? {}; }; +// @ts-ignore - Type inference issue with function call export const Env = getEnvConfig(); diff --git a/src/lib/platform.ts b/src/lib/platform.ts index 6555f0b6..b7a817bf 100644 --- a/src/lib/platform.ts +++ b/src/lib/platform.ts @@ -8,15 +8,10 @@ import { Platform } from 'react-native'; export const isWeb = Platform.OS === 'web'; // Check if running in Electron (desktop app wrapped around web) -export const isElectron = - typeof window !== 'undefined' && - window.process?.type === 'renderer'; +export const isElectron = typeof window !== 'undefined' && window.process?.type === 'renderer'; // Check if running on a desktop platform (Electron or native desktop) -export const isDesktop = - isElectron || - Platform.OS === 'macos' || - Platform.OS === 'windows'; +export const isDesktop = isElectron || Platform.OS === 'macos' || Platform.OS === 'windows'; // Check if running on native mobile platforms export const isNative = Platform.OS === 'ios' || Platform.OS === 'android'; @@ -37,14 +32,21 @@ export const platformName = (): string => { if (electronPlatform === 'linux') return 'Linux (Electron)'; return 'Electron'; } - - switch (Platform.OS) { - case 'ios': return 'iOS'; - case 'android': return 'Android'; - case 'web': return 'Web'; - case 'macos': return 'macOS'; - case 'windows': return 'Windows'; - default: return Platform.OS; + + const os = Platform.OS; + switch (os) { + case 'ios': + return 'iOS'; + case 'android': + return 'Android'; + case 'web': + return 'Web'; + case 'macos': + return 'macOS'; + case 'windows': + return 'Windows'; + default: + return os; } }; diff --git a/src/services/bluetooth-audio.service.ts b/src/services/bluetooth-audio.service.ts index f403c48f..9c5be3a3 100644 --- a/src/services/bluetooth-audio.service.ts +++ b/src/services/bluetooth-audio.service.ts @@ -1,6 +1,6 @@ import { Buffer } from 'buffer'; import { Alert, DeviceEventEmitter, PermissionsAndroid, Platform } from 'react-native'; -import BleManager, { type BleManagerDidUpdateValueForCharacteristicEvent, BleScanCallbackType, BleScanMatchMode, BleScanMode, type BleState, type Peripheral } from 'react-native-ble-manager'; +import BleManager, { type BleManagerDidUpdateValueForCharacteristicEvent, BleScanCallbackType, BleScanMatchMode, BleScanMode, type BleState, type Peripheral, type PeripheralInfo } from 'react-native-ble-manager'; import { logger } from '@/lib/logging'; import { audioService } from '@/services/audio.service'; @@ -39,7 +39,7 @@ const BUTTON_CONTROL_UUIDS = [ class BluetoothAudioService { private static instance: BluetoothAudioService; private connectedDevice: Device | null = null; - private scanTimeout: number | null = null; + private scanTimeout: ReturnType | null = null; private connectionTimeout: NodeJS.Timeout | null = null; private isInitialized: boolean = false; private hasAttemptedPreferredDeviceConnection: boolean = false; @@ -861,7 +861,7 @@ class BluetoothAudioService { } // Discover services and characteristics - await BleManager.retrieveServices(deviceId); + const peripheralInfo = await BleManager.retrieveServices(deviceId); this.connectedDevice = device; useBluetoothAudioStore.getState().setConnectedDevice({ @@ -874,8 +874,8 @@ class BluetoothAudioService { device, }); - // Set up button event monitoring - await this.setupButtonEventMonitoring(device); + // Set up button event monitoring with peripheral info + await this.setupButtonEventMonitoring(device, peripheralInfo); // Integrate with LiveKit audio routing await this.setupLiveKitAudioRouting(device); @@ -916,29 +916,19 @@ class BluetoothAudioService { } } - private async setupButtonEventMonitoring(device: Device): Promise { + private async setupButtonEventMonitoring(device: Device, peripheralInfo: PeripheralInfo): Promise { try { - const peripheralInfo = await BleManager.getDiscoveredPeripherals(); - const deviceInfo = peripheralInfo.find((p) => p.id === device.id); - - if (!deviceInfo) { - logger.warn({ - message: 'Device not found in discovered peripherals', - context: { deviceId: device.id }, - }); - return; - } - logger.info({ message: 'Setting up button event monitoring', context: { deviceId: device.id, deviceName: device.name, + availableServices: peripheralInfo?.services?.map((s: any) => s.uuid) || [], }, }); // Start notifications for known button control characteristics - await this.startNotificationsForButtonControls(device.id); + await this.startNotificationsForButtonControls(device.id, peripheralInfo); } catch (error) { logger.warn({ message: 'Could not set up button event monitoring', @@ -947,7 +937,24 @@ class BluetoothAudioService { } } - private async startNotificationsForButtonControls(deviceId: string): Promise { + /** + * Check if a service and characteristic exist on the peripheral + */ + private hasCharacteristic(peripheralInfo: PeripheralInfo, serviceUuid: string, characteristicUuid: string): boolean { + if (!peripheralInfo?.services) { + return false; + } + + const service = peripheralInfo.services.find((s: any) => s.uuid?.toUpperCase() === serviceUuid.toUpperCase()); + + if (!service || !(service as any).characteristics) { + return false; + } + + return (service as any).characteristics.some((c: any) => c.characteristic?.toUpperCase() === characteristicUuid.toUpperCase()); + } + + private async startNotificationsForButtonControls(deviceId: string, peripheralInfo: PeripheralInfo): Promise { // Try to start notifications for known button control service/characteristic combinations const buttonControlConfigs = [ { service: AINA_HEADSET_SERVICE, characteristic: AINA_HEADSET_SVC_PROP }, @@ -959,6 +966,19 @@ class BluetoothAudioService { for (const config of buttonControlConfigs) { try { + // Check if the characteristic exists before trying to start notifications + if (!this.hasCharacteristic(peripheralInfo, config.service, config.characteristic)) { + logger.debug({ + message: 'Characteristic not available on device, skipping', + context: { + deviceId, + service: config.service, + characteristic: config.characteristic, + }, + }); + continue; + } + await BleManager.startNotification(deviceId, config.service, config.characteristic); logger.info({ message: 'Started notifications for button control', diff --git a/src/services/media-button.service.ts b/src/services/media-button.service.ts index e1b11873..36e4445f 100644 --- a/src/services/media-button.service.ts +++ b/src/services/media-button.service.ts @@ -3,11 +3,7 @@ import { DeviceEventEmitter, NativeEventEmitter, NativeModules, Platform } from import { logger } from '@/lib/logging'; import { audioService } from '@/services/audio.service'; import { type AudioButtonEvent, useBluetoothAudioStore } from '@/stores/app/bluetooth-audio-store'; -import { - createDefaultPTTSettings, - type MediaButtonPTTSettings, - type PTTMode, -} from '@/types/ptt'; +import { createDefaultPTTSettings, type MediaButtonPTTSettings, type PTTMode } from '@/types/ptt'; // Re-export PTT types for backwards compatibility export { type MediaButtonPTTSettings, type PTTMode }; diff --git a/src/services/offline-event-manager.service.ts b/src/services/offline-event-manager.service.ts index 6d9d89cf..54cb9d47 100644 --- a/src/services/offline-event-manager.service.ts +++ b/src/services/offline-event-manager.service.ts @@ -11,7 +11,7 @@ import { useOfflineQueueStore } from '@/stores/offline-queue/store'; class OfflineEventManager { private static instance: OfflineEventManager; - private processingInterval: number | null = null; + private processingInterval: ReturnType | null = null; private isProcessing = false; private appStateSubscription: { remove: () => void } | null = null; private readonly PROCESSING_INTERVAL = 10000; // 10 seconds diff --git a/src/services/push-notification.electron.ts b/src/services/push-notification.electron.ts index 77595f45..6be855be 100644 --- a/src/services/push-notification.electron.ts +++ b/src/services/push-notification.electron.ts @@ -11,7 +11,7 @@ declare global { interface Window { electronAPI?: { showNotification: (options: { title: string; body: string; data: any }) => Promise; - onNotificationClicked: (callback: (data: any) => void) => () => void; + onNotificationClicked: (callback: (data: any) => void) => void; platform: string; isElectron: boolean; }; @@ -46,7 +46,8 @@ class ElectronPushNotificationService { } // Listen for notification clicks from the main process - this.cleanupListener = window.electronAPI.onNotificationClicked(this.handleNotificationClick); + window.electronAPI.onNotificationClicked(this.handleNotificationClick); + this.cleanupListener = null; // No cleanup needed for Electron API this.isInitialized = true; logger.info({ diff --git a/src/stores/app/bluetooth-audio-store.ts b/src/stores/app/bluetooth-audio-store.ts index e4784bea..674f8069 100644 --- a/src/stores/app/bluetooth-audio-store.ts +++ b/src/stores/app/bluetooth-audio-store.ts @@ -1,12 +1,7 @@ import { type Peripheral } from 'react-native-ble-manager'; import { create } from 'zustand'; -import { - createDefaultPTTSettings, - DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, - type MediaButtonPTTSettings, - type PTTMode, -} from '@/types/ptt'; +import { createDefaultPTTSettings, DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, type MediaButtonPTTSettings, type PTTMode } from '@/types/ptt'; // Re-export PTT types for backwards compatibility export { DEFAULT_MEDIA_BUTTON_PTT_SETTINGS, type MediaButtonPTTSettings, type PTTMode }; diff --git a/src/stores/app/livekit-store.ts b/src/stores/app/livekit-store.ts index d533846c..2afb88ac 100644 --- a/src/stores/app/livekit-store.ts +++ b/src/stores/app/livekit-store.ts @@ -1,4 +1,4 @@ -import notifee, { AndroidImportance } from '@notifee/react-native'; +import notifee, { AndroidForegroundServiceType, AndroidImportance } from '@notifee/react-native'; import { getRecordingPermissionsAsync, requestRecordingPermissionsAsync } from 'expo-audio'; import { Room, RoomEvent } from 'livekit-client'; import { Platform } from 'react-native'; @@ -226,6 +226,7 @@ export const useLiveKitStore = create((set, get) => ({ android: { channelId: 'notif', asForegroundService: true, + foregroundServiceTypes: [2], // 2 = MICROPHONE smallIcon: 'ic_launcher', // Ensure this icon exists in res/drawable }, }); diff --git a/yarn.lock b/yarn.lock index 54ab3c3a..491ee13f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4430,10 +4430,10 @@ "@react-types/overlays" "^3.9.1" "@react-types/shared" "^3.32.0" -"@rnmapbox/maps@10.1.42-rc.0": - version "10.1.42-rc.0" - resolved "https://registry.yarnpkg.com/@rnmapbox/maps/-/maps-10.1.42-rc.0.tgz#760a3261a5c386ff6ba640406b4b0c8872d3c702" - integrity sha512-oFv7K3kFhcSqvR+G8vExZC1T8iqHQMjsid6gBmCJKIBnBYD2dXxQlr6g/x0VH0polIVWirmrINaJzKkqrIjPig== +"@rnmapbox/maps@10.2.10": + version "10.2.10" + resolved "https://registry.yarnpkg.com/@rnmapbox/maps/-/maps-10.2.10.tgz#5ba121ffd1017e5a5550e9d4829f53701e87ce87" + integrity sha512-OfjW0rHp5bUWfzBo5fZ7qdKwAzGoocXYTsSssSPVMxZ2Y7axuhcbmsO5bV6gg+BJs5RwEsghzwTIoGydBNUClA== dependencies: "@turf/along" "6.5.0" "@turf/distance" "6.5.0" @@ -4441,7 +4441,7 @@ "@turf/length" "6.5.0" "@turf/nearest-point-on-line" "6.5.0" "@types/geojson" "^7946.0.7" - debounce "^1.2.0" + debounce "^2.2.0" "@rtsao/scc@^1.1.0": version "1.1.0" @@ -7459,10 +7459,10 @@ date-fns@^4.1.0: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14" integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg== -debounce@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" - integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== +debounce@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-2.2.0.tgz#f895fa2fbdb579a0f0d3dcf5dde19657e50eaad5" + integrity sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw== debug@2.6.9, debug@^2.2.0, debug@^2.6.9: version "2.6.9" From f1f8423bf08a40d7b23659475996425d8c54855b Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Sun, 25 Jan 2026 15:03:44 -0800 Subject: [PATCH 3/5] RU-T46 PR#202 fixes --- .github/workflows/react-native-cicd.yml | 24 +++++++- docker/docker-entrypoint.sh | 38 +++++++------ electron/main.js | 2 +- electron/preload.js | 7 ++- env.js | 4 +- nginx.conf | 3 + package.json | 1 + public/service-worker.js | 37 ++++++++++--- src/app/call/__tests__/[id].test.tsx | 6 +- src/components/maps/map-view.web.tsx | 64 ++++++++++++++++++---- src/lib/auth/api.tsx | 2 +- src/lib/native-module-shims.web.ts | 60 ++++++++++++++------ src/lib/platform.ts | 5 +- src/services/push-notification.electron.ts | 12 +++- src/services/push-notification.web.ts | 19 +++++++ src/stores/app/livekit-store.ts | 2 +- types/docker-env.d.ts | 2 +- yarn.lock | 7 +++ 18 files changed, 222 insertions(+), 73 deletions(-) diff --git a/.github/workflows/react-native-cicd.yml b/.github/workflows/react-native-cicd.yml index 4e0af743..ef560ee1 100644 --- a/.github/workflows/react-native-cicd.yml +++ b/.github/workflows/react-native-cicd.yml @@ -31,6 +31,10 @@ on: - ios - all +# Set minimal permissions by default +permissions: + contents: read + env: EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} EXPO_APPLE_ID: ${{ secrets.EXPO_APPLE_ID }} @@ -115,6 +119,8 @@ jobs: build-mobile: needs: test if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch' + permissions: + contents: write # Required for creating releases strategy: matrix: platform: [android, ios] @@ -519,17 +525,29 @@ jobs: build-docker: needs: build-web if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch' + permissions: + contents: read + packages: write # Required for pushing to GHCR runs-on: ubuntu-latest environment: RNBuild steps: - name: 🏗 Checkout repository uses: actions/checkout@v4 + - name: � Check Docker Hub credentials availability + id: docker-creds + run: | + if [[ -n "${{ secrets.DOCKER_USERNAME }}" && -n "${{ secrets.DOCKER_PASSWORD }}" ]]; then + echo "available=true" >> $GITHUB_OUTPUT + else + echo "available=false" >> $GITHUB_OUTPUT + fi + - name: 🐳 Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: 🐳 Log in to Docker Hub - if: ${{ secrets.DOCKER_USERNAME != '' && secrets.DOCKER_PASSWORD != '' }} + if: steps.docker-creds.outputs.available == 'true' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} @@ -547,7 +565,7 @@ jobs: uses: docker/metadata-action@v5 with: images: | - ${{ secrets.DOCKER_USERNAME != '' && format('{0}/resgrid-unit', secrets.DOCKER_USERNAME) || '' }} + ${{ steps.docker-creds.outputs.available == 'true' && format('{0}/resgrid-unit', secrets.DOCKER_USERNAME) || '' }} ghcr.io/${{ github.repository }} tags: | type=raw,value=7.${{ github.run_number }} @@ -571,6 +589,8 @@ jobs: build-electron: needs: test if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch' + permissions: + contents: write # Required for creating releases strategy: matrix: os: [windows-latest, macos-15, ubuntu-latest] diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 3548a1dd..9d8f2824 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -4,27 +4,33 @@ set -e # Directory where the built web app is served HTML_DIR="/usr/share/nginx/html" +# JavaScript escape function to safely escape strings for JSON/JS +# Escapes quotes, backslashes, and newlines to prevent injection +js_escape() { + printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/$/\\n/' | tr -d '\n' | sed 's/\\n$//' +} + # Create the env-config.js file with environment variables cat > "${HTML_DIR}/env-config.js" << EOF // Runtime environment configuration - generated by docker-entrypoint.sh // This file is generated at container startup and injects environment variables window.__ENV__ = { - APP_ENV: "${APP_ENV:-production}", - NAME: "${UNIT_NAME:-Resgrid Unit}", - SCHEME: "${UNIT_SCHEME:-ResgridUnit}", - VERSION: "${UNIT_VERSION:-0.0.1}", - BASE_API_URL: "${UNIT_BASE_API_URL:-https://api.resgrid.com}", - API_VERSION: "${UNIT_API_VERSION:-v4}", - RESGRID_API_URL: "${UNIT_RESGRID_API_URL:-/api/v4}", - CHANNEL_HUB_NAME: "${UNIT_CHANNEL_HUB_NAME:-eventingHub}", - REALTIME_GEO_HUB_NAME: "${UNIT_REALTIME_GEO_HUB_NAME:-geolocationHub}", - LOGGING_KEY: "${UNIT_LOGGING_KEY:-}", - APP_KEY: "${UNIT_APP_KEY:-}", - UNIT_MAPBOX_PUBKEY: "${UNIT_MAPBOX_PUBKEY:-}", - IS_MOBILE_APP: false, - SENTRY_DSN: "${UNIT_SENTRY_DSN:-}", - COUNTLY_APP_KEY: "${UNIT_COUNTLY_APP_KEY:-}", - COUNTLY_SERVER_URL: "${UNIT_COUNTLY_SERVER_URL:-}" + APP_ENV: "$(js_escape "${APP_ENV:-production}")", + NAME: "$(js_escape "${UNIT_NAME:-Resgrid Unit}")", + SCHEME: "$(js_escape "${UNIT_SCHEME:-ResgridUnit}")", + VERSION: "$(js_escape "${UNIT_VERSION:-0.0.1}")", + BASE_API_URL: "$(js_escape "${UNIT_BASE_API_URL:-https://api.resgrid.com}")", + API_VERSION: "$(js_escape "${UNIT_API_VERSION:-v4}")", + RESGRID_API_URL: "$(js_escape "${UNIT_RESGRID_API_URL:-/api/v4}")", + CHANNEL_HUB_NAME: "$(js_escape "${UNIT_CHANNEL_HUB_NAME:-eventingHub}")", + REALTIME_GEO_HUB_NAME: "$(js_escape "${UNIT_REALTIME_GEO_HUB_NAME:-geolocationHub}")", + LOGGING_KEY: "$(js_escape "${UNIT_LOGGING_KEY:-}")", + APP_KEY: "$(js_escape "${UNIT_APP_KEY:-}")", + UNIT_MAPBOX_PUBKEY: "$(js_escape "${UNIT_MAPBOX_PUBKEY:-}")", + IS_MOBILE_APP: "false", + SENTRY_DSN: "$(js_escape "${UNIT_SENTRY_DSN:-}")", + COUNTLY_APP_KEY: "$(js_escape "${UNIT_COUNTLY_APP_KEY:-}")", + COUNTLY_SERVER_URL: "$(js_escape "${UNIT_COUNTLY_SERVER_URL:-}")" }; EOF diff --git a/electron/main.js b/electron/main.js index ac30f095..a3c51d00 100644 --- a/electron/main.js +++ b/electron/main.js @@ -25,7 +25,7 @@ function createWindow() { // MacOS: use hidden title bar with traffic lights titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default', // Windows/Linux: show frame - frame: process.platform !== 'darwin' || true, + frame: process.platform !== 'darwin', // Set the background color to match the app theme backgroundColor: nativeTheme.shouldUseDarkColors ? '#1a1a1a' : '#ffffff', icon: path.join(__dirname, '../assets/icon.png'), diff --git a/electron/preload.js b/electron/preload.js index 2e7e561e..f6a5dfaf 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -10,10 +10,11 @@ contextBridge.exposeInMainWorld('electronAPI', { // Notification methods showNotification: (options) => ipcRenderer.invoke('show-notification', options), onNotificationClicked: (callback) => { - ipcRenderer.on('notification-clicked', (event, data) => callback(data)); - // Return a cleanup function + const listener = (event, data) => callback(data); + ipcRenderer.on('notification-clicked', listener); + // Return a cleanup function that removes only this specific listener return () => { - ipcRenderer.removeAllListeners('notification-clicked'); + ipcRenderer.removeListener('notification-clicked', listener); }; }, diff --git a/env.js b/env.js index 6996dc84..1487f18a 100644 --- a/env.js +++ b/env.js @@ -88,7 +88,7 @@ const client = z.object({ LOGGING_KEY: z.string(), APP_KEY: z.string(), UNIT_MAPBOX_PUBKEY: z.string(), - IS_MOBILE_APP: z.boolean(), + IS_MOBILE_APP: z.string(), SENTRY_DSN: z.string(), COUNTLY_APP_KEY: z.string(), COUNTLY_SERVER_URL: z.string(), @@ -120,7 +120,7 @@ const _clientEnv = { REALTIME_GEO_HUB_NAME: process.env.UNIT_REALTIME_GEO_HUB_NAME || 'geolocationHub', LOGGING_KEY: process.env.UNIT_LOGGING_KEY || '', APP_KEY: process.env.UNIT_APP_KEY || '', - IS_MOBILE_APP: true, // or whatever default you want + IS_MOBILE_APP: 'true', UNIT_MAPBOX_PUBKEY: process.env.UNIT_MAPBOX_PUBKEY || '', SENTRY_DSN: process.env.UNIT_SENTRY_DSN || '', COUNTLY_APP_KEY: process.env.UNIT_COUNTLY_APP_KEY || '', diff --git a/nginx.conf b/nginx.conf index c90cac95..d1346d69 100644 --- a/nginx.conf +++ b/nginx.conf @@ -44,6 +44,9 @@ http { location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; try_files $uri =404; } diff --git a/package.json b/package.json index 45debb0d..d2aff6d1 100644 --- a/package.json +++ b/package.json @@ -197,6 +197,7 @@ "dotenv": "~16.4.5", "electron": "40.0.0", "electron-builder": "26.4.0", + "electron-squirrel-startup": "^1.0.1", "eslint": "~8.57.0", "eslint-config-expo": "~7.1.2", "eslint-config-prettier": "~9.1.0", diff --git a/public/service-worker.js b/public/service-worker.js index 06484deb..0a650ff2 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -8,6 +8,9 @@ // Cache name for offline support (optional) const CACHE_NAME = 'resgrid-unit-v1'; +// Store pending notification data for newly opened windows +const pendingNotifications = new Map(); + // Handle push events self.addEventListener('push', function (event) { console.log('[Service Worker] Push received:', event); @@ -80,15 +83,11 @@ self.addEventListener('notificationclick', function (event) { // Open new window if no existing window found if (clients.openWindow) { return clients.openWindow('/').then(function (client) { - // Send message after a short delay to ensure the app is ready - setTimeout(function () { - if (client) { - client.postMessage({ - type: 'NOTIFICATION_CLICK', - data: data, - }); - } - }, 1000); + // Store notification data for handshake with the new window + if (client) { + pendingNotifications.set(client.id, data); + console.log('[Service Worker] Stored pending notification for client:', client.id); + } }); } }) @@ -118,7 +117,27 @@ self.addEventListener('activate', function (event) { self.addEventListener('message', function (event) { console.log('[Service Worker] Message received:', event.data); + // Handle skip waiting message if (event.data && event.data.type === 'SKIP_WAITING') { self.skipWaiting(); + return; + } + + // Handle client ready handshake + if (event.data && event.data.type === 'CLIENT_READY') { + const clientId = event.source.id; + console.log('[Service Worker] Client ready handshake received:', clientId); + + // Check if there's a pending notification for this client + if (pendingNotifications.has(clientId)) { + const notificationData = pendingNotifications.get(clientId); + pendingNotifications.delete(clientId); + + console.log('[Service Worker] Sending pending notification to client:', clientId); + event.source.postMessage({ + type: 'NOTIFICATION_CLICK', + data: notificationData, + }); + } } }); diff --git a/src/app/call/__tests__/[id].test.tsx b/src/app/call/__tests__/[id].test.tsx index 02c8f978..3f70d4ea 100644 --- a/src/app/call/__tests__/[id].test.tsx +++ b/src/app/call/__tests__/[id].test.tsx @@ -83,13 +83,13 @@ const mockUseWindowDimensions = useWindowDimensions as jest.MockedFunction ({ expoConfig: { extra: { - IS_MOBILE_APP: true, + IS_MOBILE_APP: "true", }, }, default: { expoConfig: { extra: { - IS_MOBILE_APP: true, + IS_MOBILE_APP: "true", }, }, }, @@ -98,7 +98,7 @@ jest.mock('expo-constants', () => ({ // Mock @env to prevent expo-constants issues jest.mock('@env', () => ({ Env: { - IS_MOBILE_APP: true, + IS_MOBILE_APP: "true", }, })); diff --git a/src/components/maps/map-view.web.tsx b/src/components/maps/map-view.web.tsx index a8caf6e3..d43f2a0f 100644 --- a/src/components/maps/map-view.web.tsx +++ b/src/components/maps/map-view.web.tsx @@ -6,6 +6,7 @@ import 'mapbox-gl/dist/mapbox-gl.css'; import mapboxgl from 'mapbox-gl'; import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import ReactDOM from 'react-dom'; import { Env } from '@/lib/env'; @@ -238,7 +239,7 @@ interface PointAnnotationProps { coordinate: [number, number]; title?: string; children?: React.ReactNode; - anchor?: { x: number; y: number }; + anchor?: string | { x: number; y: number }; onSelected?: () => void; } @@ -256,32 +257,61 @@ export const PointAnnotation: React.FC = ({ id, coordinate container.style.cursor = 'pointer'; containerRef.current = container; - // If there are children, render them into the container + // Render React children into the container using createPortal if (children) { - // Simple case - just show a marker - container.innerHTML = '
'; + ReactDOM.render(<>{children}, container); } - markerRef.current = new mapboxgl.Marker({ + // Determine marker options based on anchor prop + const markerOptions: mapboxgl.MarkerOptions = { element: container, - anchor: 'center', - }) + }; + + // Handle anchor prop - if it's a string, use it as mapbox anchor + if (typeof anchor === 'string') { + markerOptions.anchor = anchor as mapboxgl.Anchor; + } + + markerRef.current = new mapboxgl.Marker(markerOptions) .setLngLat(coordinate) .addTo(map); + // If anchor is an {x, y} object, convert to pixel offset + if (typeof anchor === 'object' && anchor !== null && 'x' in anchor && 'y' in anchor) { + // Calculate offset based on container size + // Mapbox expects offset in pixels, anchor is typically 0-1 range + // Convert anchor position to offset (center is anchor {0.5, 0.5}) + const rect = container.getBoundingClientRect(); + const xOffset = (anchor.x - 0.5) * rect.width; + const yOffset = (anchor.y - 0.5) * rect.height; + markerRef.current.setOffset([xOffset, yOffset]); + } + if (title) { markerRef.current.setPopup(new mapboxgl.Popup().setText(title)); } - if (onSelected) { - container.addEventListener('click', onSelected); + // Attach click listener to container + if (onSelected && containerRef.current) { + containerRef.current.addEventListener('click', onSelected); } return () => { + // Clean up click listener + if (onSelected && containerRef.current) { + containerRef.current.removeEventListener('click', onSelected); + } + + // Unmount React children from the portal + if (children && containerRef.current) { + ReactDOM.unmountComponentAtNode(containerRef.current); + } + + // Remove marker from map markerRef.current?.remove(); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [map, coordinate, id]); + }, [map, coordinate, id, children, anchor, onSelected, title]); // Update position when coordinate changes useEffect(() => { @@ -317,9 +347,19 @@ export const UserLocation: React.FC = ({ visible = true, show map.addControl(geolocate); // Auto-trigger to show user location - map.on('load', () => { + if (map.loaded()) { geolocate.trigger(); - }); + } else { + const onMapLoad = () => { + geolocate.trigger(); + }; + map.on('load', onMapLoad); + + return () => { + map.off('load', onMapLoad); + map.removeControl(geolocate); + }; + } return () => { map.removeControl(geolocate); diff --git a/src/lib/auth/api.tsx b/src/lib/auth/api.tsx index 4324e6bd..10d810ae 100644 --- a/src/lib/auth/api.tsx +++ b/src/lib/auth/api.tsx @@ -20,7 +20,7 @@ export const loginRequest = async (credentials: LoginCredentials): Promise('/connect/token', data); diff --git a/src/lib/native-module-shims.web.ts b/src/lib/native-module-shims.web.ts index ed62d434..0d1d009b 100644 --- a/src/lib/native-module-shims.web.ts +++ b/src/lib/native-module-shims.web.ts @@ -61,25 +61,49 @@ export const notifee = { export default notifee; // Firebase Messaging shim -export const messaging = () => ({ - getToken: async () => 'web-token', - deleteToken: async () => {}, - hasPermission: async () => 1, - requestPermission: async () => 1, - onMessage: () => () => {}, - onNotificationOpenedApp: () => () => {}, - getInitialNotification: async () => null, - setBackgroundMessageHandler: () => {}, - subscribeToTopic: async () => {}, - unsubscribeFromTopic: async () => {}, -}); +interface Messaging { + (): { + getToken: () => Promise; + deleteToken: () => Promise; + hasPermission: () => Promise; + requestPermission: () => Promise; + onMessage: () => () => void; + onNotificationOpenedApp: () => () => void; + getInitialNotification: () => Promise; + setBackgroundMessageHandler: () => void; + subscribeToTopic: () => Promise; + unsubscribeFromTopic: () => Promise; + }; + AuthorizationStatus: { + NOT_DETERMINED: number; + DENIED: number; + AUTHORIZED: number; + PROVISIONAL: number; + }; +} -messaging.AuthorizationStatus = { - NOT_DETERMINED: -1, - DENIED: 0, - AUTHORIZED: 1, - PROVISIONAL: 2, -}; +export const messaging: Messaging = Object.assign( + () => ({ + getToken: async () => 'web-token', + deleteToken: async () => {}, + hasPermission: async () => 1, + requestPermission: async () => 1, + onMessage: () => () => {}, + onNotificationOpenedApp: () => () => {}, + getInitialNotification: async () => null, + setBackgroundMessageHandler: () => {}, + subscribeToTopic: async () => {}, + unsubscribeFromTopic: async () => {}, + }), + { + AuthorizationStatus: { + NOT_DETERMINED: -1, + DENIED: 0, + AUTHORIZED: 1, + PROVISIONAL: 2, + }, + } +); // Firebase App shim export const firebaseApp = { diff --git a/src/lib/platform.ts b/src/lib/platform.ts index b7a817bf..c7fac345 100644 --- a/src/lib/platform.ts +++ b/src/lib/platform.ts @@ -8,7 +8,10 @@ import { Platform } from 'react-native'; export const isWeb = Platform.OS === 'web'; // Check if running in Electron (desktop app wrapped around web) -export const isElectron = typeof window !== 'undefined' && window.process?.type === 'renderer'; +// Prefer preload-exposed electronAPI for contextIsolation-enabled environments +export const isElectron = + typeof window !== 'undefined' && + (!!(window as any).electronAPI || window.process?.type === 'renderer'); // Check if running on a desktop platform (Electron or native desktop) export const isDesktop = isElectron || Platform.OS === 'macos' || Platform.OS === 'windows'; diff --git a/src/services/push-notification.electron.ts b/src/services/push-notification.electron.ts index 6be855be..feb09d98 100644 --- a/src/services/push-notification.electron.ts +++ b/src/services/push-notification.electron.ts @@ -11,7 +11,7 @@ declare global { interface Window { electronAPI?: { showNotification: (options: { title: string; body: string; data: any }) => Promise; - onNotificationClicked: (callback: (data: any) => void) => void; + onNotificationClicked: (callback: (data: any) => void) => () => void; platform: string; isElectron: boolean; }; @@ -45,9 +45,15 @@ class ElectronPushNotificationService { return; } + // Clean up any existing listener before registering a new one (HMR support) + if (this.cleanupListener) { + this.cleanupListener(); + this.cleanupListener = null; + } + // Listen for notification clicks from the main process - window.electronAPI.onNotificationClicked(this.handleNotificationClick); - this.cleanupListener = null; // No cleanup needed for Electron API + // Capture the unsubscribe function returned by the preload API + this.cleanupListener = window.electronAPI.onNotificationClicked(this.handleNotificationClick); this.isInitialized = true; logger.info({ diff --git a/src/services/push-notification.web.ts b/src/services/push-notification.web.ts index ace87b48..2f635070 100644 --- a/src/services/push-notification.web.ts +++ b/src/services/push-notification.web.ts @@ -45,6 +45,10 @@ class WebPushNotificationService { // Listen for messages from service worker navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerMessage); + // Wait for service worker to be ready, then send CLIENT_READY handshake + await navigator.serviceWorker.ready; + this.sendClientReadyHandshake(); + this.isInitialized = true; } catch (error) { logger.error({ @@ -54,6 +58,21 @@ class WebPushNotificationService { } } + /** + * Send CLIENT_READY handshake to service worker + * This signals that the client is ready to receive notification messages + */ + private sendClientReadyHandshake(): void { + if (navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage({ + type: 'CLIENT_READY', + }); + logger.info({ + message: 'Sent CLIENT_READY handshake to service worker', + }); + } + } + /** * Handle messages from the service worker */ diff --git a/src/stores/app/livekit-store.ts b/src/stores/app/livekit-store.ts index 2afb88ac..3e2f88bc 100644 --- a/src/stores/app/livekit-store.ts +++ b/src/stores/app/livekit-store.ts @@ -226,7 +226,7 @@ export const useLiveKitStore = create((set, get) => ({ android: { channelId: 'notif', asForegroundService: true, - foregroundServiceTypes: [2], // 2 = MICROPHONE + foregroundServiceTypes: [AndroidForegroundServiceType.MICROPHONE], smallIcon: 'ic_launcher', // Ensure this icon exists in res/drawable }, }); diff --git a/types/docker-env.d.ts b/types/docker-env.d.ts index 13292d6f..7cd92ad4 100644 --- a/types/docker-env.d.ts +++ b/types/docker-env.d.ts @@ -16,7 +16,7 @@ interface DockerEnvConfig { LOGGING_KEY: string; APP_KEY: string; UNIT_MAPBOX_PUBKEY: string; - IS_MOBILE_APP: boolean; + IS_MOBILE_APP: string; SENTRY_DSN: string; COUNTLY_APP_KEY: string; COUNTLY_SERVER_URL: string; diff --git a/yarn.lock b/yarn.lock index 491ee13f..a2c5bfd7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7884,6 +7884,13 @@ electron-publish@26.3.4: lazy-val "^1.0.5" mime "^2.5.2" +electron-squirrel-startup@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/electron-squirrel-startup/-/electron-squirrel-startup-1.0.1.tgz#c9171568d724884c7a2b03760bfeedcf921c63ab" + integrity sha512-sTfFIHGku+7PsHLJ7v0dRcZNkALrV+YEozINTW8X1nM//e5O3L+rfYuvSW00lmGHnYmUjARZulD8F2V8ISI9RA== + dependencies: + debug "^2.2.0" + electron-to-chromium@^1.5.218: version "1.5.227" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.227.tgz#c81b6af045b0d6098faed261f0bd611dc282d3a7" From ce737ddd11817951c5792f2376f9382e0c1f2b63 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Mon, 26 Jan 2026 11:41:37 -0800 Subject: [PATCH 4/5] RU-T47 PR#202 Fixes --- .github/workflows/react-native-cicd.yml | 286 +++--------------------- app.config.ts | 2 +- nginx.conf | 6 +- package.json | 2 +- scripts/extract-release-notes.sh | 124 ++++++++++ src/components/maps/map-view.web.tsx | 14 +- src/components/maps/pin-marker.tsx | 5 +- src/lib/platform.ts | 2 +- src/services/push-notification.web.ts | 101 +++++++-- src/stores/app/livekit-store.ts | 2 +- 10 files changed, 257 insertions(+), 287 deletions(-) create mode 100755 scripts/extract-release-notes.sh diff --git a/.github/workflows/react-native-cicd.yml b/.github/workflows/react-native-cicd.yml index ef560ee1..e3fbcdb8 100644 --- a/.github/workflows/react-native-cicd.yml +++ b/.github/workflows/react-native-cicd.yml @@ -101,7 +101,7 @@ jobs: cache: 'yarn' - name: 📦 Setup yarn cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cache/yarn @@ -144,7 +144,7 @@ jobs: token: ${{ secrets.EXPO_TOKEN }} - name: 📦 Setup yarn cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cache/yarn @@ -197,7 +197,7 @@ jobs: fi - name: 📱 Setup EAS build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.eas-build-local key: ${{ runner.os }}-eas-build-local-${{ hashFiles('**/package.json') }} @@ -301,126 +301,7 @@ jobs: - name: 📋 Prepare Release Notes file if: ${{ matrix.platform == 'android' }} - run: | - set -eo pipefail - - # Function to extract release notes from PR body - extract_release_notes() { - local body="$1" - - # First pass: Remove everything between CodeRabbit comment markers using sed - # This handles multi-line auto-generated content - local cleaned_body="$(printf '%s\n' "$body" \ - | sed '//,//d')" - - # Second pass: Remove the "Summary by CodeRabbit" section - cleaned_body="$(printf '%s\n' "$cleaned_body" \ - | awk ' - BEGIN { skip=0 } - /^## Summary by CodeRabbit/ { skip=1; next } - /^## / && skip==1 { skip=0 } - skip==0 { print } - ')" - - # Third pass: Remove any remaining HTML comment lines - cleaned_body="$(printf '%s\n' "$cleaned_body" | sed '/^$/d' | sed '/^$/d')" - - # Fourth pass: Remove specific CodeRabbit lines - cleaned_body="$(printf '%s\n' "$cleaned_body" \ - | (grep -v '✏️ Tip: You can customize this high-level summary in your review settings\.' || true) \ - | (grep -v '' || true) \ - | (grep -v '' || true))" - - # Fifth pass: Trim leading and trailing whitespace/empty lines - cleaned_body="$(printf '%s\n' "$cleaned_body" | sed '/^$/d' | awk 'NF {p=1} p')" - - # Try to extract content under "## Release Notes" heading if it exists - local notes="$(printf '%s\n' "$cleaned_body" \ - | awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')" - - # If no specific "Release Notes" section found, use the entire cleaned body - if [ -z "$notes" ]; then - notes="$cleaned_body" - fi - - # Final trim - notes="$(printf '%s\n' "$notes" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" - - 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 - echo "PR body length: ${#PR_BODY}" - NOTES="$(extract_release_notes "$PR_BODY")" - echo "Extracted notes length: ${#NOTES}" - else - echo "Warning: PR body is empty" - 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 - echo "PR body length: ${#PR_BODY}" - NOTES="$(extract_release_notes "$PR_BODY")" - echo "Extracted notes length: ${#NOTES}" - else - echo "Warning: PR body is empty" - fi - else - echo "No associated PR found for this commit" - fi - fi - fi - - # Fallback to recent commits if no PR body found (skip merge commits) - if [ -z "$NOTES" ]; then - echo "No PR body found, using recent commits (excluding merge commits)..." - NOTES="$(git log -n 10 --pretty=format:'- %s' --no-merges | head -n 5)" - 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 }} + run: ./scripts/extract-release-notes.sh "7.${{ github.run_number }}" "${{ github.event_name }}" "${{ github.repository }}" "${{ github.sha }}" "${{ github.token }}" - name: 📝 Send Release Notes to Changerawr if: ${{ matrix.platform == 'android' }} @@ -483,6 +364,8 @@ jobs: build-web: needs: test if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch' + permissions: + contents: read runs-on: ubuntu-latest environment: RNBuild steps: @@ -496,7 +379,7 @@ jobs: cache: 'yarn' - name: 📦 Setup yarn cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cache/yarn @@ -607,7 +490,7 @@ jobs: cache: 'yarn' - name: 📦 Setup yarn cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cache/yarn @@ -702,131 +585,21 @@ jobs: electron-dist/*.rpm retention-days: 7 + release-electron: + needs: build-electron + if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch' + permissions: + contents: write # Required for creating releases + runs-on: ubuntu-latest + environment: RNBuild + steps: + - name: 🏗 Checkout repository + uses: actions/checkout@v4 + - name: 📋 Prepare Release Notes file - if: ${{ matrix.os == 'ubuntu-latest' }} - run: | - set -eo pipefail - - # Function to extract release notes from PR body - extract_release_notes() { - local body="$1" - - # First pass: Remove everything between CodeRabbit comment markers using sed - # This handles multi-line auto-generated content - local cleaned_body="$(printf '%s\n' "$body" \ - | sed '//,//d')" - - # Second pass: Remove the "Summary by CodeRabbit" section - cleaned_body="$(printf '%s\n' "$cleaned_body" \ - | awk ' - BEGIN { skip=0 } - /^## Summary by CodeRabbit/ { skip=1; next } - /^## / && skip==1 { skip=0 } - skip==0 { print } - ')" - - # Third pass: Remove any remaining HTML comment lines - cleaned_body="$(printf '%s\n' "$cleaned_body" | sed '/^$/d' | sed '/^$/d')" - - # Fourth pass: Remove specific CodeRabbit lines - cleaned_body="$(printf '%s\n' "$cleaned_body" \ - | (grep -v '✏️ Tip: You can customize this high-level summary in your review settings\.' || true) \ - | (grep -v '' || true) \ - | (grep -v '' || true))" - - # Fifth pass: Trim leading and trailing whitespace/empty lines - cleaned_body="$(printf '%s\n' "$cleaned_body" | sed '/^$/d' | awk 'NF {p=1} p')" - - # Try to extract content under "## Release Notes" heading if it exists - local notes="$(printf '%s\n' "$cleaned_body" \ - | awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')" - - # If no specific "Release Notes" section found, use the entire cleaned body - if [ -z "$notes" ]; then - notes="$cleaned_body" - fi - - # Final trim - notes="$(printf '%s\n' "$notes" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" - - 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 - echo "PR body length: ${#PR_BODY}" - NOTES="$(extract_release_notes "$PR_BODY")" - echo "Extracted notes length: ${#NOTES}" - else - echo "Warning: PR body is empty" - 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 - echo "PR body length: ${#PR_BODY}" - NOTES="$(extract_release_notes "$PR_BODY")" - echo "Extracted notes length: ${#NOTES}" - else - echo "Warning: PR body is empty" - fi - else - echo "No associated PR found for this commit" - fi - fi - fi - - # Fallback to recent commits if no PR body found (skip merge commits) - if [ -z "$NOTES" ]; then - echo "No PR body found, using recent commits (excluding merge commits)..." - NOTES="$(git log -n 10 --pretty=format:'- %s' --no-merges | head -n 5)" - 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 }} + run: ./scripts/extract-release-notes.sh "7.${{ github.run_number }}" "${{ github.event_name }}" "${{ github.repository }}" "${{ github.sha }}" "${{ github.token }}" - - name: � Download all Electron artifacts - if: ${{ matrix.os == 'ubuntu-latest' }} + - name: 📥 Download all Electron artifacts uses: actions/download-artifact@v4 with: path: electron-artifacts/ @@ -834,19 +607,23 @@ jobs: merge-multiple: false - name: 📦 Prepare Electron release artifacts - if: ${{ matrix.os == 'ubuntu-latest' }} run: | - # Create release-artifacts directory - mkdir -p release-artifacts + # Find all matching Electron artifacts + FILES=$(find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \)) + + if [ -z "$FILES" ]; then + echo "Error: No Electron artifacts found in electron-artifacts/ directory." + exit 1 + fi - # Copy all Electron artifacts to a flat directory for release - find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) -exec cp {} release-artifacts/ \; + # Create release-artifacts directory and copy files + mkdir -p release-artifacts + echo "$FILES" | xargs -I {} cp {} release-artifacts/ echo "Release artifacts prepared:" ls -lh release-artifacts/ - name: 📦 Create GitHub Release with Electron builds - if: ${{ matrix.os == 'ubuntu-latest' }} uses: ncipollo/release-action@v1 with: tag: '7.${{ github.run_number }}' @@ -856,3 +633,4 @@ jobs: name: '7.${{ github.run_number }}' artifacts: 'release-artifacts/*' bodyFile: 'RELEASE_NOTES.md' + diff --git a/app.config.ts b/app.config.ts index da83d6c2..7e2b8fc9 100644 --- a/app.config.ts +++ b/app.config.ts @@ -107,7 +107,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ [ '@rnmapbox/maps', { - RNMapboxMapsVersion: '11.8.0', + RNMapboxMapsVersion: '11.16.2', }, ], [ diff --git a/nginx.conf b/nginx.conf index d1346d69..4ec03b0c 100644 --- a/nginx.conf +++ b/nginx.conf @@ -38,7 +38,7 @@ http { # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self';" always; # Static assets with cache location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { @@ -46,7 +46,7 @@ http { add_header Cache-Control "public, immutable"; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; - add_header X-XSS-Protection "1; mode=block" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self';" always; try_files $uri =404; } @@ -58,8 +58,8 @@ http { # Health check endpoint location /health { access_log off; - return 200 "healthy\n"; add_header Content-Type text/plain; + return 200 "healthy\n"; } } } diff --git a/package.json b/package.json index d2aff6d1..149d7eb4 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,6 @@ "@sentry/react-native": "~6.14.0", "@shopify/flash-list": "1.7.6", "@tanstack/react-query": "~5.52.1", - "@types/mapbox-gl": "3.4.1", "app-icon-badge": "^0.1.2", "axios": "~1.12.0", "babel-plugin-module-resolver": "^5.0.2", @@ -187,6 +186,7 @@ "@types/i18n-js": "~3.8.9", "@types/jest": "~29.5.14", "@types/lodash.memoize": "~4.1.9", + "@types/mapbox-gl": "3.4.1", "@types/react": "~19.0.10", "@types/react-native-base64": "~0.2.2", "@typescript-eslint/eslint-plugin": "~5.62.0", diff --git a/scripts/extract-release-notes.sh b/scripts/extract-release-notes.sh new file mode 100755 index 00000000..50014e6f --- /dev/null +++ b/scripts/extract-release-notes.sh @@ -0,0 +1,124 @@ +#!/bin/bash +set -eo pipefail + +# Argument: github_run_number, github_event_name, github_repository, github_sha, github_token +RUN_NUMBER=$1 +EVENT_NAME=$2 +REPOSITORY=$3 +SHA=$4 +export GH_TOKEN=$5 + +# Function to extract release notes from PR body +extract_release_notes() { + local body="$1" + + # First pass: Remove everything between CodeRabbit comment markers using sed + local cleaned_body="$(printf '%s\n' "$body" \ + | sed '//,//d')" + + # Second pass: Remove the "Summary by CodeRabbit" section + cleaned_body="$(printf '%s\n' "$cleaned_body" \ + | awk ' + BEGIN { skip=0 } + /^## Summary by CodeRabbit/ { skip=1; next } + /^## / && skip==1 { skip=0 } + skip==0 { print } + ')" + + # Third pass: Remove any remaining HTML comment lines + cleaned_body="$(printf '%s\n' "$cleaned_body" | sed '/^$/d' | sed '/^$/d')" + + # Fourth pass: Remove specific CodeRabbit lines + cleaned_body="$(printf '%s\n' "$cleaned_body" \ + | (grep -v '✏️ Tip: You can customize this high-level summary in your review settings\.' || true) \ + | (grep -v '' || true) \ + | (grep -v '' || true))" + + # Fifth pass: Trim leading and trailing whitespace/empty lines + cleaned_body="$(printf '%s\n' "$cleaned_body" | sed '/^$/d' | awk 'NF {p=1} p')" + + # Try to extract content under "## Release Notes" heading if it exists + local notes="$(printf '%s\n' "$cleaned_body" \ + | awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')" + + # If no specific "Release Notes" section found, use the entire cleaned body + if [ -z "$notes" ]; then + notes="$cleaned_body" + fi + + # Final trim + notes="$(printf '%s\n' "$notes" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + + printf '%s\n' "$notes" +} + +# Determine source of release notes +NOTES="" + +# Check if this was triggered by a push event (likely a merge) +if [ "$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 + echo "PR body length: ${#PR_BODY}" + NOTES="$(extract_release_notes "$PR_BODY")" + echo "Extracted notes length: ${#NOTES}" + else + echo "Warning: PR body is empty" + 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/$REPOSITORY/commits/$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 + echo "PR body length: ${#PR_BODY}" + NOTES="$(extract_release_notes "$PR_BODY")" + echo "Extracted notes length: ${#NOTES}" + else + echo "Warning: PR body is empty" + fi + else + echo "No associated PR found for this commit" + fi + fi +fi + +# Fallback to recent commits if no PR body found (skip merge commits) +if [ -z "$NOTES" ]; then + echo "No PR body found, using recent commits (excluding merge commits)..." + NOTES="$(git log -n 10 --pretty=format:'- %s' --no-merges | head -n 5)" +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.$RUN_NUMBER - $(date +%Y-%m-%d)" + echo + printf '%s\n' "$NOTES" +} > RELEASE_NOTES.md + +echo "Release notes prepared:" +cat RELEASE_NOTES.md diff --git a/src/components/maps/map-view.web.tsx b/src/components/maps/map-view.web.tsx index d43f2a0f..738891a9 100644 --- a/src/components/maps/map-view.web.tsx +++ b/src/components/maps/map-view.web.tsx @@ -248,6 +248,7 @@ export const PointAnnotation: React.FC = ({ id, coordinate const map = React.useContext(MapContext); const markerRef = useRef(null); const containerRef = useRef(null); + const containerRootRef = useRef(null); useEffect(() => { if (!map || !coordinate) return; @@ -257,9 +258,11 @@ export const PointAnnotation: React.FC = ({ id, coordinate container.style.cursor = 'pointer'; containerRef.current = container; - // Render React children into the container using createPortal + // Render React children into the container using createRoot if (children) { - ReactDOM.render(<>{children}, container); + const root = (ReactDOM as any).createRoot(container); + root.render(<>{children}); + containerRootRef.current = root; } // Determine marker options based on anchor prop @@ -302,9 +305,10 @@ export const PointAnnotation: React.FC = ({ id, coordinate containerRef.current.removeEventListener('click', onSelected); } - // Unmount React children from the portal - if (children && containerRef.current) { - ReactDOM.unmountComponentAtNode(containerRef.current); + // Unmount React children + if (children && containerRootRef.current) { + containerRootRef.current.unmount(); + containerRootRef.current = null; } // Remove marker from map diff --git a/src/components/maps/pin-marker.tsx b/src/components/maps/pin-marker.tsx index 5f931fa9..7de982ec 100644 --- a/src/components/maps/pin-marker.tsx +++ b/src/components/maps/pin-marker.tsx @@ -11,18 +11,17 @@ interface PinMarkerProps { imagePath?: MapIconKey; title: string; size?: number; - markerRef?: React.ElementRef | null; onPress?: () => void; } const PinMarker: React.FC = ({ imagePath, title, size = 32, onPress }) => { const { colorScheme } = useColorScheme(); - const icon = imagePath ? MAP_ICONS[imagePath.toLowerCase() as MapIconKey] : MAP_ICONS['call']; + const icon = (imagePath && MAP_ICONS[imagePath.toLowerCase() as MapIconKey]) || MAP_ICONS['call']; return ( - + {title} diff --git a/src/lib/platform.ts b/src/lib/platform.ts index c7fac345..2b8c89bd 100644 --- a/src/lib/platform.ts +++ b/src/lib/platform.ts @@ -61,7 +61,7 @@ declare global { }; electronAPI?: { showNotification: (options: { title: string; body: string; data: any }) => Promise; - onNotificationClicked: (callback: (data: any) => void) => void; + onNotificationClicked: (callback: (data: any) => void) => () => void; platform: string; isElectron: boolean; }; diff --git a/src/services/push-notification.web.ts b/src/services/push-notification.web.ts index 2f635070..98ae6489 100644 --- a/src/services/push-notification.web.ts +++ b/src/services/push-notification.web.ts @@ -49,6 +49,20 @@ class WebPushNotificationService { await navigator.serviceWorker.ready; this.sendClientReadyHandshake(); + // Additionally add a one-time 'controllerchange' listener to handle cases where controller is null initially (first-install) + const onControllerChange = () => { + if (navigator.serviceWorker.controller) { + logger.info({ + message: 'Service worker controller changed, sending CLIENT_READY', + }); + navigator.serviceWorker.controller.postMessage({ + type: 'CLIENT_READY', + }); + navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange); + } + }; + navigator.serviceWorker.addEventListener('controllerchange', onControllerChange); + this.isInitialized = true; } catch (error) { logger.error({ @@ -68,7 +82,18 @@ class WebPushNotificationService { type: 'CLIENT_READY', }); logger.info({ - message: 'Sent CLIENT_READY handshake to service worker', + message: 'Sent CLIENT_READY handshake to service worker controller', + }); + } else if (this.registration?.active) { + this.registration.active.postMessage({ + type: 'CLIENT_READY', + }); + logger.info({ + message: 'Sent CLIENT_READY handshake to active service worker (registration.active fallback)', + }); + } else { + logger.info({ + message: 'Silently skipping send CLIENT_READY: no controller or active registration available', }); } } @@ -78,20 +103,27 @@ class WebPushNotificationService { */ private handleServiceWorkerMessage = (event: MessageEvent): void => { if (event.data?.type === 'NOTIFICATION_CLICK') { - const data = event.data.data; - logger.info({ - message: 'Notification clicked from service worker', - context: { data }, - }); + const data = event.data?.data ?? undefined; + + // Only proceed if data is an object and has the expected fields + if (data && typeof data === 'object' && data.eventCode) { + logger.info({ + message: 'Notification clicked from service worker', + context: { data }, + }); - // Show the notification modal - if (data.eventCode) { + // Show the notification modal usePushNotificationModalStore.getState().showNotificationModal({ eventCode: data.eventCode, title: data.title, body: data.body || data.message, data, }); + } else { + logger.warn({ + message: 'Notification click received with missing or invalid data', + context: { data: event.data }, + }); } } }; @@ -169,17 +201,50 @@ class WebPushNotificationService { * Unsubscribe from push notifications */ async unsubscribe(): Promise { - if (!this.pushSubscription) { - return true; - } - try { - await this.pushSubscription.unsubscribe(); - this.pushSubscription = null; - logger.info({ - message: 'Successfully unsubscribed from push notifications', - }); - return true; + // If pushSubscription is null, try to retrieve it from the push manager as a fallback + if (!this.pushSubscription && this.registration) { + try { + this.pushSubscription = await this.registration.pushManager.getSubscription(); + } catch (error) { + logger.error({ + message: 'Failed to retrieve active push subscription during unsubscribe', + context: { error }, + }); + } + } + + if (!this.pushSubscription) { + logger.info({ + message: 'No active push subscription found to unsubscribe', + }); + return true; + } + + const success = await this.pushSubscription.unsubscribe(); + + if (success) { + this.pushSubscription = null; + + // Clear any potential persisted client-side records + // Explicitly clearing any typical local storage keys as a safety measure + try { + localStorage.removeItem('push_subscription'); + localStorage.removeItem('push_endpoint'); + } catch (storageError) { + // Ignore errors from localStorage if it's not available + } + + logger.info({ + message: 'Successfully unsubscribed from push notifications', + }); + } else { + logger.warn({ + message: 'Push subscription unsubscribe returned false', + }); + } + + return success; } catch (error) { logger.error({ message: 'Failed to unsubscribe from push notifications', diff --git a/src/stores/app/livekit-store.ts b/src/stores/app/livekit-store.ts index 3e2f88bc..ecc8811b 100644 --- a/src/stores/app/livekit-store.ts +++ b/src/stores/app/livekit-store.ts @@ -226,7 +226,7 @@ export const useLiveKitStore = create((set, get) => ({ android: { channelId: 'notif', asForegroundService: true, - foregroundServiceTypes: [AndroidForegroundServiceType.MICROPHONE], + foregroundServiceTypes: [AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MICROPHONE], smallIcon: 'ic_launcher', // Ensure this icon exists in res/drawable }, }); From f238be4598afafa7d4cef79f14512efa68a5cf8a Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Mon, 26 Jan 2026 13:53:52 -0800 Subject: [PATCH 5/5] RU-T47 PR#202 fixes --- .github/workflows/react-native-cicd.yml | 10 +++---- src/app/(app)/calls.tsx | 2 +- src/components/calls/call-images-modal.tsx | 2 +- src/components/maps/map-view.web.tsx | 23 +++++++++------- src/components/ui/bottomsheet/index.tsx | 26 +++++++++---------- src/lib/platform.ts | 4 +-- .../__tests__/callkeep.service.ios.test.ts | 7 ++--- src/services/callkeep.service.ios.ts | 6 ++--- src/services/push-notification.ts | 12 ++++----- src/services/push-notification.web.ts | 4 +-- src/types/declarations.d.ts | 7 +++++ 11 files changed, 55 insertions(+), 48 deletions(-) create mode 100644 src/types/declarations.d.ts diff --git a/.github/workflows/react-native-cicd.yml b/.github/workflows/react-native-cicd.yml index e3fbcdb8..ee602934 100644 --- a/.github/workflows/react-native-cicd.yml +++ b/.github/workflows/react-native-cicd.yml @@ -608,17 +608,15 @@ jobs: - name: 📦 Prepare Electron release artifacts run: | - # Find all matching Electron artifacts - FILES=$(find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \)) - - if [ -z "$FILES" ]; then + # Find all matching Electron artifacts and check for existence + if [ -z "$(find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) -print -quit)" ]; then echo "Error: No Electron artifacts found in electron-artifacts/ directory." exit 1 fi - # Create release-artifacts directory and copy files + # Create release-artifacts directory and copy files using null-delimited find/xargs mkdir -p release-artifacts - echo "$FILES" | xargs -I {} cp {} release-artifacts/ + find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) -print0 | xargs -0 cp -t release-artifacts/ echo "Release artifacts prepared:" ls -lh release-artifacts/ diff --git a/src/app/(app)/calls.tsx b/src/app/(app)/calls.tsx index 0e4fa7c4..005e0a1a 100644 --- a/src/app/(app)/calls.tsx +++ b/src/app/(app)/calls.tsx @@ -106,7 +106,7 @@ export default function Calls() { {/* FAB button for creating new call - only show if user has permission */} {canUserCreateCalls ? ( - + ) : null} diff --git a/src/components/calls/call-images-modal.tsx b/src/components/calls/call-images-modal.tsx index 7083d90a..42bee422 100644 --- a/src/components/calls/call-images-modal.tsx +++ b/src/components/calls/call-images-modal.tsx @@ -262,7 +262,7 @@ const CallImagesModal: React.FC = ({ isOpen, onClose, call // At this point, imageSource is guaranteed to be non-null return ( - handleImagePress(imageSource, item.Name)} testID={`image-${item.Id}-touchable`} activeOpacity={0.7} style={{ width: '100%' }} delayPressIn={0} delayPressOut={0}> + handleImagePress(imageSource!, item.Name)} testID={`image-${item.Id}-touchable`} activeOpacity={0.7} style={{ width: '100%' }} delayPressIn={0} delayPressOut={0}> (null); +export const MapContext = React.createContext(null); // StyleURL constants matching native Mapbox SDK export const StyleURL = { @@ -76,7 +76,7 @@ export const MapView = forwardRef( ref ) => { const mapContainer = useRef(null); - const map = useRef(null); + const map = useRef(null); const [isLoaded, setIsLoaded] = useState(false); useImperativeHandle(ref, () => ({ @@ -166,7 +166,7 @@ interface CameraProps { // Camera component export const Camera = forwardRef(({ centerCoordinate, zoomLevel, heading, pitch, animationDuration = 1000, followUserLocation, followZoomLevel }, ref) => { const map = React.useContext(MapContext); - const geolocateControl = useRef(null); + const geolocateControl = useRef(null); useImperativeHandle(ref, () => ({ setCamera: (options: { centerCoordinate?: [number, number]; zoomLevel?: number; heading?: number; pitch?: number; animationDuration?: number }) => { @@ -203,6 +203,8 @@ export const Camera = forwardRef(({ centerCoordinate, zoomLeve useEffect(() => { if (!map || !followUserLocation) return; + let triggerTimeoutId: any; + // Add geolocate control for following user if (!geolocateControl.current) { geolocateControl.current = new mapboxgl.GeolocateControl({ @@ -216,11 +218,14 @@ export const Camera = forwardRef(({ centerCoordinate, zoomLeve } // Trigger tracking after control is added - setTimeout(() => { + triggerTimeoutId = setTimeout(() => { geolocateControl.current?.trigger(); }, 100); return () => { + if (triggerTimeoutId) { + clearTimeout(triggerTimeoutId); + } if (geolocateControl.current) { map.removeControl(geolocateControl.current); geolocateControl.current = null; @@ -246,7 +251,7 @@ interface PointAnnotationProps { // PointAnnotation component export const PointAnnotation: React.FC = ({ id, coordinate, title, children, anchor = { x: 0.5, y: 0.5 }, onSelected }) => { const map = React.useContext(MapContext); - const markerRef = useRef(null); + const markerRef = useRef(null); const containerRef = useRef(null); const containerRootRef = useRef(null); @@ -266,18 +271,16 @@ export const PointAnnotation: React.FC = ({ id, coordinate } // Determine marker options based on anchor prop - const markerOptions: mapboxgl.MarkerOptions = { + const markerOptions: any = { element: container, }; // Handle anchor prop - if it's a string, use it as mapbox anchor if (typeof anchor === 'string') { - markerOptions.anchor = anchor as mapboxgl.Anchor; + markerOptions.anchor = anchor as any; } - markerRef.current = new mapboxgl.Marker(markerOptions) - .setLngLat(coordinate) - .addTo(map); + markerRef.current = new mapboxgl.Marker(markerOptions).setLngLat(coordinate).addTo(map); // If anchor is an {x, y} object, convert to pixel offset if (typeof anchor === 'object' && anchor !== null && 'x' in anchor && 'y' in anchor) { diff --git a/src/components/ui/bottomsheet/index.tsx b/src/components/ui/bottomsheet/index.tsx index 06800a6f..e04e7ede 100644 --- a/src/components/ui/bottomsheet/index.tsx +++ b/src/components/ui/bottomsheet/index.tsx @@ -36,14 +36,14 @@ const bottomSheetItemStyle = tva({ const BottomSheetContext = createContext<{ visible: boolean; - bottomSheetRef: React.RefObject; + bottomSheetRef: React.RefObject | null; handleClose: () => void; handleOpen: () => void; }>({ visible: false, - bottomSheetRef: { current: null }, - handleClose: () => {}, - handleOpen: () => {}, + bottomSheetRef: null, + handleClose: () => { }, + handleOpen: () => { }, }); type IBottomSheetProps = React.ComponentProps; @@ -68,7 +68,7 @@ export const BottomSheet = ({ snapToIndex = 1, onOpen, onClose, ...props }: { sn , handleClose, handleOpen, }} @@ -166,14 +166,14 @@ export const BottomSheetContent = ({ ...props }: IBottomSheetContent) => { const keyDownHandlers = useMemo(() => { return Platform.OS === 'web' ? { - onKeyDown: (e: React.KeyboardEvent) => { - if (e.key === 'Escape') { - e.preventDefault(); - handleClose(); - return; - } - }, - } + onKeyDown: (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + e.preventDefault(); + handleClose(); + return; + } + }, + } : {}; }, [handleClose]); diff --git a/src/lib/platform.ts b/src/lib/platform.ts index 2b8c89bd..5d4be2e0 100644 --- a/src/lib/platform.ts +++ b/src/lib/platform.ts @@ -9,9 +9,7 @@ export const isWeb = Platform.OS === 'web'; // Check if running in Electron (desktop app wrapped around web) // Prefer preload-exposed electronAPI for contextIsolation-enabled environments -export const isElectron = - typeof window !== 'undefined' && - (!!(window as any).electronAPI || window.process?.type === 'renderer'); +export const isElectron = typeof window !== 'undefined' && (!!(window as any).electronAPI || window.process?.type === 'renderer'); // Check if running on a desktop platform (Electron or native desktop) export const isDesktop = isElectron || Platform.OS === 'macos' || Platform.OS === 'windows'; diff --git a/src/services/__tests__/callkeep.service.ios.test.ts b/src/services/__tests__/callkeep.service.ios.test.ts index c66aedef..9b86dcd4 100644 --- a/src/services/__tests__/callkeep.service.ios.test.ts +++ b/src/services/__tests__/callkeep.service.ios.test.ts @@ -157,9 +157,10 @@ describe('CallKeepService', () => { // Register callback service.setMuteStateCallback(mockMuteCallback); + // Simulate mute event // Simulate mute event const muteEventHandler = mockCallKeep.addEventListener.mock.calls.find( - call => call[0] === 'didPerformSetMutedCallAction' + (call: any[]) => call[0] === 'didPerformSetMutedCallAction' )?.[1] as any; expect(muteEventHandler).toBeDefined(); @@ -191,7 +192,7 @@ describe('CallKeepService', () => { service.setMuteStateCallback(errorCallback); const muteEventHandler = mockCallKeep.addEventListener.mock.calls.find( - call => call[0] === 'didPerformSetMutedCallAction' + (call: any[]) => call[0] === 'didPerformSetMutedCallAction' )?.[1] as any; if (muteEventHandler) { @@ -226,7 +227,7 @@ describe('CallKeepService', () => { service.setMuteStateCallback(null); const muteEventHandler = mockCallKeep.addEventListener.mock.calls.find( - call => call[0] === 'didPerformSetMutedCallAction' + (call: any[]) => call[0] === 'didPerformSetMutedCallAction' )?.[1] as any; if (muteEventHandler) { diff --git a/src/services/callkeep.service.ios.ts b/src/services/callkeep.service.ios.ts index 8ebef9fe..0f073d30 100644 --- a/src/services/callkeep.service.ios.ts +++ b/src/services/callkeep.service.ios.ts @@ -247,7 +247,7 @@ export class CallKeepService { }); // Call ended from CallKit UI - RNCallKeep.addEventListener('endCall', ({ callUUID }) => { + RNCallKeep.addEventListener('endCall', ({ callUUID }: { callUUID: string }) => { logger.info({ message: 'CallKeep call ended from system UI', context: { callUUID }, @@ -260,7 +260,7 @@ export class CallKeepService { }); // Call answered (not typically used for outgoing calls, but good to handle) - RNCallKeep.addEventListener('answerCall', ({ callUUID }) => { + RNCallKeep.addEventListener('answerCall', ({ callUUID }: { callUUID: string }) => { logger.debug({ message: 'CallKeep call answered', context: { callUUID }, @@ -268,7 +268,7 @@ export class CallKeepService { }); // Mute/unmute events - RNCallKeep.addEventListener('didPerformSetMutedCallAction', ({ muted, callUUID }) => { + RNCallKeep.addEventListener('didPerformSetMutedCallAction', ({ muted, callUUID }: { muted: boolean; callUUID: string }) => { logger.debug({ message: 'CallKeep mute state changed', context: { muted, callUUID }, diff --git a/src/services/push-notification.ts b/src/services/push-notification.ts index 71a84923..cc477de3 100644 --- a/src/services/push-notification.ts +++ b/src/services/push-notification.ts @@ -109,7 +109,7 @@ class PushNotificationService { } } - private handleRemoteMessage = async (remoteMessage: FirebaseMessagingTypes.RemoteMessage): Promise => { + private handleRemoteMessage = async (remoteMessage: any): Promise => { logger.info({ message: 'FCM message received', context: { @@ -238,7 +238,7 @@ class PushNotificationService { // Register background message handler (only once) if (!this.backgroundMessageHandlerRegistered) { - messaging().setBackgroundMessageHandler(async (remoteMessage) => { + messaging().setBackgroundMessageHandler(async (remoteMessage: any) => { logger.info({ message: 'Background FCM message received', context: { @@ -278,7 +278,7 @@ class PushNotificationService { this.fcmOnMessageUnsubscribe = messaging().onMessage(this.handleRemoteMessage); // Listen for notification opened app (when user taps on notification) - this.fcmOnNotificationOpenedAppUnsubscribe = messaging().onNotificationOpenedApp((remoteMessage) => { + this.fcmOnNotificationOpenedAppUnsubscribe = messaging().onNotificationOpenedApp((remoteMessage: any) => { logger.info({ message: 'Notification opened app (from background)', context: { @@ -319,7 +319,7 @@ class PushNotificationService { setTimeout(() => { messaging() .getInitialNotification() - .then((remoteMessage) => { + .then((remoteMessage: any) => { if (remoteMessage) { logger.info({ message: 'App opened from notification (killed state)', @@ -356,7 +356,7 @@ class PushNotificationService { }, 500); } }) - .catch((error) => { + .catch((error: any) => { logger.error({ message: 'Error checking initial notification', context: { error }, @@ -468,7 +468,7 @@ class PushNotificationService { // Register device with backend await registerUnitDevice({ UnitId: unitId, - Token: this.pushToken, + Token: this.pushToken || '', Platform: Platform.OS === 'ios' ? 1 : 2, DeviceUuid: getDeviceUuid() || '', Prefix: departmentCode, diff --git a/src/services/push-notification.web.ts b/src/services/push-notification.web.ts index 98ae6489..8dff3c81 100644 --- a/src/services/push-notification.web.ts +++ b/src/services/push-notification.web.ts @@ -222,10 +222,10 @@ class WebPushNotificationService { } const success = await this.pushSubscription.unsubscribe(); - + if (success) { this.pushSubscription = null; - + // Clear any potential persisted client-side records // Explicitly clearing any typical local storage keys as a safety measure try { diff --git a/src/types/declarations.d.ts b/src/types/declarations.d.ts new file mode 100644 index 00000000..d8824724 --- /dev/null +++ b/src/types/declarations.d.ts @@ -0,0 +1,7 @@ +declare module 'countly-sdk-react-native-bridge'; +declare module 'countly-sdk-react-native-bridge/CountlyConfig'; +declare module 'react-native-callkeep'; +declare module '@react-native-firebase/messaging'; +declare module 'mapbox-gl'; +declare module 'react-dom'; +declare module '*.css';