mirror of
https://github.com/ivabus/gui
synced 2025-04-23 14:07:14 +03:00
retries (#355)
This commit is contained in:
parent
b412ab8a2a
commit
dba09e5d34
5 changed files with 79 additions and 61 deletions
4
.github/workflows/build-sign-notarize.yml
vendored
4
.github/workflows/build-sign-notarize.yml
vendored
|
@ -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
|
||||||
|
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -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
|
||||||
|
|
|
@ -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> => {
|
||||||
|
|
34
modules/desktop/src/libs/utils/retry.ts
Normal file
34
modules/desktop/src/libs/utils/retry.ts
Normal 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));
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue