fix and update
This commit is contained in:
@@ -28,6 +28,7 @@ async function bootstrap() {
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('docs', app, document, {
|
||||
yamlDocumentUrl: '/openapi.yaml',
|
||||
swaggerOptions: { persistAuthorization: true },
|
||||
customSiteTitle: 'Документация API'
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ import { usePublicSettings } from './public-settings-provider';
|
||||
import { useToast } from './toast-provider';
|
||||
import { PinLockModal } from './pin-lock-modal';
|
||||
import { AppBootstrapScreen } from './app-bootstrap-screen';
|
||||
import { EMBEDDED_AUTH_MESSAGE, type EmbeddedAuthPayload } from '@/lib/embedded-auth-bridge';
|
||||
import { EMBEDDED_AUTH_MESSAGE, type EmbeddedAuthPayload, useEmbeddedAuthFallback } from '@/lib/embedded-auth-bridge';
|
||||
import { usePinIdleLock } from '@/hooks/use-pin-idle-lock';
|
||||
import { clearPinIdleWatch, isPinIdleWatchActive, markPinIdleWatch } from '@/lib/pin-idle-storage';
|
||||
|
||||
@@ -145,6 +145,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const refreshInFlightRef = React.useRef<Promise<void> | null>(null);
|
||||
const initialBootstrapDoneRef = React.useRef(false);
|
||||
|
||||
useEmbeddedAuthFallback();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isApiGatewayReady()) {
|
||||
setIsApiReady(true);
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
const API_ORIGIN_PROXY_PREFIX = '/idp-api';
|
||||
const configuredApiUrl = (process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3000').replace(/\/$/, '');
|
||||
const configuredWsUrl = (process.env.NEXT_PUBLIC_WS_URL ?? 'ws://localhost:8085/ws').replace(/\/$/, '');
|
||||
const MOBILE_API_URL_KEY = 'lendry_mobile_api_url';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__LENDRY_DIRECT_API_URL__?: string;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDirectApiOverride(): string | null {
|
||||
if (typeof window === 'undefined') return null;
|
||||
const fromWindow = window.__LENDRY_DIRECT_API_URL__?.trim();
|
||||
if (fromWindow) return fromWindow.replace(/\/$/, '');
|
||||
const fromStorage = window.localStorage.getItem(MOBILE_API_URL_KEY)?.trim();
|
||||
if (fromStorage) return fromStorage.replace(/\/$/, '');
|
||||
return null;
|
||||
}
|
||||
|
||||
function getServerApiUrl(): string {
|
||||
return (process.env.INTERNAL_API_URL ?? configuredApiUrl).replace(/\/$/, '');
|
||||
@@ -26,6 +42,9 @@ function isLocalDevApiUrl(url: string): boolean {
|
||||
}
|
||||
|
||||
function resolveBrowserApiBaseUrl(): string {
|
||||
const directApi = resolveDirectApiOverride();
|
||||
if (directApi) return directApi;
|
||||
|
||||
// SSR: внутренний URL Docker-сети (api-gateway), не публичный домен.
|
||||
if (typeof window === 'undefined') {
|
||||
return getServerApiUrl();
|
||||
@@ -261,7 +280,7 @@ export async function uploadMediaObject(
|
||||
const apiBase = getApiUrl().replace(/\/$/, '');
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, file instanceof File ? file.name : 'upload.bin');
|
||||
const response = await fetch(`${apiBase}/media/upload`, {
|
||||
const response = await platformFetch(`${apiBase}/media/upload`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-Upload-Token': presigned.uploadToken },
|
||||
body: formData
|
||||
@@ -272,7 +291,7 @@ export async function uploadMediaObject(
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(presigned.uploadUrl, {
|
||||
const response = await platformFetch(presigned.uploadUrl, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': contentType },
|
||||
body: file
|
||||
@@ -473,6 +492,17 @@ export function resetPinRequiredNotification() {
|
||||
pinNotificationSent = false;
|
||||
}
|
||||
|
||||
let customFetchImpl: typeof fetch | null = null;
|
||||
|
||||
/** Tauri/mobile: native HTTP (bypasses WebView CORS and self-signed TLS). */
|
||||
export function setCustomFetch(fetchImpl: typeof fetch | null) {
|
||||
customFetchImpl = fetchImpl;
|
||||
}
|
||||
|
||||
function platformFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||
return (customFetchImpl ?? fetch)(input, init);
|
||||
}
|
||||
|
||||
function notifyPinRequired(sessionId: string) {
|
||||
if (!pinRequiredHandler || pinNotificationSent) return;
|
||||
pinNotificationSent = true;
|
||||
@@ -909,7 +939,7 @@ function releaseApiSlot(): void {
|
||||
/** Liveness: nginx → api-gateway HTTP. */
|
||||
async function probeGatewayAlive(): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${getApiUrl()}/health`, {
|
||||
const response = await platformFetch(`${getApiUrl()}/health`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
signal: AbortSignal.timeout(4500)
|
||||
@@ -956,7 +986,7 @@ async function fetchWithGatewayRetry(
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
||||
try {
|
||||
assertGatewayCircuitClosed(behavior);
|
||||
const response = await fetch(url, init);
|
||||
const response = await platformFetch(url, init);
|
||||
if (GATEWAY_RETRY_STATUS.has(response.status) && attempt + 1 < maxAttempts) {
|
||||
await sleep(gatewayRetryDelay(attempt));
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user