This commit is contained in:
ABevier 2023-03-29 23:02:20 -04:00 committed by GitHub
parent b412ab8a2a
commit dba09e5d34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 79 additions and 61 deletions

View file

@ -41,7 +41,7 @@ jobs:
steps: steps:
- uses: teaxyz/setup@v0 - uses: teaxyz/setup@v0
with: with:
version: 0.25.0 version: 0.26.2
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: get gui version - name: get gui version
@ -282,4 +282,4 @@ jobs:
run: | run: |
export S3_INSTALLERS_KEY=s3://preview.gui.tea.xyz/$prefix/dist.tgz export S3_INSTALLERS_KEY=s3://preview.gui.tea.xyz/$prefix/dist.tgz
aws s3 cp dist.tgz $S3_INSTALLERS_KEY aws s3 cp dist.tgz $S3_INSTALLERS_KEY
echo s3-key=$S3_INSTALLERS_KEY >> $GITHUB_OUTPUT echo s3-key=$S3_INSTALLERS_KEY >> $GITHUB_OUTPUT

View file

@ -36,7 +36,7 @@ jobs:
steps: steps:
- uses: teaxyz/setup@v0 - uses: teaxyz/setup@v0
with: with:
version: 0.25.0 version: 0.26.2
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: install app dependencies - name: install app dependencies
run: tea -E xc setup run: tea -E xc setup
@ -51,7 +51,7 @@ jobs:
steps: steps:
- uses: teaxyz/setup@v0 - uses: teaxyz/setup@v0
with: with:
version: 0.25.0 version: 0.26.2
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: cache node_modules build - name: cache node_modules build
# TODO: cache issue in our self-hosted macos runner ESPIPE: invalid seek, read # TODO: cache issue in our self-hosted macos runner ESPIPE: invalid seek, read
@ -257,4 +257,4 @@ jobs:
```bash ```bash
http://preview.gui.tea.xyz.s3-website-us-east-1.amazonaws.com/${{ needs.changes.outputs.preview_folder }}/${{ steps.app_files.outputs.dmg_x86 }} http://preview.gui.tea.xyz.s3-website-us-east-1.amazonaws.com/${{ needs.changes.outputs.preview_folder }}/${{ steps.app_files.outputs.dmg_x86 }}
``` ```
copy-paste into a browser to download copy-paste into a browser to download

View file

