Merge API remote base URL config into dev

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-06-24 18:19:02 +03:00
9 changed files with 26 additions and 18 deletions
+3 -3
View File
@@ -15,10 +15,10 @@
# is honored — AZ-498 / contract @
# _docs/02_document/contracts/satellite-provider/tiles.md)
# Prefix for every API request (production: empty; tests / alt deployments: set).
# Prefix for every API request (production: empty; remote API / tests: set).
# A trailing slash is stripped automatically.
# Example: VITE_API_BASE_URL=http://azaion-ui:80
VITE_API_BASE_URL=
# Example: VITE_API_BASE_URL=https://api.azaion.com
VITE_API_BASE_URL=https://api.azaion.com
# OpenWeatherMap API key. Required for the FlightsPage weather feature.
# Leave unset in CI tests — the e2e profile routes to owm-stub.
+9 -2
View File
@@ -38,6 +38,13 @@ export function getApiBase(): string {
return raw.replace(/\/+$/, '')
}
export function authenticatedApiUrl(path: string): string {
const url = getApiBase() + path
if (!accessToken) return url
const separator = url.includes('?') ? '&' : '?'
return `${url}${separator}access_token=${encodeURIComponent(accessToken)}`
}
/**
* Indirection for the failed-refresh redirect. Default impl writes
* `'/login'` to `window.location.href` — the production behavior. Tests
@@ -68,13 +75,13 @@ async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
if (options.body && typeof options.body === 'string') headers.set('Content-Type', 'application/json')
const fullUrl = getApiBase() + url
let res = await fetch(fullUrl, { ...options, headers })
let res = await fetch(fullUrl, { ...options, headers, credentials: 'include' })
if (res.status === 401 && accessToken) {
const refreshed = await refreshToken()
if (refreshed) {
headers.set('Authorization', `Bearer ${accessToken}`)
res = await fetch(fullUrl, { ...options, headers })
res = await fetch(fullUrl, { ...options, headers, credentials: 'include' })
} else {
setToken(null)
navigateToLoginImpl()
+1 -1
View File
@@ -1,3 +1,3 @@
export { api, setToken, getToken, getApiBase, setNavigateToLogin } from './client'
export { api, setToken, getToken, getApiBase, authenticatedApiUrl, setNavigateToLogin } from './client'
export { createSSE } from './sse'
export { endpoints } from './endpoints'
+2 -2
View File
@@ -5,10 +5,10 @@ export function createSSE<T>(
onMessage: (data: T) => void,
onError?: (err: Event) => void,
): () => void {
const token = getToken()
const prefixedUrl = getApiBase() + url
const token = getToken()
const fullUrl = token
? `${prefixedUrl}${prefixedUrl.includes('?') ? '&' : '?'}access_token=${token}`
? `${prefixedUrl}${prefixedUrl.includes('?') ? '&' : '?'}access_token=${encodeURIComponent(token)}`
: prefixedUrl
const source = new EventSource(fullUrl)
+2 -2
View File
@@ -1,6 +1,6 @@
import { useState, useCallback, useEffect, useMemo, useRef } from 'react'
import { useTranslation } from 'react-i18next'
import { api, endpoints } from '../../api'
import { api, endpoints, authenticatedApiUrl } from '../../api'
import MediaList from './MediaList'
import VideoPlayer, { type VideoPlayerHandle } from './VideoPlayer'
import CanvasEditor, { type CanvasEditorHandle } from './CanvasEditor'
@@ -195,7 +195,7 @@ export default function AnnotationsPage() {
img.crossOrigin = 'anonymous'
img.src = selectedMedia.path.startsWith('blob:')
? selectedMedia.path
: endpoints.annotations.mediaFile(selectedMedia.id)
: authenticatedApiUrl(endpoints.annotations.mediaFile(selectedMedia.id))
await new Promise(res => { img.onload = res; img.onerror = res })
w = img.naturalWidth
h = img.naturalHeight
+3 -3
View File
@@ -1,5 +1,5 @@
import { useRef, useEffect, useState, useCallback, forwardRef, useImperativeHandle } from 'react'
import { endpoints } from '../../api'
import { endpoints, authenticatedApiUrl } from '../../api'
import { MediaType } from '../../types'
import type { Media, AnnotationListItem, Detection, Affiliation, CombatReadiness } from '../../types'
import { getClassColor, getClassNameFallback, hexToRgba } from '../../class-colors'
@@ -112,11 +112,11 @@ const CanvasEditor = forwardRef<CanvasEditorHandle, Props>(function CanvasEditor
img.crossOrigin = 'anonymous'
const isLocalPath = media.path.startsWith('blob:') || media.path.startsWith('data:')
if (annotation && !isLocalPath) {
img.src = endpoints.annotations.annotationImage(annotation.id)
img.src = authenticatedApiUrl(endpoints.annotations.annotationImage(annotation.id))
} else if (isLocalPath) {
img.src = media.path
} else {
img.src = endpoints.annotations.mediaFile(media.id)
img.src = authenticatedApiUrl(endpoints.annotations.mediaFile(media.id))
}
img.onload = () => {
imgRef.current = img
+2 -2
View File
@@ -1,5 +1,5 @@
import { useRef, useState, useCallback, useEffect, forwardRef, useImperativeHandle } from 'react'
import { endpoints } from '../../api'
import { endpoints, authenticatedApiUrl } from '../../api'
import type { Media } from '../../types'
interface Props {
@@ -49,7 +49,7 @@ const VideoPlayer = forwardRef<VideoPlayerHandle, Props>(function VideoPlayer({
const videoUrl = media.path.startsWith('blob:')
? media.path
: endpoints.annotations.mediaFile(media.id)
: authenticatedApiUrl(endpoints.annotations.mediaFile(media.id))
const stepFrames = useCallback((count: number) => {
const video = videoRef.current
+2 -2
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { api, endpoints } from '../../api'
import { api, endpoints, authenticatedApiUrl } from '../../api'
import { useDebounce } from '../../hooks'
import { useFlight } from '../../components'
import { useSavedAnnotations } from '../../components/SavedAnnotationsContext'
@@ -97,7 +97,7 @@ export default function DatasetPage() {
imageName: item.imageName,
status: item.status,
createdDate: item.createdDate,
thumbnailUrl: endpoints.annotations.annotationThumbnail(item.annotationId),
thumbnailUrl: authenticatedApiUrl(endpoints.annotations.annotationThumbnail(item.annotationId)),
isSeed: item.isSeed,
isLocal: false,
}))
+2 -1
View File
@@ -13,8 +13,9 @@ export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
target: 'https://api.azaion.com',
changeOrigin: true,
secure: true,
},
},
},