refactor(core): standardize frontend error handling (#10667)

This commit is contained in:
forehalo
2025-03-06 13:10:18 +00:00
parent 2e86bfffae
commit e02fb4fa94
70 changed files with 495 additions and 480 deletions

View File

@@ -1,6 +1,5 @@
import type { GetWorkspacePageMetaByIdQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -9,12 +8,12 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, mergeMap } from 'rxjs';
import type { DocService } from '../../doc';
import type { GlobalCache } from '../../storage';
import { isBackendError, isNetworkError } from '../error';
import type { CloudDocMetaStore } from '../stores/cloud-doc-meta';
export type CloudDocMetaType =
@@ -47,13 +46,7 @@ export class CloudDocMeta extends Entity {
return fromPromise(
this.store.fetchCloudDocMeta(this.workspaceId, this.docId)
).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mergeMap(meta => {
this.cache.set<CloudDocMetaType>(this.cacheKey, meta);
return EMPTY;

View File

@@ -1,6 +1,5 @@
import type { InvoicesQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -9,10 +8,10 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, map, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../error';
import type { InvoicesStore } from '../stores/invoices';
export type Invoice = NonNullable<
@@ -50,13 +49,7 @@ export class Invoices extends Entity {
this.pageInvoices$.setValue(data.invoices);
return EMPTY;
}),
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
catchErrorInto(this.error$),
onStart(() => {
this.pageInvoices$.setValue(undefined);

View File

@@ -1,3 +1,4 @@
import { UserFriendlyError } from '@affine/error';
import {
backoffRetry,
effect,
@@ -12,7 +13,6 @@ import { isEqual } from 'lodash-es';
import { EMPTY, mergeMap } from 'rxjs';
import { validateAndReduceImage } from '../../../utils/reduce-image';
import { BackendError } from '../error';
import type { AccountProfile, AuthStore } from '../stores/auth';
export interface AuthSessionInfo {
@@ -114,10 +114,7 @@ export class AuthSession extends Entity {
return null;
}
} catch (e) {
if (
e instanceof BackendError &&
e.originError.name === 'UNSUPPORTED_CLIENT_VERSION'
) {
if (UserFriendlyError.fromAny(e).is('UNSUPPORTED_CLIENT_VERSION')) {
return null;
}
throw e;

View File

@@ -1,6 +1,5 @@
import type { PricesQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -9,10 +8,10 @@ import {
mapInto,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { exhaustMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../error';
import type { ServerService } from '../services/server';
import type { SubscriptionStore } from '../stores/subscription';
@@ -55,13 +54,7 @@ export class SubscriptionPrices extends Entity {
}
return this.store.fetchSubscriptionPrices(signal);
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mapInto(this.prices$),
catchErrorInto(this.error$),
onStart(() => this.isRevalidating$.next(true)),

View File

@@ -5,7 +5,6 @@ import {
SubscriptionVariant,
} from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -14,10 +13,10 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, map, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../error';
import type { AuthService } from '../services/auth';
import type { ServerService } from '../services/server';
import type { SubscriptionStore } from '../stores/subscription';
@@ -122,13 +121,7 @@ export class Subscription extends Entity {
subscriptions: subscriptions,
};
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mergeMap(data => {
if (data) {
this.store.setCachedSubscriptions(

View File

@@ -1,5 +1,4 @@
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -8,10 +7,10 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, map, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../error';
import type { AuthService } from '../services/auth';
import type { ServerService } from '../services/server';
import type { UserCopilotQuotaStore } from '../stores/user-copilot-quota';
@@ -54,13 +53,7 @@ export class UserCopilotQuota extends Entity {
return aiQuota;
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mergeMap(data => {
if (data) {
const { limit, used } = data;

View File

@@ -1,6 +1,5 @@
import { FeatureType } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -9,10 +8,10 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, map, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../error';
import type { AuthService } from '../services/auth';
import type { UserFeatureStore } from '../stores/user-feature';
@@ -59,13 +58,7 @@ export class UserFeature extends Entity {
features: features,
};
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mergeMap(data => {
if (data) {
this.features$.next(data.features);

View File

@@ -1,6 +1,5 @@
import type { QuotaQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -9,12 +8,12 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import bytes from 'bytes';
import { EMPTY, map, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../error';
import type { AuthService } from '../services/auth';
import type { UserQuotaStore } from '../stores/user-quota';
@@ -79,13 +78,7 @@ export class UserQuota extends Entity {
return { quota, used };
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mergeMap(data => {
if (data) {
const { quota, used } = data;

View File

@@ -1,6 +1,5 @@
import type { InvoicesQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -9,11 +8,11 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, map, mergeMap } from 'rxjs';
import type { WorkspaceService } from '../../workspace';
import { isBackendError, isNetworkError } from '../error';
import type { WorkspaceServerService } from '../services/workspace-server';
import { InvoicesStore } from '../stores/invoices';
@@ -61,13 +60,7 @@ export class WorkspaceInvoices extends Entity {
this.pageInvoices$.setValue(data.invoices);
return EMPTY;
}),
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
catchErrorInto(this.error$),
onStart(() => {
this.pageInvoices$.setValue(undefined);

View File

@@ -1,7 +1,6 @@
import type { SubscriptionQuery, SubscriptionRecurring } from '@affine/graphql';
import { SubscriptionPlan } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -10,11 +9,11 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, mergeMap } from 'rxjs';
import type { WorkspaceService } from '../../workspace';
import { isBackendError, isNetworkError } from '../error';
import type { WorkspaceServerService } from '../services/workspace-server';
import { SubscriptionStore } from '../stores/subscription';
export type SubscriptionType = NonNullable<
@@ -123,13 +122,7 @@ export class WorkspaceSubscription extends Entity {
subscription: subscription,
};
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mergeMap(data => {
if (data && data.subscription && data.workspaceId && this.store) {
this.store.setCachedWorkspaceSubscription(

View File

@@ -1,30 +0,0 @@
import type { UserFriendlyError } from '@affine/graphql';
export class NetworkError extends Error {
constructor(
public readonly originError: Error,
public readonly status?: number
) {
super(`Network error: ${originError.message}`);
this.stack = originError.stack;
}
}
export function isNetworkError(error: Error): error is NetworkError {
return error instanceof NetworkError;
}
export class BackendError extends Error {
get status() {
return this.originError.status;
}
constructor(public readonly originError: UserFriendlyError) {
super(`Server error: ${originError.message}`);
this.stack = originError.stack;
}
}
export function isBackendError(error: Error): error is BackendError {
return error instanceof BackendError;
}

View File

@@ -1,12 +1,6 @@
export type { Invoice } from './entities/invoices';
export { Server } from './entities/server';
export type { AuthAccountInfo } from './entities/session';
export {
BackendError,
isBackendError,
isNetworkError,
NetworkError,
} from './error';
export { AccountChanged } from './events/account-changed';
export { AccountLoggedIn } from './events/account-logged-in';
export { AccountLoggedOut } from './events/account-logged-out';

View File

@@ -1,6 +1,5 @@
import type { GetInviteInfoQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
fromPromise,
@@ -8,10 +7,10 @@ import {
onComplete,
onStart,
Service,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, exhaustMap, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../error';
import type { AcceptInviteStore } from '../stores/accept-invite';
import type { InviteInfoStore } from '../stores/invite-info';
@@ -52,14 +51,7 @@ export class AcceptInviteService extends Service {
this.accepted$.next(res);
return EMPTY;
}),
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
count: 3,
}),
smartRetry(),
catchErrorInto(this.error$),
onStart(() => {
this.inviteId$.setValue(inviteId);

View File

@@ -1,4 +1,5 @@
import { AIProvider } from '@affine/core/blocksuite/ai';
import { UserFriendlyError } from '@affine/error';
import type { OAuthProviderType } from '@affine/graphql';
import { track } from '@affine/track';
import { OnEvent, Service } from '@toeverything/infra';
@@ -7,7 +8,6 @@ import { distinctUntilChanged, map, skip } from 'rxjs';
import { ApplicationFocused } from '../../lifecycle';
import type { UrlService } from '../../url';
import { type AuthAccountInfo, AuthSession } from '../entities/session';
import { BackendError } from '../error';
import { AccountChanged } from '../events/account-changed';
import { AccountLoggedIn } from '../events/account-logged-in';
import { AccountLoggedOut } from '../events/account-logged-out';
@@ -103,7 +103,7 @@ export class AuthService extends Service {
} catch (e) {
track.$.$.auth.signInFail({
method: 'magic-link',
reason: e instanceof BackendError ? e.originError.name : 'unknown',
reason: UserFriendlyError.fromAny(e).name,
});
throw e;
}
@@ -119,7 +119,7 @@ export class AuthService extends Service {
} catch (e) {
track.$.$.auth.signInFail({
method,
reason: e instanceof BackendError ? e.originError.name : 'unknown',
reason: UserFriendlyError.fromAny(e).name,
});
throw e;
}
@@ -159,7 +159,7 @@ export class AuthService extends Service {
track.$.$.auth.signInFail({
method: 'oauth',
provider,
reason: e instanceof BackendError ? e.originError.name : 'unknown',
reason: UserFriendlyError.fromAny(e).name,
});
throw e;
}
@@ -181,7 +181,7 @@ export class AuthService extends Service {
track.$.$.auth.signInFail({
method: 'oauth',
provider,
reason: e instanceof BackendError ? e.originError.name : 'unknown',
reason: UserFriendlyError.fromAny(e).name,
});
throw e;
}
@@ -201,7 +201,7 @@ export class AuthService extends Service {
} catch (e) {
track.$.$.auth.signInFail({
method: 'password',
reason: e instanceof BackendError ? e.originError.name : 'unknown',
reason: UserFriendlyError.fromAny(e).name,
});
throw e;
}

View File

@@ -1,8 +1,7 @@
import { DebugLogger } from '@affine/debug';
import { UserFriendlyError } from '@affine/graphql';
import { UserFriendlyError } from '@affine/error';
import { fromPromise, Service } from '@toeverything/infra';
import { BackendError, NetworkError } from '../error';
import type { ServerService } from './server';
const logger = new DebugLogger('affine:fetch');
@@ -46,40 +45,54 @@ export class FetchService extends Service {
abortController.abort('timeout');
}, timeout);
const res = await globalThis
.fetch(new URL(input, this.serverService.server.serverMetadata.baseUrl), {
...init,
signal: abortController.signal,
headers: {
...init?.headers,
'x-affine-version': BUILD_CONFIG.appVersion,
},
})
.catch(err => {
logger.debug('network error', err);
throw new NetworkError(err);
});
clearTimeout(timeoutId);
if (res.status === 504) {
const error = new Error('Gateway Timeout');
logger.debug('network error', error);
throw new NetworkError(error, res.status);
}
if (!res.ok) {
logger.warn(
'backend error',
new Error(`${res.status} ${res.statusText}`)
let res: Response;
try {
res = await globalThis.fetch(
new URL(input, this.serverService.server.serverMetadata.baseUrl),
{
...init,
signal: abortController.signal,
headers: {
...init?.headers,
'x-affine-version': BUILD_CONFIG.appVersion,
},
}
);
let reason: string | any = '';
if (res.headers.get('Content-Type')?.includes('application/json')) {
try {
reason = await res.json();
} catch {
// ignore
} catch (err: any) {
throw new UserFriendlyError({
status: 504,
code: 'NETWORK_ERROR',
type: 'NETWORK_ERROR',
name: 'NETWORK_ERROR',
message: `Network error: ${err.message}`,
stacktrace: err.stack,
});
} finally {
clearTimeout(timeoutId);
}
if (!res.ok) {
if (res.status === 504) {
const error = new Error('Gateway Timeout');
logger.debug('network error', error);
throw new UserFriendlyError({
status: 504,
code: 'NETWORK_ERROR',
type: 'NETWORK_ERROR',
name: 'NETWORK_ERROR',
message: 'Gateway Timeout',
stacktrace: error.stack,
});
} else {
if (res.headers.get('Content-Type') === 'application/json') {
throw UserFriendlyError.fromAny(await res.json());
} else {
throw UserFriendlyError.fromAny(await res.text());
}
}
throw new BackendError(UserFriendlyError.fromAnyError(reason));
}
return res;
};
}

View File

@@ -1,15 +1,13 @@
import { UserFriendlyError } from '@affine/error';
import {
gqlFetcherFactory,
GraphQLError,
type GraphQLQuery,
type QueryOptions,
type QueryResponse,
UserFriendlyError,
} from '@affine/graphql';
import { fromPromise, Service } from '@toeverything/infra';
import type { Observable } from 'rxjs';
import { BackendError } from '../error';
import { AuthService } from './auth';
import type { FetchService } from './fetch';
@@ -40,16 +38,9 @@ export class GraphQLService extends Service {
try {
return await this.rawGql(options);
} catch (anyError) {
let error = anyError;
const error = UserFriendlyError.fromAny(anyError);
// NOTE(@forehalo):
// GraphQL error is not present by non-200 status code, but by responding `errors` fields in the body
// So it will never be `BackendError` originally.
if (anyError instanceof GraphQLError) {
error = new BackendError(UserFriendlyError.fromAnyError(anyError));
}
if (error instanceof BackendError && error.status === 403) {
if (error.isStatus(401)) {
this.framework.get(AuthService).session.revalidate();
}

View File

@@ -1,16 +1,16 @@
import { UserFriendlyError } from '@affine/graphql';
import { type UserFriendlyError } from '@affine/error';
import {
backoffRetry,
catchErrorInto,
effect,
fromPromise,
LiveData,
onComplete,
onStart,
Service,
smartRetry,
} from '@toeverything/infra';
import { catchError, EMPTY, exhaustMap, mergeMap } from 'rxjs';
import { EMPTY, exhaustMap, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../error';
import type { SelfhostGenerateLicenseStore } from '../stores/selfhost-generate-license';
export class SelfhostGenerateLicenseService extends Service {
@@ -26,22 +26,12 @@ export class SelfhostGenerateLicenseService extends Service {
return fromPromise(async () => {
return await this.store.generateKey(sessionId);
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mergeMap(key => {
this.licenseKey$.next(key);
return EMPTY;
}),
catchError(err => {
this.error$.next(UserFriendlyError.fromAnyError(err));
console.error(err);
return EMPTY;
}),
catchErrorInto(this.error$),
onStart(() => {
this.isLoading$.next(true);
}),

View File

@@ -1,6 +1,5 @@
import type { License } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
exhaustMapWithTrailing,
@@ -9,11 +8,11 @@ import {
onComplete,
onStart,
Service,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, mergeMap } from 'rxjs';
import type { WorkspaceService } from '../../workspace';
import { isBackendError, isNetworkError } from '../error';
import type { SelfhostLicenseStore } from '../stores/selfhost-license';
export class SelfhostLicenseService extends Service {
@@ -36,13 +35,7 @@ export class SelfhostLicenseService extends Service {
}
return await this.store.getLicense(currentWorkspaceId, signal);
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mergeMap(data => {
if (data) {
this.license$.next(data);

View File

@@ -1,5 +1,4 @@
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -7,10 +6,10 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, mergeMap, switchMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../../cloud';
import type { TemplateDownloaderStore } from '../store/downloader';
export class TemplateDownloader extends Entity {
@@ -29,13 +28,7 @@ export class TemplateDownloader extends Entity {
this.data$.next(data);
return EMPTY;
}),
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
catchErrorInto(this.error$),
onStart(() => {
this.isDownloading$.next(true);

View File

@@ -1,6 +1,5 @@
import type { GetMembersByWorkspaceIdQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -8,10 +7,10 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, map, mergeMap, switchMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../../cloud';
import type { WorkspaceService } from '../../workspace';
import type { WorkspaceMembersStore } from '../stores/members';
@@ -51,13 +50,7 @@ export class WorkspaceMembers extends Entity {
this.pageMembers$.setValue(data.members);
return EMPTY;
}),
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
catchErrorInto(this.error$),
onStart(() => {
this.pageMembers$.setValue(undefined);

View File

@@ -1,6 +1,5 @@
import { DocRole, type GetPageGrantedUsersListQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
fromPromise,
@@ -8,10 +7,10 @@ import {
onComplete,
onStart,
Service,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, exhaustMap, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../../cloud';
import type { DocService } from '../../doc';
import type { WorkspaceService } from '../../workspace';
import type { DocGrantedUsersStore } from '../stores/doc-granted-users';
@@ -65,13 +64,7 @@ export class DocGrantedUsersService extends Service {
return EMPTY;
}),
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
catchErrorInto(this.error$),
onStart(() => {
this.isLoading$.setValue(true);

View File

@@ -1,5 +1,4 @@
import {
backoffRetry,
catchErrorInto,
effect,
fromPromise,
@@ -7,10 +6,10 @@ import {
onComplete,
onStart,
Service,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, exhaustMap, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../../cloud';
import type { WorkspaceService } from '../../workspace';
import type { Member } from '../entities/members';
import type { MemberSearchStore } from '../stores/member-search';
@@ -50,13 +49,7 @@ export class MemberSearchService extends Service {
return EMPTY;
}),
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
catchErrorInto(this.error$),
onStart(() => {
this.isLoading$.setValue(true);

View File

@@ -1,7 +1,6 @@
import { DebugLogger } from '@affine/debug';
import type { WorkspaceQuotaQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -10,12 +9,12 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { cssVarV2 } from '@toeverything/theme/v2';
import bytes from 'bytes';
import { EMPTY, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../../cloud';
import type { WorkspaceService } from '../../workspace';
import type { WorkspaceQuotaStore } from '../stores/quota';
@@ -76,14 +75,7 @@ export class WorkspaceQuota extends Entity {
);
return { quota: data, used: data.usedStorageQuota };
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
count: 3,
}),
smartRetry(),
mergeMap(data => {
if (data) {
const { quota, used } = data;

View File

@@ -1,7 +1,6 @@
import { DebugLogger } from '@affine/debug';
import type { GetWorkspacePublicPagesQuery } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -10,10 +9,10 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../../cloud';
import type { GlobalCache } from '../../storage';
import type { WorkspaceService } from '../../workspace';
import type { ShareDocsStore } from '../stores/share-docs';
@@ -43,13 +42,7 @@ export class ShareDocsList extends Entity {
signal
);
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mergeMap(list => {
this.cache.set('share-docs', list);
return EMPTY;

View File

@@ -1,6 +1,5 @@
import type { GetWorkspacePageByIdQuery, PublicDocMode } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -9,10 +8,10 @@ import {
mapInto,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { switchMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../../cloud';
import type { DocService } from '../../doc';
import type { WorkspaceService } from '../../workspace';
import type { ShareStore } from '../stores/share';
@@ -44,13 +43,7 @@ export class ShareInfo extends Entity {
signal
)
).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
smartRetry(),
mapInto(this.info$),
catchErrorInto(this.error$),
onStart(() => this.isRevalidating$.next(true)),

View File

@@ -8,7 +8,8 @@ import {
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { DocGrantedUsersService } from '@affine/core/modules/permissions';
import { ShareInfoService } from '@affine/core/modules/share-doc';
import { DocRole, UserFriendlyError } from '@affine/graphql';
import { UserFriendlyError } from '@affine/error';
import { DocRole } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { InformationIcon } from '@blocksuite/icons/rc';
@@ -62,7 +63,7 @@ export const MembersPermission = ({
await docGrantedUsersService.updateDocDefaultRole(docRole);
shareInfoService.shareInfo.revalidate();
} catch (error) {
const err = UserFriendlyError.fromAnyError(error);
const err = UserFriendlyError.fromAny(error);
notify.error({
title: err.name,
message: err.message,

View File

@@ -1,7 +1,8 @@
import { Menu, MenuItem, MenuTrigger, notify } from '@affine/component';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { ShareInfoService } from '@affine/core/modules/share-doc';
import { PublicDocMode, UserFriendlyError } from '@affine/graphql';
import { UserFriendlyError } from '@affine/error';
import { PublicDocMode } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import {
@@ -74,7 +75,7 @@ export const PublicDoc = ({ disabled }: { disabled?: boolean }) => {
icon: <SingleSelectCheckSolidIcon color={cssVar('primaryColor')} />,
});
} catch (error) {
const err = UserFriendlyError.fromAnyError(error);
const err = UserFriendlyError.fromAny(error);
notify.error({
title: err.name,
message: err.message,

View File

@@ -14,11 +14,8 @@ import {
type Member,
MemberSearchService,
} from '@affine/core/modules/permissions';
import {
DocRole,
UserFriendlyError,
WorkspaceMemberStatus,
} from '@affine/graphql';
import { UserFriendlyError } from '@affine/error';
import { DocRole, WorkspaceMemberStatus } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { ArrowLeftBigIcon } from '@blocksuite/icons/rc';
@@ -113,7 +110,7 @@ export const InviteMemberEditor = ({
title: t['Invitation sent'](),
});
} catch (error) {
const err = UserFriendlyError.fromAnyError(error);
const err = UserFriendlyError.fromAny(error);
notify.error({
title: t[`error.${err.name}`](err.data),
});

View File

@@ -16,7 +16,8 @@ import {
GuardService,
WorkspacePermissionService,
} from '@affine/core/modules/permissions';
import { DocRole, UserFriendlyError } from '@affine/graphql';
import { UserFriendlyError } from '@affine/error';
import { DocRole } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import { useLiveData, useService } from '@toeverything/infra';
@@ -161,7 +162,7 @@ const Options = ({
});
}
} catch (error) {
const err = UserFriendlyError.fromAnyError(error);
const err = UserFriendlyError.fromAny(error);
notify.error({
title: t[`error.${err.name}`](err.data),
});
@@ -219,7 +220,7 @@ const Options = ({
await docGrantedUsersService.revokeUsersRole(userId);
docGrantedUsersService.loadMore();
} catch (error) {
const err = UserFriendlyError.fromAnyError(error);
const err = UserFriendlyError.fromAny(error);
notify.error({
title: t[`error.${err.name}`](err.data),
});

View File

@@ -1,7 +1,6 @@
import { DebugLogger } from '@affine/debug';
import type { GetWorkspaceConfigQuery, InviteLink } from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
@@ -9,10 +8,10 @@ import {
LiveData,
onComplete,
onStart,
smartRetry,
} from '@toeverything/infra';
import { EMPTY, exhaustMap, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../../cloud';
import type { WorkspaceService } from '../../workspace';
import type { WorkspaceShareSettingStore } from '../stores/share-setting';
@@ -45,14 +44,7 @@ export class WorkspaceShareSetting extends Entity {
signal
)
).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
count: 3,
}),
smartRetry(),
mergeMap(value => {
if (value) {
this.enableAi$.next(value.enableAi);