import { createContext, useEffect, useRef, useState } from "react"; import { PacketRequestUpdate } from "../ProtocolProvider/protocol/packets/packet.requestupdate"; import { useSender } from "../ProtocolProvider/useSender"; import { usePacket } from "../ProtocolProvider/usePacket"; import { useConsoleLogger } from "@/app/hooks/useConsoleLogger"; import { useFileStorage } from "@/app/hooks/useFileStorage"; import { APPLICATION_ARCH, APPLICATION_PLATFROM, CORE_VERSION } from "@/app/constants"; import { APP_VERSION } from "@/app/version"; export interface UpdateProviderProps { children: React.ReactNode; } export enum UpdateStatus { DOWNLOADING, COMPILE, READY_FOR_RESTART, NO_UPDATES, KERNEL_UPDATE_NEED, APP_UPDATE_AVAILABLE } /** * Ответ от сервера обновлений на запрос наличия обновлений. Содержит информацию о том, какие обновления есть, и ссылки на них */ export interface UpdateServerResponse { version: string; platform: string; arch: string; kernel_update_required: boolean; service_pack_url: string; kernel_url: string; } export interface UpdateContextValue { updateStatus: UpdateStatus; downloadProgress: number; kernelUpdateUrl: string; checkForUpdates: () => void; downloadLastApplicationUpdate: () => void; restartAppForUpdateApply: () => void; } export const UpdateProviderContext = createContext(null); /** * Провайдер для управления обновлениями приложения. При инициализации * запрашвиает сервер обновлений с основного сервера и получает актуальные версии с сервера обновлений */ export function UpdateProvider(props: UpdateProviderProps) { const send = useSender(); const {info, error} = useConsoleLogger('UpdateProvider'); const updateServerRef = useRef(null); const [updateStatus, setUpdateStatus] = useState(UpdateStatus.NO_UPDATES); const [downloadProgress, setDownloadProgress] = useState(0); const [kernelUpdateUrl, setKernelUpdateUrl] = useState(""); const [appUpdateUrl, setAppUpdateUrl] = useState(""); const [appActualVersion, setAppActualVersion] = useState(""); const {writeFile} = useFileStorage(); useEffect(() => { let packet = new PacketRequestUpdate(); /** * Клиент хочет получить сервер обновлений с основного сервера */ packet.setUpdateServer(""); send(packet); }, []); usePacket(0xA, (packet : PacketRequestUpdate) => { updateServerRef.current = packet.getUpdateServer(); info(`Update server ${updateServerRef.current}`); checkForUpdates(); }, []); const checkForUpdates = async () => { if(updateServerRef.current == null){ /** * SDU еще не определен */ return; } /** * Запрашиваем обновления с SDU сервера */ let response = await fetch (`${updateServerRef.current}/updates/get?app=${APP_VERSION}&kernel=${CORE_VERSION}&arch=${APPLICATION_ARCH}&platform=${APPLICATION_PLATFROM}`).catch((e) => { error("Failed to check for updates: " + e.message); }); if(!response || response.status != 200){ error("Failed to check for updates, SDU unavailable"); return; } let updateInfo : UpdateServerResponse = await response.json(); console.info("Update info: ", updateInfo); if(updateInfo.kernel_update_required){ /** * Чтобы дальше получать обновления приложения нужно сначала обновить ядро */ setUpdateStatus(UpdateStatus.KERNEL_UPDATE_NEED); setKernelUpdateUrl(updateInfo.kernel_url); info("Kernel update needed for next application updates"); return; } if(updateInfo.service_pack_url != null){ /** * Доступно обновление приложения, которое можно скачать и установить */ setUpdateStatus(UpdateStatus.APP_UPDATE_AVAILABLE); setAppUpdateUrl(updateInfo.service_pack_url); setAppActualVersion(updateInfo.version); info("Application update available"); return; } } const downloadLastApplicationUpdate = async () => { if(updateStatus != UpdateStatus.APP_UPDATE_AVAILABLE){ return; } setUpdateStatus(UpdateStatus.DOWNLOADING); const xhr = new XMLHttpRequest(); xhr.open("GET", updateServerRef.current + appUpdateUrl, true); xhr.responseType = "blob"; xhr.onprogress = (event) => { if (event.lengthComputable) { const percentComplete = (event.loaded / event.total) * 100; setDownloadProgress(Math.round(percentComplete)); } }; xhr.onload = async () => { if (xhr.status === 200) { setUpdateStatus(UpdateStatus.COMPILE); const blob : Blob = xhr.response; let bundleName = `bundle ${appActualVersion}.zip`; await writeFile(bundleName, Buffer.from(await blob.arrayBuffer())); info("Update downloaded, starting compiler..."); await window.electron.ipcRenderer.invoke('update:installServiceUpdate', bundleName); info("Update compiled successfully."); setTimeout(() => { setUpdateStatus(UpdateStatus.READY_FOR_RESTART); }, 10000); } } xhr.onerror = () => { error("Error downloading update"); setUpdateStatus(UpdateStatus.APP_UPDATE_AVAILABLE); } xhr.send(); } const restartAppForUpdateApply = () => { if(updateStatus != UpdateStatus.READY_FOR_RESTART){ return; } window.electron.ipcRenderer.invoke('update:restartApp'); } return ( {props.children} ); }