mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-08 00:26:37 +08:00
merge: remove legacy openflare update API
This commit is contained in:
@@ -3,36 +3,13 @@
|
||||
import {useMutation, useQuery} from '@tanstack/react-query';
|
||||
import {useCallback, useEffect, useRef, useState} from 'react';
|
||||
|
||||
import type {
|
||||
LatestReleaseInfo,
|
||||
ReleaseChannel,
|
||||
UpgradeStreamSnapshot,
|
||||
UploadedServerBinaryInfo,
|
||||
} from '@/lib/services/openflare';
|
||||
import {StatusService, UpdateService} from '@/lib/services/openflare';
|
||||
|
||||
const UPGRADE_STATUS_POLL_INTERVAL = 3000;
|
||||
import {AdminStatusService} from '@/lib/services/admin';
|
||||
import type {AppUpdateStatus} from '@/lib/services/admin/types';
|
||||
import {StatusService} from '@/lib/services/openflare';
|
||||
|
||||
export const openflarePublicStatusQueryKey = ['openflare', 'public-status'] as const;
|
||||
|
||||
export const openflareLatestReleaseQueryKey = (channel: ReleaseChannel = 'stable') =>
|
||||
['openflare', 'latest-release', channel] as const;
|
||||
|
||||
export function mergeReleaseWithUpgradeStream(
|
||||
release: LatestReleaseInfo | null | undefined,
|
||||
stream: UpgradeStreamSnapshot | null,
|
||||
) {
|
||||
if (!release || !stream) {
|
||||
return release;
|
||||
}
|
||||
|
||||
return {
|
||||
...release,
|
||||
in_progress: stream.in_progress,
|
||||
upgrade_status: stream.upgrade_status,
|
||||
upgrade_logs: stream.upgrade_logs,
|
||||
};
|
||||
}
|
||||
export const adminUpdateStatusQueryKey = ['admin', 'update'] as const;
|
||||
|
||||
export function useOpenFlareServerUpgrade({
|
||||
open,
|
||||
@@ -41,15 +18,8 @@ export function useOpenFlareServerUpgrade({
|
||||
open: boolean;
|
||||
canUpgrade: boolean;
|
||||
}) {
|
||||
const [selectedChannel, setSelectedChannel] = useState<ReleaseChannel>('stable');
|
||||
const [feedback, setFeedback] = useState<string | null>(null);
|
||||
const [manualStatusMessage, setManualStatusMessage] = useState<string | null>(null);
|
||||
const [manualErrorMessage, setManualErrorMessage] = useState<string | null>(null);
|
||||
const [uploadedBinary, setUploadedBinary] = useState<UploadedServerBinaryInfo | null>(null);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [upgradeStream, setUpgradeStream] = useState<UpgradeStreamSnapshot | null>(null);
|
||||
|
||||
const upgradeRefreshPendingRef = useRef(false);
|
||||
const upgradeReloadStartedRef = useRef(false);
|
||||
const upgradeReloadTimerRef = useRef<number | null>(null);
|
||||
|
||||
@@ -59,30 +29,11 @@ export function useOpenFlareServerUpgrade({
|
||||
enabled: open,
|
||||
});
|
||||
|
||||
const stableReleaseQuery = useQuery({
|
||||
queryKey: openflareLatestReleaseQueryKey('stable'),
|
||||
queryFn: () => UpdateService.getLatestRelease('stable'),
|
||||
const updateQuery = useQuery({
|
||||
queryKey: adminUpdateStatusQueryKey,
|
||||
queryFn: () => AdminStatusService.getUpdateStatus(),
|
||||
enabled: open && canUpgrade,
|
||||
refetchInterval: (query) => {
|
||||
const release = query.state.data;
|
||||
if (open && release?.in_progress) {
|
||||
return UPGRADE_STATUS_POLL_INTERVAL;
|
||||
}
|
||||
return 60 * 60 * 1000;
|
||||
},
|
||||
});
|
||||
|
||||
const previewReleaseQuery = useQuery({
|
||||
queryKey: openflareLatestReleaseQueryKey('preview'),
|
||||
queryFn: () => UpdateService.getLatestRelease('preview'),
|
||||
enabled: false,
|
||||
refetchInterval: (query) => {
|
||||
const release = query.state.data;
|
||||
if (open && release?.in_progress) {
|
||||
return UPGRADE_STATUS_POLL_INTERVAL;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
const scheduleUpgradePageReload = useCallback(() => {
|
||||
@@ -113,236 +64,64 @@ export function useOpenFlareServerUpgrade({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !canUpgrade) {
|
||||
setUpgradeStream(null);
|
||||
return;
|
||||
}
|
||||
|
||||
let closed = false;
|
||||
let reconnectTimer: number | null = null;
|
||||
let socket: WebSocket | null = null;
|
||||
|
||||
const connect = () => {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket = UpdateService.createUpgradeLogsWebSocket();
|
||||
if (!socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
const snapshot = UpdateService.parseUpgradeStreamSnapshot(String(event.data));
|
||||
if (snapshot) {
|
||||
if (snapshot.in_progress || snapshot.upgrade_status === 'succeeded') {
|
||||
upgradeRefreshPendingRef.current = true;
|
||||
}
|
||||
if (snapshot.upgrade_status === 'failed') {
|
||||
upgradeRefreshPendingRef.current = false;
|
||||
}
|
||||
setUpgradeStream(snapshot);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
if (upgradeRefreshPendingRef.current) {
|
||||
scheduleUpgradePageReload();
|
||||
return;
|
||||
}
|
||||
reconnectTimer = window.setTimeout(connect, 1500);
|
||||
};
|
||||
};
|
||||
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
closed = true;
|
||||
if (reconnectTimer !== null) {
|
||||
window.clearTimeout(reconnectTimer);
|
||||
}
|
||||
socket?.close();
|
||||
};
|
||||
}, [canUpgrade, open, scheduleUpgradePageReload]);
|
||||
|
||||
const upgradeMutation = useMutation({
|
||||
mutationFn: (channel: ReleaseChannel) => UpdateService.upgradeServer(channel),
|
||||
onSuccess: (release) => {
|
||||
upgradeRefreshPendingRef.current = true;
|
||||
setUploadedBinary(null);
|
||||
setManualStatusMessage(null);
|
||||
setManualErrorMessage(null);
|
||||
setFeedback(
|
||||
`服务升级任务已启动,目标版本 ${release.tag_name}(${release.channel === 'preview' ? '预览版' : '正式版'})。页面可能短暂不可用。`,
|
||||
);
|
||||
void stableReleaseQuery.refetch();
|
||||
if (release.channel === 'preview') {
|
||||
void previewReleaseQuery.refetch();
|
||||
}
|
||||
mutationFn: () => AdminStatusService.applyUpdate(),
|
||||
onSuccess: () => {
|
||||
scheduleUpgradePageReload();
|
||||
setFeedback('升级包已校验完成,服务正在重启。');
|
||||
},
|
||||
onError: (error) => {
|
||||
setFeedback(error instanceof Error ? error.message : '升级失败,请稍后重试。');
|
||||
},
|
||||
});
|
||||
|
||||
const uploadBinaryMutation = useMutation({
|
||||
mutationFn: (binary: File) =>
|
||||
UpdateService.uploadServerBinary(binary, (progress) => {
|
||||
setUploadProgress(progress);
|
||||
}),
|
||||
onSuccess: (candidate) => {
|
||||
setUploadProgress(0);
|
||||
setFeedback(null);
|
||||
setManualErrorMessage(null);
|
||||
setUploadedBinary(candidate);
|
||||
setManualStatusMessage(candidate.comparison_message);
|
||||
},
|
||||
onError: (error) => {
|
||||
setUploadProgress(0);
|
||||
setUploadedBinary(null);
|
||||
setManualStatusMessage(null);
|
||||
setManualErrorMessage(
|
||||
error instanceof Error ? error.message : '上传升级包失败,请稍后重试。',
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const confirmManualUpgradeMutation = useMutation({
|
||||
mutationFn: (uploadToken: string) => UpdateService.confirmManualServerUpgrade(uploadToken),
|
||||
onSuccess: (candidate) => {
|
||||
upgradeRefreshPendingRef.current = true;
|
||||
setFeedback(null);
|
||||
setManualErrorMessage(null);
|
||||
setUploadedBinary(candidate);
|
||||
setManualStatusMessage(
|
||||
`手动升级任务已启动,目标版本 ${candidate.detected_version}。页面可能短暂不可用。`,
|
||||
);
|
||||
void stableReleaseQuery.refetch();
|
||||
void previewReleaseQuery.refetch();
|
||||
},
|
||||
onError: (error) => {
|
||||
setManualStatusMessage(null);
|
||||
setManualErrorMessage(
|
||||
error instanceof Error ? error.message : '确认手动升级失败,请稍后重试。',
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const resetTransientState = useCallback(() => {
|
||||
setSelectedChannel('stable');
|
||||
setFeedback(null);
|
||||
setManualStatusMessage(null);
|
||||
setManualErrorMessage(null);
|
||||
setUploadedBinary(null);
|
||||
setUploadProgress(0);
|
||||
setUpgradeStream(null);
|
||||
upgradeRefreshPendingRef.current = false;
|
||||
upgradeReloadStartedRef.current = false;
|
||||
}, []);
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
resetTransientState();
|
||||
if (canUpgrade) {
|
||||
void stableReleaseQuery.refetch();
|
||||
void updateQuery.refetch();
|
||||
}
|
||||
}, [canUpgrade, resetTransientState, stableReleaseQuery]);
|
||||
}, [canUpgrade, resetTransientState, updateQuery]);
|
||||
|
||||
const handleCheckRelease = useCallback(() => {
|
||||
setFeedback(null);
|
||||
if (!canUpgrade) {
|
||||
return;
|
||||
}
|
||||
if (selectedChannel === 'preview') {
|
||||
void previewReleaseQuery.refetch();
|
||||
return;
|
||||
}
|
||||
void stableReleaseQuery.refetch();
|
||||
}, [canUpgrade, previewReleaseQuery, selectedChannel, stableReleaseQuery]);
|
||||
|
||||
const handleChannelChange = useCallback((channel: ReleaseChannel) => {
|
||||
setSelectedChannel(channel);
|
||||
setFeedback(null);
|
||||
}, []);
|
||||
void updateQuery.refetch();
|
||||
}, [canUpgrade, updateQuery]);
|
||||
|
||||
const handleUpgrade = useCallback(() => {
|
||||
setFeedback(null);
|
||||
setManualStatusMessage(null);
|
||||
setManualErrorMessage(null);
|
||||
upgradeMutation.mutate(selectedChannel);
|
||||
}, [selectedChannel, upgradeMutation]);
|
||||
|
||||
const handleUploadBinary = useCallback(
|
||||
(binary: File) => {
|
||||
setUploadProgress(0);
|
||||
setManualStatusMessage(null);
|
||||
setManualErrorMessage(null);
|
||||
uploadBinaryMutation.mutate(binary);
|
||||
},
|
||||
[uploadBinaryMutation],
|
||||
);
|
||||
|
||||
const handleConfirmManualUpgrade = useCallback(() => {
|
||||
if (!uploadedBinary?.upload_token) {
|
||||
setManualStatusMessage(null);
|
||||
setManualErrorMessage('请先上传并检查升级包。');
|
||||
return;
|
||||
}
|
||||
setFeedback(null);
|
||||
setManualStatusMessage(null);
|
||||
setManualErrorMessage(null);
|
||||
confirmManualUpgradeMutation.mutate(uploadedBinary.upload_token);
|
||||
}, [confirmManualUpgradeMutation, uploadedBinary?.upload_token]);
|
||||
|
||||
const selectedRelease =
|
||||
selectedChannel === 'preview' ? previewReleaseQuery.data : stableReleaseQuery.data;
|
||||
const releaseWithStream = mergeReleaseWithUpgradeStream(selectedRelease, upgradeStream);
|
||||
const selectedReleaseError =
|
||||
selectedChannel === 'preview' ? previewReleaseQuery.error : stableReleaseQuery.error;
|
||||
const isSelectedReleaseError =
|
||||
selectedChannel === 'preview' ? previewReleaseQuery.isError : stableReleaseQuery.isError;
|
||||
const isChecking =
|
||||
selectedChannel === 'preview'
|
||||
? previewReleaseQuery.isFetching
|
||||
: stableReleaseQuery.isFetching;
|
||||
const isInitialLoading =
|
||||
(selectedChannel === 'preview'
|
||||
? previewReleaseQuery.isLoading && !previewReleaseQuery.data
|
||||
: stableReleaseQuery.isLoading && !stableReleaseQuery.data) && canUpgrade;
|
||||
upgradeMutation.mutate();
|
||||
}, [upgradeMutation]);
|
||||
|
||||
const update = updateQuery.data;
|
||||
const releaseErrorMessage =
|
||||
feedback ||
|
||||
(isSelectedReleaseError
|
||||
? selectedReleaseError instanceof Error
|
||||
? selectedReleaseError.message
|
||||
(updateQuery.isError
|
||||
? updateQuery.error instanceof Error
|
||||
? updateQuery.error.message
|
||||
: '版本检查失败,请稍后重试。'
|
||||
: undefined);
|
||||
|
||||
const currentVersion = statusQuery.data?.version || releaseWithStream?.current_version || 'unknown';
|
||||
const currentVersion = statusQuery.data?.version || update?.current_version || 'unknown';
|
||||
|
||||
return {
|
||||
currentVersion,
|
||||
selectedChannel,
|
||||
release: releaseWithStream,
|
||||
uploadedBinary,
|
||||
update,
|
||||
releaseErrorMessage,
|
||||
manualStatusMessage,
|
||||
manualErrorMessage,
|
||||
isInitialLoading,
|
||||
isChecking,
|
||||
isInitialLoading: updateQuery.isLoading && !updateQuery.data && canUpgrade,
|
||||
isChecking: updateQuery.isFetching,
|
||||
isUpgrading: upgradeMutation.isPending,
|
||||
isUploadingBinary: uploadBinaryMutation.isPending,
|
||||
uploadProgress,
|
||||
isConfirmingManualUpgrade: confirmManualUpgradeMutation.isPending,
|
||||
stableRelease: stableReleaseQuery.data,
|
||||
handleOpen,
|
||||
handleCheckRelease,
|
||||
handleChannelChange,
|
||||
handleUpgrade,
|
||||
handleUploadBinary,
|
||||
handleConfirmManualUpgrade,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type {AppUpdateStatus};
|
||||
@@ -40,7 +40,6 @@ import {
|
||||
ProxyRouteService,
|
||||
StatusService,
|
||||
TlsCertificateService,
|
||||
UpdateService,
|
||||
UptimeKumaService,
|
||||
WafService,
|
||||
WebsiteService,
|
||||
@@ -77,7 +76,6 @@ const services = {
|
||||
openflareAccessLog: AccessLogService,
|
||||
openflareOption: OptionService,
|
||||
openflareUptimeKuma: UptimeKumaService,
|
||||
openflareUpdate: UpdateService,
|
||||
openflareStatus: StatusService,
|
||||
} as const;
|
||||
|
||||
@@ -199,7 +197,6 @@ export {
|
||||
AccessLogService,
|
||||
OptionService,
|
||||
StatusService,
|
||||
UpdateService,
|
||||
UptimeKumaService,
|
||||
} from './openflare';
|
||||
|
||||
@@ -227,6 +224,5 @@ export type {
|
||||
OptionItem,
|
||||
GeoIPLookupResult,
|
||||
DatabaseCleanupResult,
|
||||
LatestReleaseInfo,
|
||||
OpenFlarePublicStatus,
|
||||
} from './openflare';
|
||||
|
||||
@@ -15,7 +15,6 @@ export { OriginService } from './origin.service';
|
||||
export { AccessLogService } from './access-log.service';
|
||||
export { OptionService } from './option.service';
|
||||
export { UptimeKumaService } from './uptimekuma.service';
|
||||
export { UpdateService } from './update.service';
|
||||
export { StatusService } from './status.service';
|
||||
|
||||
export type {
|
||||
@@ -93,11 +92,7 @@ export type {
|
||||
DatabaseCleanupPayload,
|
||||
DatabaseCleanupResult,
|
||||
DatabaseCleanupTarget,
|
||||
LatestReleaseInfo,
|
||||
OpenFlarePublicStatus,
|
||||
UpgradeLogItem,
|
||||
UpgradeStreamSnapshot,
|
||||
UploadedServerBinaryInfo,
|
||||
OriginDetail,
|
||||
OriginItem,
|
||||
OriginMutationPayload,
|
||||
@@ -128,7 +123,6 @@ import {DnsAccountService} from './dns-account.service';
|
||||
import {NodeService} from './node.service';
|
||||
import {OptionService} from './option.service';
|
||||
import {StatusService} from './status.service';
|
||||
import {UpdateService} from './update.service';
|
||||
import {UptimeKumaService} from './uptimekuma.service';
|
||||
import {OriginService} from './origin.service';
|
||||
import {PagesService} from './pages.service';
|
||||
@@ -153,6 +147,5 @@ export const openflareServices = {
|
||||
accessLog: AccessLogService,
|
||||
option: OptionService,
|
||||
uptimeKuma: UptimeKumaService,
|
||||
update: UpdateService,
|
||||
status: StatusService,
|
||||
} as const;
|
||||
|
||||
@@ -696,45 +696,6 @@ export interface OpenFlarePublicStatus {
|
||||
system_name: string;
|
||||
}
|
||||
|
||||
export interface UpgradeLogItem {
|
||||
level: string;
|
||||
message: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface LatestReleaseInfo {
|
||||
tag_name: string;
|
||||
body: string;
|
||||
html_url: string;
|
||||
published_at: string;
|
||||
channel: ReleaseChannel;
|
||||
prerelease: boolean;
|
||||
current_version: string;
|
||||
has_update: boolean;
|
||||
upgrade_supported: boolean;
|
||||
in_progress: boolean;
|
||||
upgrade_status: 'idle' | 'running' | 'succeeded' | 'failed' | string;
|
||||
upgrade_logs: UpgradeLogItem[];
|
||||
}
|
||||
|
||||
export interface UpgradeStreamSnapshot {
|
||||
in_progress: boolean;
|
||||
upgrade_status: 'idle' | 'running' | 'succeeded' | 'failed' | string;
|
||||
upgrade_logs: UpgradeLogItem[];
|
||||
}
|
||||
|
||||
export interface UploadedServerBinaryInfo {
|
||||
upload_token: string;
|
||||
file_name: string;
|
||||
detected_version: string;
|
||||
current_version: string;
|
||||
has_update: boolean;
|
||||
upgrade_supported: boolean;
|
||||
ready_to_upgrade: boolean;
|
||||
comparison_message: string;
|
||||
uploaded_at: string;
|
||||
}
|
||||
|
||||
export interface WAFRuleGroup {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
import type {InternalAxiosRequestConfig} from 'axios';
|
||||
|
||||
import {getApiBaseUrl} from '@/lib/services/core/config';
|
||||
import {ApiErrorBase} from '@/lib/services/core/errors';
|
||||
import type {ApiResponse} from '@/lib/services/core';
|
||||
|
||||
import {OpenFlareBaseService} from './base.service';
|
||||
import type {LatestReleaseInfo, ReleaseChannel, UpgradeStreamSnapshot, UploadedServerBinaryInfo} from './types';
|
||||
|
||||
/**
|
||||
* OpenFlare 服务端升级 API(`/api/v1/d/update/*`)。
|
||||
*/
|
||||
export class UpdateService extends OpenFlareBaseService {
|
||||
protected static override readonly basePath: string = '/api/v1/d/update';
|
||||
|
||||
private static getApiUrl(path: string): string {
|
||||
const baseURL = getApiBaseUrl();
|
||||
const fullPath = this.getFullPath(path);
|
||||
if (!baseURL) {
|
||||
return fullPath;
|
||||
}
|
||||
return `${baseURL.replace(/\/$/, '')}${fullPath}`;
|
||||
}
|
||||
|
||||
static getLatestRelease(channel: ReleaseChannel = 'stable'): Promise<LatestReleaseInfo> {
|
||||
return this.get<LatestReleaseInfo>('/latest-release', { channel });
|
||||
}
|
||||
|
||||
static upgradeServer(channel: ReleaseChannel = 'stable'): Promise<LatestReleaseInfo> {
|
||||
return this.post<LatestReleaseInfo>('/upgrade', { channel });
|
||||
}
|
||||
|
||||
static uploadServerBinary(
|
||||
binary: File,
|
||||
onProgress?: (progress: number) => void,
|
||||
): Promise<UploadedServerBinaryInfo> {
|
||||
const formData = new FormData();
|
||||
formData.append('binary', binary);
|
||||
|
||||
if (!onProgress) {
|
||||
return this.post<UploadedServerBinaryInfo>('/manual-upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
} as InternalAxiosRequestConfig);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', this.getApiUrl('/manual-upload'));
|
||||
xhr.withCredentials = true;
|
||||
|
||||
xhr.upload.addEventListener('progress', (event) => {
|
||||
if (event.lengthComputable) {
|
||||
onProgress(Math.round((event.loaded / event.total) * 100));
|
||||
}
|
||||
});
|
||||
|
||||
xhr.addEventListener('load', () => {
|
||||
let payload: ApiResponse<UploadedServerBinaryInfo> | null = null;
|
||||
try {
|
||||
payload = JSON.parse(
|
||||
xhr.responseText,
|
||||
) as ApiResponse<UploadedServerBinaryInfo>;
|
||||
} catch {
|
||||
payload = null;
|
||||
}
|
||||
if (xhr.status < 200 || xhr.status >= 300) {
|
||||
reject(
|
||||
new ApiErrorBase(
|
||||
payload?.message || `请求失败(${xhr.status})`,
|
||||
undefined,
|
||||
xhr.status,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!payload) {
|
||||
reject(new ApiErrorBase('响应格式无效', undefined, xhr.status));
|
||||
return;
|
||||
}
|
||||
resolve(payload.data);
|
||||
});
|
||||
|
||||
xhr.addEventListener('error', () => {
|
||||
reject(new ApiErrorBase('上传过程中网络连接中断,请检查网络后重试', undefined, 0));
|
||||
});
|
||||
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
|
||||
static confirmManualServerUpgrade(uploadToken: string): Promise<UploadedServerBinaryInfo> {
|
||||
return this.post<UploadedServerBinaryInfo>('/manual-upgrade', {
|
||||
upload_token: uploadToken,
|
||||
});
|
||||
}
|
||||
|
||||
static createUpgradeLogsWebSocket(): WebSocket | null {
|
||||
if (typeof window === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const apiUrl = this.getApiUrl('/logs/ws');
|
||||
const resolvedUrl = apiUrl.startsWith('http://')
|
||||
? `ws://${apiUrl.slice('http://'.length)}`
|
||||
: apiUrl.startsWith('https://')
|
||||
? `wss://${apiUrl.slice('https://'.length)}`
|
||||
: `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}${apiUrl}`;
|
||||
|
||||
return new WebSocket(resolvedUrl);
|
||||
}
|
||||
|
||||
static parseUpgradeStreamSnapshot(rawMessage: string): UpgradeStreamSnapshot | null {
|
||||
try {
|
||||
const parsed = JSON.parse(rawMessage) as Partial<UpgradeStreamSnapshot>;
|
||||
if (
|
||||
typeof parsed.in_progress !== 'boolean' ||
|
||||
typeof parsed.upgrade_status !== 'string' ||
|
||||
!Array.isArray(parsed.upgrade_logs)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
in_progress: parsed.in_progress,
|
||||
upgrade_status: parsed.upgrade_status,
|
||||
upgrade_logs: parsed.upgrade_logs,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user