Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install bytes #73

Merged
merged 2 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
APP_STORE,
APP_STORE_APP_ID,
DEVHUB_APP_ID,
DISTRIBUTION_TYPE_DEFAULT_APP,
MAIN_SCREEN,
SEARCH_HEIGH,
SETTINGS_SCREEN,
Expand Down Expand Up @@ -637,22 +638,22 @@ const router = t.router({

const holochainManager = getHolochainManager(HOLOCHAIN_DATA_ROOT!.name);

const distributionInfo: DistributionInfoV1 = {
type: 'default-app',
};

if (happAndUiBytes.uiBytes) {
await holochainManager.installWebHappFromBytes(
happAndUiBytes,
appId,
distributionInfo,
{
type: DISTRIBUTION_TYPE_DEFAULT_APP,
},
networkSeed,
);
} else {
await holochainManager.installHeadlessHappFromBytes(
happAndUiBytes.happBytes,
appId,
distributionInfo,
{
type: DISTRIBUTION_TYPE_DEFAULT_APP,
},
networkSeed,
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import path from 'path';
import semver from 'semver';
import type { ZodSchema } from 'zod';

import { APP_STORE_APP_ID, DEVHUB_APP_ID } from '$shared/const';
import { APP_STORE_APP_ID, DEVHUB_APP_ID, DISTRIBUTION_TYPE_DEFAULT_APP } from '$shared/const';
import type { AppToInstall } from '$shared/types';
import {
type EventKeys,
Expand Down Expand Up @@ -122,7 +122,7 @@ export const processHeadlessAppInstallation =
await holochainManager.installHeadlessHappFromBytes(
Array.from(happBytes),
id,
{ type: 'default-app' },
{ type: DISTRIBUTION_TYPE_DEFAULT_APP },
defaultAppsNetworkSeed,
);
}
Expand Down
7 changes: 5 additions & 2 deletions src/renderer/src/lib/components/AppDetailsPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import clsx from 'clsx';

import { Button } from '$components';
import type { DistributionInfoV1 } from '$shared/types';

export let imageUrl: string | undefined = undefined;
export let title: string;
export let subtitle = '';
export let version: string = '';
export let distributionInfo: DistributionInfoV1 | undefined = undefined;
export let selectedIndex = 0;
export let buttons: Array<string>;
</script>
Expand All @@ -26,7 +27,9 @@
<div class="flex flex-col">
<div class="flex items-end">
<h3 class="h3">{title}</h3>
<p class="ml-2 text-xs">{version}</p>
{#if distributionInfo?.type === 'appstore'}
<p class="ml-2 text-xs">{distributionInfo.appVersion}</p>
{/if}
</div>
{#if subtitle}
<p class="text-xs text-white/80">{subtitle}</p>
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/lib/const/queryCacheKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export const APP_STORE_MY_HAPPS_QUERY_KEY = 'appstore-my-happs';
export const APP_STORE_HAPPS_QUERY_KEY = 'appstore-happs';
export const ALL_APP_VERSIONS_DEVHUB_QUERY_KEY = 'all-app-versions-versions-devhub';
export const ALL_APP_VERSIONS_APPSTORE_QUERY_KEY = 'all-app-versions-versions-appstore';
export const CHECK_FOR_APP_UI_UPDATES_QUERY_KEY = 'check-for-app-ui-updates';
17 changes: 17 additions & 0 deletions src/renderer/src/lib/helpers/other.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { encodeHashToBase64 } from '@holochain/client';

import { createAppStoreClient, createDevHubClient } from '$services';
import {
AppStoreDistributionInfoSchema,
type CellId,
CellInfoSchema,
DistributionInfoV1Schema,
type ExtendedAppInfo,
ExtendedAppInfoSchema,
type InitializeAppPorts
Expand Down Expand Up @@ -53,3 +55,18 @@ export const convertFileToUint8Array = async (file: File): Promise<Uint8Array> =
const buffer = await file.arrayBuffer();
return new Uint8Array(buffer);
};

export const getAppStoreDistributionHash = (app: unknown): string | undefined => {
const parsedApp = DistributionInfoV1Schema.safeParse(app);
if (!parsedApp.success) {
return undefined;
}
const parsedAppData = AppStoreDistributionInfoSchema.safeParse(parsedApp.data);
if (!parsedAppData.success) {
return undefined;
}

return parsedAppData.data.appVersionActionHash;
};

export const filterHash = (hash: unknown): hash is string => hash !== undefined;
68 changes: 68 additions & 0 deletions src/renderer/src/lib/modal/InstallAppFromBytes.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script lang="ts">
import { getModalStore } from '@skeletonlabs/skeleton';

import { goto } from '$app/navigation';
import { PRESEARCH_URL_QUERY } from '$const';
import { showModalError } from '$helpers';
import { i18n, trpc } from '$services';
import { APPS_VIEW, DISTRIBUTION_TYPE_APPSTORE } from '$shared/const';

import ModalInstallForm from './ModalInstallForm.svelte';

const client = trpc();

const modalStore = getModalStore();

export let bytes: Uint8Array;
export let appName: string;
export let appstoreDnaHash: string;
export let appEntryActionHash: string;
export let appVersionActionHash: string;
export let appVersion: string;

let formData = {
appId: '',
networkSeed: ''
};

const installedApps = client.getInstalledApps.createQuery();
const installWebhappFromBytes = client.installWebhappFromBytes.createMutation();
</script>

<ModalInstallForm
name={appName}
bind:formData
onSubmit={() =>
$installWebhappFromBytes.mutate(
{
bytes: bytes,
distributionInfo: {
type: DISTRIBUTION_TYPE_APPSTORE,
appName,
appstoreDnaHash,
appEntryActionHash,
appVersionActionHash,
appVersion
},
appId: formData.appId,
networkSeed: formData.networkSeed
},
{
onSuccess: () => {
$installedApps.refetch();
goto(`${APPS_VIEW}?${PRESEARCH_URL_QUERY}=${formData.appId}`);
modalStore.close();
},
onError: (error) => {
modalStore.close();
console.error(error);
showModalError({
modalStore,
errorTitle: $i18n.t('appError'),
errorMessage: $i18n.t(error.message)
});
}
}
)}
isPending={$installWebhappFromBytes.isPending}
/>
3 changes: 2 additions & 1 deletion src/renderer/src/lib/modal/ModalInstallForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

export let formData: AppInstallFormData;
export let files: FileList | null = null;
export let name: string = '';
export let onSubmit: () => void;
export let isPending = false;
export let acceptFileType = false;
Expand All @@ -22,7 +23,7 @@
>
<slot name="avatar" />
<header class="pt-4 text-2xl font-bold">
{acceptFileType ? $i18n.t('installFromYourDevice') : $i18n.t('kando')}
{name ? name : acceptFileType ? $i18n.t('installFromYourDevice') : $i18n.t('kando')}
</header>
<form class="modal-form flex flex-col space-y-4 p-4" on:submit|preventDefault={onSubmit}>
{#if acceptFileType}
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/lib/modal/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as AddNewHappVersion } from './AddNewHappVersion.svelte';
export { default as AddPublisher } from './AddPublisher.svelte';
export { default as DevHubInstallationConfirmation } from './DevHubInstallationConfirmation.svelte';
export { default as InstallAppFromBytes } from './InstallAppFromBytes.svelte';
export { default as InstallFromFile } from './InstallFromFile.svelte';
export { default as InstallKando } from './InstallKando.svelte';
36 changes: 13 additions & 23 deletions src/renderer/src/lib/queries/happs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ActionHash, type ActionHashB64, decodeHashFromBase64 } from '@holochain/client';
import { type ActionHash, decodeHashFromBase64 } from '@holochain/client';
// @ts-expect-error the @spartan-hc/bundles package has no typescript types
import { Bundle } from '@spartan-hc/bundles';
import { createMutation, createQuery, QueryClient } from '@tanstack/svelte-query';
Expand All @@ -7,8 +7,7 @@ import type {
AppVersionEntry,
BundleHashes,
CreatePublisherFrontendInput,
DevhubAppClient,
Entity
DevhubAppClient
} from 'appstore-tools';
import { sha256 } from 'js-sha256';
import { get, type Writable } from 'svelte/store';
Expand All @@ -18,17 +17,17 @@ import {
ALL_APP_VERSIONS_DEVHUB_QUERY_KEY,
APP_STORE_HAPPS_QUERY_KEY,
APP_STORE_MY_HAPPS_QUERY_KEY,
CHECK_FOR_APP_UI_UPDATES_QUERY_KEY,
PUBLISHERS_QUERY_KEY
} from '$const';
import { uint8ArrayToURIComponent } from '$helpers';
import { getAppStoreClient, getDevHubClient } from '$services';
import {
APP_STORE_CLIENT_NOT_INITIALIZED_ERROR,
DEV_HUB_CLIENT_NOT_INITIALIZED_ERROR,
type ExtendedAppInfo,
NO_PUBLISHERS_AVAILABLE_ERROR
} from '$shared/types';
import type { AppData, PublishNewVersionData } from '$types';
import { type AppData, type PublishNewVersionData } from '$types';

type ClientType = DevhubAppClient | AppstoreAppClient;

Expand Down Expand Up @@ -309,30 +308,21 @@ export const createFetchWebappBytesMutation = () => {
});
};

export const createCheckForAppUiUpdatesMutation = () => {
return createMutation({
mutationFn: async (
installedApps: ExtendedAppInfo[]
): Promise<Record<ActionHashB64, Entity<AppVersionEntry>>> => {
export const createCheckForAppUiUpdatesQuery = () => (appVersionActionHashes: string[]) => {
return createQuery({
staleTime: 1800000,
queryKey: [CHECK_FOR_APP_UI_UPDATES_QUERY_KEY, appVersionActionHashes],
queryFn: async () => {
const appStoreClient = getAppStoreClientOrThrow();
const distinctVersionHashes = installedApps
.filter((info) => info.distributionInfo.type === 'appstore')
.map((info) =>
info.distributionInfo.type === 'appstore'
? info.distributionInfo.appVersionActionHash
: undefined
);

const updates: Record<ActionHashB64, Entity<AppVersionEntry>> = {};

await Promise.all(
const distinctVersionHashes = appVersionActionHashes;
const updates = await Promise.all(
distinctVersionHashes.map(async (hash) => {
const maybeUpdate = await appStoreClient.checkForUiUpdate(decodeHashFromBase64(hash!));
if (maybeUpdate) updates[hash as string] = maybeUpdate;
return maybeUpdate ? { [hash]: maybeUpdate } : null;
})
);

return updates;
return updates.reduce((acc, update) => (update ? { ...acc, ...update } : acc), {});
}
});
};
6 changes: 3 additions & 3 deletions src/renderer/src/lib/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
createAppStoreHappsQuery,
createAppStoreMyHappsQuery,
createAppVersionsAppstoreQuery,
createCheckForAppUiUpdatesMutation,
createCheckForAppUiUpdatesQuery,
createFetchWebappBytesMutation,
createPublisherMutation,
createPublishersQuery,
Expand All @@ -22,7 +22,7 @@ export function createAppQueries() {
const publisherMutation = createPublisherMutation(queryClient);
const publishHappMutation = createPublishHappMutation(queryClient);
const publishNewVersionMutation = createPublishNewVersionMutation(queryClient);
const checkForAppUiUpdatesMutation = createCheckForAppUiUpdatesMutation();
const checkForAppUiUpdatesQuery = createCheckForAppUiUpdatesQuery();

return {
publishersQuery,
Expand All @@ -33,6 +33,6 @@ export function createAppQueries() {
publishNewVersionMutation,
fetchWebappBytesMutation,
appStoreHappsQuery,
checkForAppUiUpdatesMutation
checkForAppUiUpdatesQuery
};
}
14 changes: 11 additions & 3 deletions src/renderer/src/routes/(main)/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,24 @@
import { IconButton, Input, TopBar } from '$components';
import { SEARCH_URL_QUERY, SELECTED_ICON_STYLE } from '$const';
import {
filterHash,
getAppStoreDistributionHash,
handleNavigationWithAnimationDelay,
initializeDefaultAppPorts,
setSearchInput,
showModalError,
validateApp
} from '$helpers';
import { Gear, Home, Rocket } from '$icons';
import { createAppQueries } from '$queries';
import { i18n, trpc } from '$services';
import { APP_STORE, APPS_VIEW } from '$shared/const';
import { getErrorMessage } from '$shared/helpers';
import { navigationStore } from '$stores';
import { createAppQueries } from '$queries';

const client = trpc();

const { checkForAppUiUpdatesMutation } = createAppQueries();
const { checkForAppUiUpdatesQuery } = createAppQueries();

const modalStore = getModalStore();

Expand Down Expand Up @@ -58,6 +60,12 @@

$: if (type) inputExpanded = true;

$: uiUpdates = checkForAppUiUpdatesQuery(
$installedApps?.data
?.map((app) => getAppStoreDistributionHash(app.distributionInfo))
.filter(filterHash) ?? []
);

const handleNavigation = handleNavigationWithAnimationDelay(() => (inputExpanded = false));

const handlePress = (event: CustomEvent): void => {
Expand Down Expand Up @@ -168,7 +176,7 @@
<IconButton onClick={() => $openSettings.mutate(undefined)}>
<div class="relative">
<Gear />
{#if Object.values($checkForAppUiUpdates.data ?? {}).some(Boolean)}
{#if Object.values($uiUpdates.data ?? {}).some(Boolean)}
<div class="absolute -right-1 -top-1 h-3 w-3 rounded-full bg-warning-500"></div>
{/if}
</div>
Expand Down
23 changes: 15 additions & 8 deletions src/renderer/src/routes/(main)/app-store/[slug]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { page } from '$app/stores';
import { AppDetailsPanel, Button } from '$components';
import { createImageUrl, showModalError, uint8ArrayToURIComponent } from '$helpers';
import { InstallAppFromBytes } from '$modal';
import { createAppQueries } from '$queries';
import { i18n } from '$services';

Expand Down Expand Up @@ -57,14 +58,20 @@
});
},
onSuccess: (bytes) => {
console.log('GOT BYTES: ', bytes.length);
// const distributionInfo = {
// appName: app.title,
// appVersion: latestVersion.content.version,
// appVersionActionHash: latestVersion.id,
// appEntryActionHash: latestVersion.address,
// appstoreDnaHash: latestVersion.content.apphub_hrl.dna
// };
modalStore.trigger({
type: 'component',
component: {
ref: InstallAppFromBytes,
props: {
bytes: bytes,
appName: app.title,
appVersion: latestVersion.content.version,
appVersionActionHash: latestVersion.id,
appEntryActionHash: latestVersion.address,
appstoreDnaHash: latestVersion.content.apphub_hrl.dna
}
}
});
}
});
},
Expand Down
Loading