Skip to content

Commit

Permalink
feat: Implement S3 logic to fetch latest explorer release
Browse files Browse the repository at this point in the history
  • Loading branch information
cyaiox committed Oct 16, 2024
1 parent 46244a7 commit 4be859a
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 47 deletions.
15 changes: 12 additions & 3 deletions packages/main/src/modules/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@ import { spawn } from 'child_process';
import { app, BrowserWindow, ipcMain } from 'electron';
import { CancelError, download } from 'electron-dl';
import log from 'electron-log/main';
import { Analytics, IPC_EVENTS, IPC_EVENT_DATA_TYPE, ANALYTICS_EVENT, IPC_HANDLERS, getErrorMessage } from '#shared';
import {
Analytics,
IPC_EVENTS,
IPC_EVENT_DATA_TYPE,
ANALYTICS_EVENT,
IPC_HANDLERS,
getErrorMessage,
getBucketURL,
RELEASE_PREFIX,
} from '#shared';
import { getAppBasePath, decompressFile, getOSName, isAppUpdated, PLATFORM, getAppVersion } from '../helpers';
import { getUserId } from './config';

Expand Down Expand Up @@ -54,8 +63,8 @@ export async function downloadExplorer(event: Electron.IpcMainInvokeEvent, url:

log.info('[Main Window][IPC][DownloadExplorer] Downloading', url);

const versionPattern = /^https:\/\/github.com\/decentraland\/.+\/releases\/download\/(v?\d+\.\d+\.\d+-?\w+)\/(\w+.zip)$/;
const version = url.match(versionPattern)?.[1];
const versionPattern = new RegExp(`(^${getBucketURL()}\\/\\${RELEASE_PREFIX})\\/(v?\\d+\\.\\d+\\.\\d+-?\\w*)\\/(\\w+.zip)$`);
const version = url.match(versionPattern)?.[2];

if (!version) {
log.error('[Main Window][IPC][DownloadExplorer] No valid url provided');
Expand Down
2 changes: 2 additions & 0 deletions packages/preload/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
getOSName,
} from './ipc';
import { getVersion, getIsPrerelease, getRunDevVersion, getDownloadedFilePath } from './argvs';
import { getLatestExplorerRelease } from './s3';
export {
downloadExplorer,
downloadState,
Expand All @@ -27,4 +28,5 @@ export {
getIsPrerelease,
getRunDevVersion,
getDownloadedFilePath,
getLatestExplorerRelease,
};
66 changes: 66 additions & 0 deletions packages/preload/src/s3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { S3Client, ListObjectsV2Command } from '@aws-sdk/client-s3';

Check failure on line 1 in packages/preload/src/s3.ts

View workflow job for this annotation

GitHub Actions / typechecking / typescript

Cannot find module '@aws-sdk/client-s3' or its corresponding type declarations.
import type { ListObjectsV2Output, S3ClientConfig } from '@aws-sdk/client-s3';

Check failure on line 2 in packages/preload/src/s3.ts

View workflow job for this annotation

GitHub Actions / typechecking / typescript

Cannot find module '@aws-sdk/client-s3' or its corresponding type declarations.
import { getBucketURL, RELEASE_PREFIX } from '#shared';
import { getOSName } from './ipc';

const BUCKET = import.meta.env.VITE_AWS_S3_BUCKET;

let config: S3ClientConfig = { retryMode: 'standard', maxAttempts: 3 };

if (import.meta.env.VITE_AWS_ACCESS_KEY_ID && import.meta.env.VITE_AWS_SECRET_ACCESS_KEY) {
config = {
...config,
credentials: {
accessKeyId: import.meta.env.VITE_AWS_ACCESS_KEY_ID,
secretAccessKey: import.meta.env.VITE_AWS_SECRET_ACCESS_KEY,
},
};
}

if (import.meta.env.VITE_AWS_ENDPOINT_URL) {
config = {
...config,
endpoint: import.meta.env.VITE_AWS_ENDPOINT_URL,
};
}

if (import.meta.env.VITE_AWS_DEFAULT_REGION) {
config = {
...config,
region: import.meta.env.VITE_AWS_DEFAULT_REGION,
};
}

const s3 = new S3Client(config);

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

const data: ListObjectsV2Output = await s3.send(new ListObjectsV2Command(params));

if (data.Contents) {
return data.Contents.sort((a, b) => (b.LastModified?.getTime() ?? 0) - (a.LastModified?.getTime() ?? 0));

Check failure on line 46 in packages/preload/src/s3.ts

View workflow job for this annotation

GitHub Actions / typechecking / typescript

Parameter 'a' implicitly has an 'any' type.

Check failure on line 46 in packages/preload/src/s3.ts

View workflow job for this annotation

GitHub Actions / typechecking / typescript

Parameter 'b' implicitly has an 'any' type.
}
} 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 os = (await getOSName()).toLowerCase();
const release = releases?.find(release => release.Key?.toLowerCase().includes(os));

Check failure on line 57 in packages/preload/src/s3.ts

View workflow job for this annotation

GitHub Actions / typechecking / typescript

Parameter 'release' implicitly has an 'any' type.
if (release && release.Key) {
const versionMatch = release.Key.match(/v?\d+\.\d+\.\d+-?\w*/);

return {
browser_download_url: `${getBucketURL()}/${release.Key}`,
version: versionMatch?.[0] ?? '0.0.0',
};
}
}
53 changes: 12 additions & 41 deletions packages/renderer/src/components/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
launchState,
isExplorerInstalled,
isExplorerUpdated,
getOSName,
getVersion,
getIsPrerelease,
getRunDevVersion,
getDownloadedFilePath,
getLatestExplorerRelease,
} from '#preload';
import {
IPC_EVENT_DATA_TYPE,
Expand All @@ -25,55 +25,28 @@ import {
getErrorMessage,
IpcRendererDownloadCompletedEventData,
} from '#shared';
import { APPS, AppState, GithubReleaseResponse, GithubRelease } from './types';
import { AppState, ReleaseResponse } from './types';
import { Landscape, LoadingBar } from './Home.styles';
import LANDSCAPE_IMG from '/@assets/landscape.png';

const ONE_SECOND = 1000;
const FIVE_SECONDS = 5 * ONE_SECOND;

/**
* Retrieves the latest release information from the GitHub API.
* @param version - Optional. The specific version to retrieve. If not provided, retrieves the latest version.
* @param isPrerelease - Optional. Specifies whether to retrieve a prerelease version. Default is false.
* Retrieves the latest release.
* TODO: @param version - Optional. The specific version to retrieve. If not provided, retrieves the latest version.
* TODO: @param isPrerelease - Optional. Specifies whether to retrieve a prerelease version. Default is false.
* @returns A Promise that resolves to the latest release information.
* @throws An error if no asset is found for the specified platform or if the API request fails.
*/
async function getLatestRelease(version?: string, isPrerelease: boolean = false): Promise<GithubReleaseResponse> {
async function getLatestRelease(version?: string, isPrerelease: boolean = false): Promise<ReleaseResponse> {
try {
const resp = await fetch(`https://api.github.com/repos/decentraland/${APPS.Explorer}/releases`);
if (resp.status === 200) {
const releases: GithubRelease[] = await resp.json();
const os = await getOSName();
let isMatchingOS = false;
let isValidVersion = false;
let isValidPrerelease = false;

for (const release of releases) {
for (const asset of release.assets) {
isMatchingOS = asset.name.toLowerCase().includes(os.toLowerCase());
isValidVersion = !version || version === release.name;
isValidPrerelease = !isPrerelease || (isPrerelease && !!release.prerelease);
if (isMatchingOS && isValidVersion && isValidPrerelease) {
return {
browser_download_url: asset.browser_download_url,
version: release.name,
};
}
}
}

if (!isMatchingOS) {
throw new Error('No asset found for your platform');
} else if (!isValidVersion) {
throw new Error('No asset found for the specified version');
} else if (!isValidPrerelease) {
throw new Error('No asset found with the prerelease flag');
}
const release = await getLatestExplorerRelease(version, isPrerelease);
if (release) {
return release;
}

const _resp = await resp.json();
throw new Error(getErrorMessage(_resp));
throw new Error('No asset found for your platform');
} catch (error) {
log.error('[Renderer][Home][GetLatestRelease]', error);
throw new Error('Failed to fetch latest release');
Expand Down Expand Up @@ -330,16 +303,14 @@ export const Home: React.FC = memo(() => {
}, []);

const handleOnClickRetry = useCallback(() => {
if (isFetching) {
return handleRetryFetch(true);
} else if (isDownloading) {
if (isDownloading) {
return handleRetryDownload(true);
} else if (isInstalling) {
return handleRetryInstall(true);
} else if (isLaunching) {
return handleLaunch();
} else {
return null;
return handleRetryFetch(true);
}
}, [state]);

Expand Down
2 changes: 1 addition & 1 deletion packages/renderer/src/components/Home/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export enum APPS {
Explorer = 'unity-explorer',
}

export interface GithubReleaseResponse {
export interface ReleaseResponse {
browser_download_url: string;
version: string;
}
Expand Down
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './analytics/types';
export * from './ipc/types';
export * from './helpers';
export * from './s3/helpers';
export * from './s3/types';
4 changes: 2 additions & 2 deletions packages/shared/src/s3/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export function getBucketURL(): string {
return import.meta.env.VITE_AWS_S3_ENDPOINT
? `${import.meta.env.VITE_AWS_S3_ENDPOINT}/${import.meta.env.VITE_AWS_S3_BUCKET}`
return import.meta.env.VITE_AWS_ENDPOINT_URL
? `${import.meta.env.VITE_AWS_ENDPOINT_URL}/${import.meta.env.VITE_AWS_S3_BUCKET}`
: `https://${import.meta.env.VITE_AWS_S3_BUCKET}.s3.amazonaws.com`;
}
1 change: 1 addition & 0 deletions packages/shared/src/s3/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const RELEASE_PREFIX = '@dcl/unity-explorer/releases';

0 comments on commit 4be859a

Please sign in to comment.