mirror of
https://github.com/uqbar-dao/nectar.git
synced 2024-12-22 08:01:47 +03:00
Merge pull request #551 from kinode-dao/bp/appstoreuistore
app_store UI: don't persist local UI store
This commit is contained in:
commit
e0f7a716ef
@ -21,16 +21,24 @@ const MirrorSelector: React.FC<MirrorSelectorProps> = ({ packageId, onMirrorSele
|
||||
if (!appData) return;
|
||||
const mirrors = [appData.package_id.publisher_node, ...(appData.metadata?.properties?.mirrors || [])];
|
||||
setAvailableMirrors(mirrors);
|
||||
setSelectedMirror(appData.package_id.publisher_node);
|
||||
|
||||
mirrors.forEach(mirror => {
|
||||
// Start with the publisher node
|
||||
setSelectedMirror(appData.package_id.publisher_node);
|
||||
setMirrorStatuses(prev => ({ ...prev, [appData.package_id.publisher_node]: null }));
|
||||
|
||||
for (const mirror of mirrors) {
|
||||
if (mirror.startsWith('http')) {
|
||||
setMirrorStatuses(prev => ({ ...prev, [mirror]: 'http' }));
|
||||
setSelectedMirror(mirror);
|
||||
break;
|
||||
} else {
|
||||
setMirrorStatuses(prev => ({ ...prev, [mirror]: null }));
|
||||
checkMirrorStatus(mirror);
|
||||
const status = await checkMirrorStatus(mirror);
|
||||
if (status) {
|
||||
setSelectedMirror(mirror);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [packageId, fetchListing, checkMirror]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -41,8 +49,10 @@ const MirrorSelector: React.FC<MirrorSelectorProps> = ({ packageId, onMirrorSele
|
||||
try {
|
||||
const status = await checkMirror(mirror);
|
||||
setMirrorStatuses(prev => ({ ...prev, [mirror]: status?.is_online ?? false }));
|
||||
return status?.is_online ?? false;
|
||||
} catch {
|
||||
setMirrorStatuses(prev => ({ ...prev, [mirror]: false }));
|
||||
return false;
|
||||
}
|
||||
}, [checkMirror]);
|
||||
|
||||
@ -62,7 +72,6 @@ const MirrorSelector: React.FC<MirrorSelectorProps> = ({ packageId, onMirrorSele
|
||||
setIsCustomMirrorSelected(false);
|
||||
setCustomMirror("");
|
||||
if (!value.startsWith('http')) {
|
||||
// Recheck the status when a non-HTTP mirror is selected
|
||||
setMirrorStatuses(prev => ({ ...prev, [value]: null }));
|
||||
await checkMirrorStatus(value);
|
||||
}
|
||||
@ -92,7 +101,6 @@ const MirrorSelector: React.FC<MirrorSelectorProps> = ({ packageId, onMirrorSele
|
||||
return (
|
||||
<div className="mirror-selector">
|
||||
<select value={selectedMirror || ""} onChange={handleMirrorChange}>
|
||||
<option value="">Select a mirror</option>
|
||||
{availableMirrors.map((mirror, index) => (
|
||||
<option key={`${mirror}-${index}`} value={mirror}>
|
||||
{mirror} {getMirrorStatus(mirror, mirrorStatuses[mirror])}
|
||||
|
@ -45,361 +45,361 @@ interface AppsStore {
|
||||
|
||||
}
|
||||
|
||||
const useAppsStore = create<AppsStore>()(
|
||||
persist(
|
||||
(set, get): AppsStore => ({
|
||||
listings: {},
|
||||
installed: {},
|
||||
downloads: {},
|
||||
ourApps: [],
|
||||
activeDownloads: {},
|
||||
homepageApps: [],
|
||||
const useAppsStore = create<AppsStore>()((set, get) => ({
|
||||
listings: {},
|
||||
installed: {},
|
||||
downloads: {},
|
||||
ourApps: [],
|
||||
activeDownloads: {},
|
||||
homepageApps: [],
|
||||
|
||||
|
||||
fetchData: async (id: string) => {
|
||||
if (!id) return;
|
||||
try {
|
||||
const [listing, downloads, installedApp] = await Promise.all([
|
||||
get().fetchListing(id),
|
||||
get().fetchDownloadsForApp(id),
|
||||
get().fetchInstalledApp(id)
|
||||
]);
|
||||
set((state) => ({
|
||||
listings: listing ? { ...state.listings, [id]: listing } : state.listings,
|
||||
downloads: { ...state.downloads, [id]: downloads },
|
||||
installed: installedApp ? { ...state.installed, [id]: installedApp } : state.installed
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error fetching app data:", error);
|
||||
}
|
||||
},
|
||||
|
||||
fetchListings: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data: AppListing[] = await res.json();
|
||||
const listingsMap = data.reduce((acc, listing) => {
|
||||
acc[`${listing.package_id.package_name}:${listing.package_id.publisher_node}`] = listing;
|
||||
return acc;
|
||||
}, {} as Record<string, AppListing>);
|
||||
set({ listings: listingsMap });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching listings:", error);
|
||||
}
|
||||
},
|
||||
|
||||
fetchListing: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const listing: AppListing = await res.json();
|
||||
set((state) => ({
|
||||
listings: { ...state.listings, [id]: listing }
|
||||
}));
|
||||
return listing;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching listing:", error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
fetchInstalled: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/installed`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data: PackageState[] = await res.json();
|
||||
const installedMap = data.reduce((acc, pkg) => {
|
||||
acc[`${pkg.package_id.package_name}:${pkg.package_id.publisher_node}`] = pkg;
|
||||
return acc;
|
||||
}, {} as Record<string, PackageState>);
|
||||
set({ installed: installedMap });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching installed apps:", error);
|
||||
}
|
||||
},
|
||||
|
||||
fetchInstalledApp: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/installed/${id}`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const installedApp: PackageState = await res.json();
|
||||
set((state) => ({
|
||||
installed: { ...state.installed, [id]: installedApp }
|
||||
}));
|
||||
return installedApp;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching installed app:", error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
fetchDownloads: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const downloads: DownloadItem[] = await res.json();
|
||||
set({ downloads: { root: downloads } });
|
||||
return downloads;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching downloads:", error);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
fetchOurApps: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/ourapps`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data: AppListing[] = await res.json();
|
||||
set({ ourApps: data });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching our apps:", error);
|
||||
}
|
||||
},
|
||||
|
||||
fetchDownloadsForApp: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads/${id}`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const downloads: DownloadItem[] = await res.json();
|
||||
set((state) => ({
|
||||
downloads: { ...state.downloads, [id]: downloads }
|
||||
}));
|
||||
return downloads;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching downloads for app:", error);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
fetchHomepageApps: async () => {
|
||||
try {
|
||||
const res = await fetch('/apps');
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data: HomepageApp[] = await res.json();
|
||||
set({ homepageApps: data });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching homepage apps:", error);
|
||||
}
|
||||
},
|
||||
|
||||
getLaunchUrl: (id: string) => {
|
||||
const app = get().homepageApps.find(app => `${app.package}:${app.publisher}` === id);
|
||||
if (app && app.path) {
|
||||
return app.path;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
checkMirror: async (node: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/mirrorcheck/${node}`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
return await res.json() as MirrorCheckFile;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking mirror:", error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
installApp: async (id: string, version_hash: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/install`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ version_hash })
|
||||
});
|
||||
if (res.status === HTTP_STATUS.CREATED) {
|
||||
await get().fetchInstalled();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error installing app:", error);
|
||||
}
|
||||
},
|
||||
|
||||
uninstallApp: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}`, { method: 'DELETE' });
|
||||
if (res.status === HTTP_STATUS.NO_CONTENT) {
|
||||
await get().fetchInstalled();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error uninstalling app:", error);
|
||||
}
|
||||
},
|
||||
|
||||
downloadApp: async (id: string, version_hash: string, downloadFrom: string) => {
|
||||
const [package_name, publisher_node] = id.split(':');
|
||||
const appId = `${id}:${version_hash}`;
|
||||
set((state) => ({
|
||||
activeDownloads: {
|
||||
...state.activeDownloads,
|
||||
[appId]: { downloaded: 0, total: 100 }
|
||||
}
|
||||
}));
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/download`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
package_id: { package_name, publisher_node },
|
||||
version_hash,
|
||||
download_from: downloadFrom,
|
||||
}),
|
||||
});
|
||||
if (res.status !== HTTP_STATUS.OK) {
|
||||
get().clearActiveDownload(appId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error downloading app:", error);
|
||||
get().clearActiveDownload(appId);
|
||||
}
|
||||
},
|
||||
|
||||
clearAllActiveDownloads: () => set({ activeDownloads: {} }),
|
||||
|
||||
removeDownload: async (packageId: string, versionHash: string) => {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/downloads/${packageId}/remove`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ version_hash: versionHash }),
|
||||
});
|
||||
if (response.ok) {
|
||||
await get().fetchDownloadsForApp(packageId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to remove download:', error);
|
||||
}
|
||||
},
|
||||
|
||||
getCaps: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/caps`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
return await res.json() as PackageManifest;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error getting caps:", error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
approveCaps: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/caps`, { method: 'POST' });
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
await get().fetchListing(id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error approving caps:", error);
|
||||
}
|
||||
},
|
||||
|
||||
startMirroring: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads/${id}/mirror`, {
|
||||
method: 'PUT'
|
||||
});
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
await get().fetchDownloadsForApp(id.split(':').slice(0, -1).join(':'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error starting mirroring:", error);
|
||||
}
|
||||
},
|
||||
|
||||
stopMirroring: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads/${id}/mirror`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
await get().fetchDownloadsForApp(id.split(':').slice(0, -1).join(':'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error stopping mirroring:", error);
|
||||
}
|
||||
},
|
||||
|
||||
setAutoUpdate: async (id: string, version_hash: string, autoUpdate: boolean) => {
|
||||
try {
|
||||
const method = autoUpdate ? 'PUT' : 'DELETE';
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/auto-update`, {
|
||||
method,
|
||||
body: JSON.stringify({ version_hash })
|
||||
});
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
await get().fetchListing(id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error setting auto-update:", error);
|
||||
}
|
||||
},
|
||||
|
||||
setActiveDownload: (appId, downloaded, total) => {
|
||||
set((state) => ({
|
||||
activeDownloads: {
|
||||
...state.activeDownloads,
|
||||
[appId]: { downloaded, total }
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
clearActiveDownload: (appId) => {
|
||||
set((state) => {
|
||||
const { [appId]: _, ...rest } = state.activeDownloads;
|
||||
return { activeDownloads: rest };
|
||||
});
|
||||
},
|
||||
|
||||
ws: new KinodeClientApi({
|
||||
uri: WEBSOCKET_URL,
|
||||
nodeId: (window as any).our?.node,
|
||||
processId: "main:app_store:sys",
|
||||
onMessage: (message) => {
|
||||
console.log('WebSocket message received', message);
|
||||
try {
|
||||
const data = JSON.parse(message);
|
||||
if (data.kind === 'progress') {
|
||||
const { package_id, version_hash, downloaded, total } = data.data;
|
||||
const appId = `${package_id.package_name}:${package_id.publisher_node}:${version_hash}`;
|
||||
get().setActiveDownload(appId, downloaded, total);
|
||||
} else if (data.kind === 'complete') {
|
||||
const { package_id, version_hash } = data.data;
|
||||
const appId = `${package_id.package_name}:${package_id.publisher_node}:${version_hash}`;
|
||||
get().clearActiveDownload(appId);
|
||||
get().fetchData(`${package_id.package_name}:${package_id.publisher_node}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
}
|
||||
},
|
||||
onOpen: (_e) => {
|
||||
console.log('WebSocket connection opened');
|
||||
},
|
||||
onClose: (_e) => {
|
||||
console.log('WebSocket connection closed');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
},
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: 'app_store',
|
||||
fetchData: async (id: string) => {
|
||||
if (!id) return;
|
||||
try {
|
||||
const [listing, downloads, installedApp] = await Promise.all([
|
||||
get().fetchListing(id),
|
||||
get().fetchDownloadsForApp(id),
|
||||
get().fetchInstalledApp(id)
|
||||
]);
|
||||
set((state) => ({
|
||||
listings: listing ? { ...state.listings, [id]: listing } : state.listings,
|
||||
downloads: { ...state.downloads, [id]: downloads },
|
||||
installed: installedApp ? { ...state.installed, [id]: installedApp } : state.installed
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error fetching app data:", error);
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
|
||||
fetchListings: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data: AppListing[] = await res.json();
|
||||
const listingsMap = data.reduce((acc, listing) => {
|
||||
acc[`${listing.package_id.package_name}:${listing.package_id.publisher_node}`] = listing;
|
||||
return acc;
|
||||
}, {} as Record<string, AppListing>);
|
||||
set({ listings: listingsMap });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching listings:", error);
|
||||
}
|
||||
},
|
||||
|
||||
fetchListing: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const listing: AppListing = await res.json();
|
||||
set((state) => ({
|
||||
listings: { ...state.listings, [id]: listing }
|
||||
}));
|
||||
return listing;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching listing:", error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
fetchInstalled: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/installed`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data: PackageState[] = await res.json();
|
||||
const installedMap = data.reduce((acc, pkg) => {
|
||||
acc[`${pkg.package_id.package_name}:${pkg.package_id.publisher_node}`] = pkg;
|
||||
return acc;
|
||||
}, {} as Record<string, PackageState>);
|
||||
set({ installed: installedMap });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching installed apps:", error);
|
||||
}
|
||||
},
|
||||
|
||||
fetchInstalledApp: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/installed/${id}`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const installedApp: PackageState = await res.json();
|
||||
set((state) => ({
|
||||
installed: { ...state.installed, [id]: installedApp }
|
||||
}));
|
||||
return installedApp;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching installed app:", error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
fetchDownloads: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const downloads: DownloadItem[] = await res.json();
|
||||
set({ downloads: { root: downloads } });
|
||||
return downloads;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching downloads:", error);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
fetchOurApps: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/ourapps`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data: AppListing[] = await res.json();
|
||||
set({ ourApps: data });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching our apps:", error);
|
||||
}
|
||||
},
|
||||
|
||||
fetchDownloadsForApp: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads/${id}`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const downloads: DownloadItem[] = await res.json();
|
||||
set((state) => ({
|
||||
downloads: { ...state.downloads, [id]: downloads }
|
||||
}));
|
||||
return downloads;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching downloads for app:", error);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
fetchHomepageApps: async () => {
|
||||
try {
|
||||
const res = await fetch('/apps');
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
const data: HomepageApp[] = await res.json();
|
||||
set({ homepageApps: data });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching homepage apps:", error);
|
||||
}
|
||||
},
|
||||
|
||||
getLaunchUrl: (id: string) => {
|
||||
const app = get().homepageApps.find(app => `${app.package}:${app.publisher}` === id);
|
||||
if (app && app.path) {
|
||||
return app.path;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
checkMirror: async (node: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/mirrorcheck/${node}`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
return await res.json() as MirrorCheckFile;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking mirror:", error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
installApp: async (id: string, version_hash: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/install`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ version_hash })
|
||||
});
|
||||
if (res.status === HTTP_STATUS.CREATED) {
|
||||
await get().fetchInstalled();
|
||||
|
||||
// hacky: a small delay (500ms) before fetching homepage apps
|
||||
// to give the app time to add itself to the homepage
|
||||
// might make sense to add more state and do retry logic instead.
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
await get().fetchHomepageApps();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error installing app:", error);
|
||||
}
|
||||
},
|
||||
|
||||
uninstallApp: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}`, { method: 'DELETE' });
|
||||
if (res.status === HTTP_STATUS.NO_CONTENT) {
|
||||
await get().fetchInstalled();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error uninstalling app:", error);
|
||||
}
|
||||
},
|
||||
|
||||
downloadApp: async (id: string, version_hash: string, downloadFrom: string) => {
|
||||
const [package_name, publisher_node] = id.split(':');
|
||||
const appId = `${id}:${version_hash}`;
|
||||
set((state) => ({
|
||||
activeDownloads: {
|
||||
...state.activeDownloads,
|
||||
[appId]: { downloaded: 0, total: 100 }
|
||||
}
|
||||
}));
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/download`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
package_id: { package_name, publisher_node },
|
||||
version_hash,
|
||||
download_from: downloadFrom,
|
||||
}),
|
||||
});
|
||||
if (res.status !== HTTP_STATUS.OK) {
|
||||
get().clearActiveDownload(appId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error downloading app:", error);
|
||||
get().clearActiveDownload(appId);
|
||||
}
|
||||
},
|
||||
|
||||
clearAllActiveDownloads: () => set({ activeDownloads: {} }),
|
||||
|
||||
removeDownload: async (packageId: string, versionHash: string) => {
|
||||
try {
|
||||
const response = await fetch(`${BASE_URL}/downloads/${packageId}/remove`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ version_hash: versionHash }),
|
||||
});
|
||||
if (response.ok) {
|
||||
await get().fetchDownloadsForApp(packageId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to remove download:', error);
|
||||
}
|
||||
},
|
||||
|
||||
getCaps: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/caps`);
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
return await res.json() as PackageManifest;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error getting caps:", error);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
approveCaps: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/caps`, { method: 'POST' });
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
await get().fetchListing(id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error approving caps:", error);
|
||||
}
|
||||
},
|
||||
|
||||
startMirroring: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads/${id}/mirror`, {
|
||||
method: 'PUT'
|
||||
});
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
await get().fetchDownloadsForApp(id.split(':').slice(0, -1).join(':'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error starting mirroring:", error);
|
||||
}
|
||||
},
|
||||
|
||||
stopMirroring: async (id: string) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/downloads/${id}/mirror`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
await get().fetchDownloadsForApp(id.split(':').slice(0, -1).join(':'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error stopping mirroring:", error);
|
||||
}
|
||||
},
|
||||
|
||||
setAutoUpdate: async (id: string, version_hash: string, autoUpdate: boolean) => {
|
||||
try {
|
||||
const method = autoUpdate ? 'PUT' : 'DELETE';
|
||||
const res = await fetch(`${BASE_URL}/apps/${id}/auto-update`, {
|
||||
method,
|
||||
body: JSON.stringify({ version_hash })
|
||||
});
|
||||
if (res.status === HTTP_STATUS.OK) {
|
||||
await get().fetchListing(id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error setting auto-update:", error);
|
||||
}
|
||||
},
|
||||
|
||||
setActiveDownload: (appId, downloaded, total) => {
|
||||
set((state) => ({
|
||||
activeDownloads: {
|
||||
...state.activeDownloads,
|
||||
[appId]: { downloaded, total }
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
clearActiveDownload: (appId) => {
|
||||
set((state) => {
|
||||
const { [appId]: _, ...rest } = state.activeDownloads;
|
||||
return { activeDownloads: rest };
|
||||
});
|
||||
},
|
||||
|
||||
ws: new KinodeClientApi({
|
||||
uri: WEBSOCKET_URL,
|
||||
nodeId: (window as any).our?.node,
|
||||
processId: "main:app_store:sys",
|
||||
onMessage: (message) => {
|
||||
console.log('WebSocket message received', message);
|
||||
try {
|
||||
const data = JSON.parse(message);
|
||||
if (data.kind === 'progress') {
|
||||
const { package_id, version_hash, downloaded, total } = data.data;
|
||||
const appId = `${package_id.package_name}:${package_id.publisher_node}:${version_hash}`;
|
||||
get().setActiveDownload(appId, downloaded, total);
|
||||
} else if (data.kind === 'complete') {
|
||||
const { package_id, version_hash } = data.data;
|
||||
const appId = `${package_id.package_name}:${package_id.publisher_node}:${version_hash}`;
|
||||
get().clearActiveDownload(appId);
|
||||
get().fetchData(`${package_id.package_name}:${package_id.publisher_node}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
}
|
||||
},
|
||||
onOpen: (_e) => {
|
||||
console.log('WebSocket connection opened');
|
||||
},
|
||||
onClose: (_e) => {
|
||||
console.log('WebSocket connection closed');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
export default useAppsStore
|
Loading…
Reference in New Issue
Block a user