@ -11,7 +11,6 @@
* - connect to a local platform api and returns a data * - connect to a local platform api and returns a data
*/ */
import semverCompare from "semver/functions/compare";
import type { Package, Review, AirtablePost, Bottle } from "@tea/ui/types"; import type { Package, Review, AirtablePost, Bottle } from "@tea/ui/types";
import { type GUIPackage, type DeviceAuth, type Session, AuthStatus } from "./types"; import { type GUIPackage, type DeviceAuth, type Session, AuthStatus } from "./types";
@ -21,26 +20,24 @@ import { installPackageCommand } from "./native/cli";
import { get as apiGet } from "$libs/v1-client"; import { get as apiGet } from "$libs/v1-client";
import axios from "axios"; import axios from "axios";
import withRetry from "./utils/retry";
const log = window.require("electron-log"); const log = window.require("electron-log");
const { ipcRenderer, shell } = window.require("electron"); const { ipcRenderer, shell } = window.require("electron");
let retryLimit = 0;
export async function getDistPackages(): Promise<Package[]> { export async function getDistPackages(): Promise<Package[]> {
let packages: Package[] = [];
try { try {
const req = await axios.get<Package[]>( return withRetry(async () => {
"https://s3.amazonaws.com/preview.gui.tea.xyz/packages.json" const req = await axios.get<Package[]>(
); "https://s3.amazonaws.com/preview.gui.tea.xyz/packages.json"
log.info("packages received:", req.data.length); );
packages = req.data; log.info("packages received:", req.data.length);
return req.data;
});
} catch (error) { } catch (error) {
retryLimit++;
log.error("getDistPackagesList:", error); log.error("getDistPackagesList:", error);
if (retryLimit < 3) packages = await getDistPackages(); return [];
} }
retryLimit = 0;
return packages;
} }
export async function getInstalledPackages(): Promise<InstalledPackage[]> { export async function getInstalledPackages(): Promise<InstalledPackage[]> {
@ -135,36 +132,31 @@ export async function getDeviceAuth(deviceId: string): Promise<DeviceAuth> {
export async function getPackageBottles(packageName: string): Promise<Bottle[]> { export async function getPackageBottles(packageName: string): Promise<Bottle[]> {
try { try {
const pkg = await apiGet<Package>(`packages/${packageName.replaceAll("/", ":")}`); return withRetry(async () => {
log.info(`got ${pkg?.bottles?.length || 0} bottles for ${packageName}`); const pkg = await apiGet<Package>(`packages/${packageName.replaceAll("/", ":")}`);
return (pkg && pkg.bottles) || []; log.info(`got ${pkg?.bottles?.length || 0} bottles for ${packageName}`);
return (pkg && pkg.bottles) || [];
});
} catch (error) { } catch (error) {
log.error(error); log.error("getPackageBottles:", error);
return []; return [];
} }
} }
const retryGetPackage: { [key: string]: number } = {};
export async function getPackage(packageName: string): Promise<Partial<Package>> { export async function getPackage(packageName: string): Promise<Partial<Package>> {
let pkg: Partial<Package> = {};
try { try {
const data = await apiGet<Partial<Package>>(`packages/${packageName.replaceAll("/", ":")}`); return await withRetry(async () => {
if (data) { const data = await apiGet<Partial<Package>>(`packages/${packageName.replaceAll("/", ":")}`);
pkg = data; if (data) {
} else { return data;
throw new Error(`package:${packageName} not found`); } else {
} throw new Error(`package:${packageName} not found`);
}
});
} catch (error) { } catch (error) {
log.error(error); log.error("getPackage:", error);
retryGetPackage[packageName] = (retryGetPackage[packageName] || 0) + 1; return {};
if (retryGetPackage[packageName] < 3) {
pkg = await getPackage(packageName);
} else {
log.info(`failed to get package:${packageName} after 3 tries`);
}
} }
return pkg;
} }
export const getSession = async (): Promise<Session | null> => { export const getSession = async (): Promise<Session | null> => {

View file

@ -0,0 +1,34 @@
const log = window.require("electron-log");
export type RetryOptions = {
// Number of times to retry. default 10
maxRetries?: number;
// Initial delay in ms. default 100
initialDelayMs?: number;
// Maximum delay in ms. default 5000
maxDelayMs?: number;
};
// Retry a function up to maxRetries times, with exponential backoff
// With defaults retry cadence will look like this:
// 100ms, 200ms, 400ms, 800ms, 1600ms, 3200ms, 5000ms, 5000ms, 5000ms, 5000ms
export default async function withRetry<T>(
fn: () => Promise<T>,
{ maxRetries = 10, initialDelayMs = 100, maxDelayMs = 5000 }: RetryOptions = {}
) {
let retries = 0;
let currentDelay = initialDelayMs;
while (retries <= maxRetries) {
try {
return await fn();
} catch (err) {
log.error(err);
retries++;
await wait(currentDelay);
currentDelay = Math.min(currentDelay * 2, maxDelayMs);
}
}
throw new Error(`Failed after ${maxRetries} retries`);
}
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

View file

@ -9,33 +9,25 @@ export async function get<T>(
urlPath: string, urlPath: string,
params?: { [key: string]: string } params?: { [key: string]: string }
): Promise<T | null> { ): Promise<T | null> {
try { console.log(`GET /v1/${urlPath}`);
console.log(`GET /v1/${urlPath}`);
const [session] = await Promise.all([getSession()]); const [session] = await Promise.all([getSession()]);
const headers = const headers =
session?.device_id && session?.user session?.device_id && session?.user
? await getHeaders(`GET/${urlPath}`, session) ? await getHeaders(`GET/${urlPath}`, session)
: { Authorization: "public " }; : { Authorization: "public " };
const req = await axios.request({ const req = await axios.request({
method: "GET", method: "GET",
baseURL: "https://api.tea.xyz", baseURL: "https://api.tea.xyz",
url: ["v1", ...urlPath.split("/")].filter((p) => p).join("/"), url: ["v1", ...urlPath.split("/")].filter((p) => p).join("/"),
headers, headers,
params params,
}); validateStatus: (status) => status >= 200 && status < 300
});
if (req.status == 200) { return req.data as T;
return req.data as T;
} else {
return await get<T>(urlPath, params || {});
}
} catch (error) {
console.error("ERROR", error);
return null;
}
} }
async function getHeaders(path: string, session: Session) { async function getHeaders(path: string, session: Session) {