Skip to content

Commit

Permalink
feat: Fetch releases from s3 (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyaiox authored Nov 20, 2024
1 parent 6f435eb commit 2749454
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 14 deletions.
11 changes: 11 additions & 0 deletions buildResources/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access to use voice chat in the application</string>

<key>NSQuitAlwaysKeepsWindows</key>
<false/>
</dict>
</plist>
6 changes: 1 addition & 5 deletions electron-builder.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,7 @@ const config = {
],
hardenedRuntime: true,
entitlements: 'buildResources/entitlements.mac.plist',
extendInfo: [
{
NSMicrophoneUsageDescription: 'Need microphone access to use voice chat in the application',
},
],
extendInfo: 'buildResources/Info.plist',
extraResources: ['icon.icns'],
},
dmg: {
Expand Down
20 changes: 19 additions & 1 deletion packages/main/src/modules/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { spawn } from 'child_process';
import { app, BrowserWindow, ipcMain } from 'electron';
import { CancelError, download } from 'electron-dl';
import log from 'electron-log/main';
import semver from 'semver';
import {
Analytics,
IPC_EVENTS,
Expand Down Expand Up @@ -48,7 +49,7 @@ function getExplorerBinPath(version?: string): string {
}
}

export function isExplorerInstalled(_event: Electron.IpcMainInvokeEvent, version: string) {
export function isExplorerInstalled(_event: Electron.IpcMainInvokeEvent, version?: string) {
return fs.existsSync(getExplorerBinPath(version));
}

Expand Down Expand Up @@ -162,13 +163,30 @@ export async function installExplorer(event: Electron.IpcMainInvokeEvent, versio
analytics.track(ANALYTICS_EVENT.INSTALL_VERSION_SUCCESS, { version });

fs.rmSync(filePath);
// Delete old versions
cleanupVersions();
} catch (error) {
log.error('[Main Window][IPC][InstallExplorer] Failed to install app:', error);
event.sender.send(IPC_EVENTS.INSTALL_STATE, { type: IPC_EVENT_DATA_TYPE.ERROR, error });
analytics.track(ANALYTICS_EVENT.INSTALL_VERSION_ERROR, { version });
}
}

async function cleanupVersions() {
const installations = fs.readdirSync(EXPLORER_PATH).filter(folder => semver.valid(folder));
if (installations.length < 1) {
return;
}

const sortedVersions = installations.sort(semver.compare);
sortedVersions.slice(0, -2).forEach(version => {
const folderPath = join(EXPLORER_PATH, version);
if (fs.existsSync(folderPath)) {
fs.rmSync(folderPath, { recursive: true, force: true });
}
});
}

export async function launchExplorer(event: Electron.IpcMainInvokeEvent, version?: string) {
const versionData = JSON.parse(fs.readFileSync(EXPLORER_VERSION_PATH, 'utf8'));

Expand Down
2 changes: 1 addition & 1 deletion packages/preload/src/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function installState(cb: (event: IpcRendererEvent, state: IpcRendererEve
return ipcRenderer.on(IPC_EVENTS.INSTALL_STATE, cb);
}

export async function isExplorerInstalled(version: string): Promise<boolean> {
export async function isExplorerInstalled(version?: string): Promise<boolean> {
const resp = await ipcRenderer.invoke(IPC_HANDLERS.IS_EXPLORER_INSTALLED, version);
return resp;
}
Expand Down
45 changes: 40 additions & 5 deletions packages/preload/src/s3.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { S3Client, ListObjectsV2Command } from '@aws-sdk/client-s3';
import type { ListObjectsV2Output, S3ClientConfig } from '@aws-sdk/client-s3';
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
import type { ListObjectsV2Output, GetObjectCommandOutput, S3ClientConfig } from '@aws-sdk/client-s3';
import { Readable } from 'node:stream';
import { getBucketURL, RELEASE_PREFIX } from '#shared';
import { getOSName } from './ipc';

Expand Down Expand Up @@ -33,11 +34,11 @@ if (import.meta.env.VITE_AWS_DEFAULT_REGION) {

const s3 = new S3Client(config);

export async function fetchExplorerReleases(): Promise<ListObjectsV2Output['Contents']> {
export async function fetchExplorerReleases(version?: string): Promise<ListObjectsV2Output['Contents']> {
try {
const params = {
Bucket: BUCKET,
Prefix: RELEASE_PREFIX,
Prefix: version ? `${RELEASE_PREFIX}/${version}` : RELEASE_PREFIX,
};

const data: ListObjectsV2Output = await s3.send(new ListObjectsV2Command(params));
Expand All @@ -51,8 +52,28 @@ export async function fetchExplorerReleases(): Promise<ListObjectsV2Output['Cont
}
}

export async function fetchExplorerLatestRelease() {
try {
const params = {
Bucket: BUCKET,
Key: `${RELEASE_PREFIX}/latest.json`,
};

const response: GetObjectCommandOutput = await s3.send(new GetObjectCommand(params));

if (response.Body instanceof Readable) {
const jsonString = await streamToString(response.Body);
return JSON.parse(jsonString);
}
} catch (err) {
console.error('Error Fetching Explorer releases', err);
throw err;
}
}

export async function getLatestExplorerRelease(_version?: string, _isPrerelease: boolean = false) {
const releases = await fetchExplorerReleases();
const latestRelease = await fetchExplorerLatestRelease();
const releases = await fetchExplorerReleases(latestRelease['version']);
const os = (await getOSName()).toLowerCase();
const release = releases?.find(release => release.Key?.toLowerCase().includes(os));
if (release && release.Key) {
Expand All @@ -64,3 +85,17 @@ export async function getLatestExplorerRelease(_version?: string, _isPrerelease:
};
}
}

/**
* Converts a Readable stream to a string.
* @param stream The Readable stream to convert.
* @returns A promise resolving to the string content of the stream.
*/
async function streamToString(stream: Readable): Promise<string> {
return new Promise((resolve, reject) => {
const chunks: Uint8Array[] = []; // Ensure the array is of type Uint8Array
stream.on('data', chunk => chunks.push(chunk as Uint8Array)); // Cast each chunk
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))); // Use Buffer.concat
stream.on('error', reject);
});
}
8 changes: 6 additions & 2 deletions packages/renderer/src/components/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,16 @@ export const Home: React.FC = memo(() => {
try {
const { browser_download_url: url, version } = await getLatestRelease(getVersion(), getIsPrerelease());
setDownloadUrl(url);
// If there is any Explorer version installed, set isInstalled = true
setIsInstalled(await isExplorerInstalled());

// Validates if the version fetched is installed or not to download the new version
const _isInstalled = await isExplorerInstalled(version);
if (!_isInstalled) {
handleDownload(url);
return;
}
setIsInstalled(true);

setState(AppState.Installed);

const _isUpdated = await isExplorerUpdated(version);
Expand Down Expand Up @@ -268,7 +272,7 @@ export const Home: React.FC = memo(() => {
}, []);

const renderDownloadStep = useCallback(() => {
const isUpdating = isInstalling && isInstalled && !isUpdated;
const isUpdating = isDownloading && isInstalled && !isUpdated;

return (
<Box>
Expand Down

0 comments on commit 2749454

Please sign in to comment.