mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-22 20:46:38 +08:00
refactor(core): initial multiple servers infra (#8745)
This is the initial refactoring of affine to support multiple servers, but many more changes are needed to make multi-server actually work.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Skeleton } from '@affine/component';
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { ServerService } from '@affine/core/modules/cloud';
|
||||
import { UrlService } from '@affine/core/modules/url';
|
||||
import { OAuthProviderType } from '@affine/graphql';
|
||||
import track from '@affine/track';
|
||||
@@ -7,8 +8,6 @@ import { GithubIcon, GoogleDuotoneIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { type ReactElement, useCallback } from 'react';
|
||||
|
||||
import { ServerConfigService } from '../../../modules/cloud';
|
||||
|
||||
const OAuthProviderMap: Record<
|
||||
OAuthProviderType,
|
||||
{
|
||||
@@ -30,11 +29,11 @@ const OAuthProviderMap: Record<
|
||||
};
|
||||
|
||||
export function OAuth({ redirectUrl }: { redirectUrl?: string }) {
|
||||
const serverConfig = useService(ServerConfigService).serverConfig;
|
||||
const serverService = useService(ServerService);
|
||||
const urlService = useService(UrlService);
|
||||
const oauth = useLiveData(serverConfig.features$.map(r => r?.oauth));
|
||||
const oauth = useLiveData(serverService.server.features$.map(r => r?.oauth));
|
||||
const oauthProviders = useLiveData(
|
||||
serverConfig.config$.map(r => r?.oauthProviders)
|
||||
serverService.server.config$.map(r => r?.oauthProviders)
|
||||
);
|
||||
const scheme = urlService.getClientScheme();
|
||||
|
||||
@@ -66,6 +65,7 @@ function OAuthProvider({
|
||||
scheme?: string;
|
||||
popupWindow: (url: string) => void;
|
||||
}) {
|
||||
const serverService = useService(ServerService);
|
||||
const { icon } = OAuthProviderMap[provider];
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
@@ -85,12 +85,12 @@ function OAuthProvider({
|
||||
// if (BUILD_CONFIG.isAndroid) {}
|
||||
|
||||
const oauthUrl =
|
||||
BUILD_CONFIG.serverUrlPrefix + `/oauth/login?${params.toString()}`;
|
||||
serverService.server.baseUrl + `/oauth/login?${params.toString()}`;
|
||||
|
||||
track.$.$.auth.signIn({ method: 'oauth', provider });
|
||||
|
||||
popupWindow(oauthUrl);
|
||||
}, [popupWindow, provider, redirectUrl, scheme]);
|
||||
}, [popupWindow, provider, redirectUrl, scheme, serverService]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
||||
@@ -18,7 +18,7 @@ import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useMutation } from '../../../components/hooks/use-mutation';
|
||||
import { ServerConfigService } from '../../../modules/cloud';
|
||||
import { ServerService } from '../../../modules/cloud';
|
||||
import type { AuthPanelProps } from './index';
|
||||
|
||||
const useEmailTitle = (emailType: AuthPanelProps<'sendEmail'>['emailType']) => {
|
||||
@@ -142,10 +142,10 @@ export const SendEmail = ({
|
||||
// todo(@pengx17): impl redirectUrl for sendEmail?
|
||||
}: AuthPanelProps<'sendEmail'>) => {
|
||||
const t = useI18n();
|
||||
const serverConfig = useService(ServerConfigService).serverConfig;
|
||||
const serverService = useService(ServerService);
|
||||
|
||||
const passwordLimits = useLiveData(
|
||||
serverConfig.credentialsRequirement$.map(r => r?.password)
|
||||
serverService.server.credentialsRequirement$.map(r => r?.password)
|
||||
);
|
||||
const [hasSentEmail, setHasSentEmail] = useState(false);
|
||||
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { Tooltip } from '@affine/component/ui/tooltip';
|
||||
import { SubscriptionPlan } from '@affine/graphql';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useServices } from '@toeverything/infra';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { type SyntheticEvent, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
ServerConfigService,
|
||||
SubscriptionService,
|
||||
} from '../../../modules/cloud';
|
||||
import { ServerService, SubscriptionService } from '../../../modules/cloud';
|
||||
import * as styles from './style.css';
|
||||
|
||||
export const UserPlanButton = ({
|
||||
@@ -15,13 +12,11 @@ export const UserPlanButton = ({
|
||||
}: {
|
||||
onClick: (e: SyntheticEvent<Element, Event>) => void;
|
||||
}) => {
|
||||
const { serverConfigService, subscriptionService } = useServices({
|
||||
ServerConfigService,
|
||||
SubscriptionService,
|
||||
});
|
||||
const serverService = useService(ServerService);
|
||||
const subscriptionService = useService(SubscriptionService);
|
||||
|
||||
const hasPayment = useLiveData(
|
||||
serverConfigService.serverConfig.features$.map(r => r?.payment)
|
||||
serverService.server.features$.map(r => r?.payment)
|
||||
);
|
||||
const plan = useLiveData(
|
||||
subscriptionService.subscription.pro$.map(subscription =>
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@ import {
|
||||
useSharingUrl,
|
||||
} from '@affine/core/components/hooks/affine/use-share-url';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { ServerConfigService } from '@affine/core/modules/cloud';
|
||||
import { ServerService } from '@affine/core/modules/cloud';
|
||||
import { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
@@ -70,13 +70,13 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
|
||||
const currentMode = useLiveData(editor.mode$);
|
||||
const editorContainer = useLiveData(editor.editorContainer$);
|
||||
const shareInfoService = useService(ShareInfoService);
|
||||
const serverConfig = useService(ServerConfigService).serverConfig;
|
||||
const serverService = useService(ServerService);
|
||||
useEffect(() => {
|
||||
shareInfoService.shareInfo.revalidate();
|
||||
}, [shareInfoService]);
|
||||
const isSharedPage = useLiveData(shareInfoService.shareInfo.isShared$);
|
||||
const sharedMode = useLiveData(shareInfoService.shareInfo.sharedMode$);
|
||||
const baseUrl = useLiveData(serverConfig.config$.map(c => c?.baseUrl));
|
||||
const baseUrl = serverService.server.baseUrl;
|
||||
const isLoading =
|
||||
isSharedPage === null || sharedMode === null || baseUrl === null;
|
||||
|
||||
|
||||
+21
-2
@@ -3,12 +3,11 @@ import {
|
||||
cleanupCopilotSessionMutation,
|
||||
createCopilotMessageMutation,
|
||||
createCopilotSessionMutation,
|
||||
fetcher as defaultFetcher,
|
||||
forkCopilotSessionMutation,
|
||||
getBaseUrl,
|
||||
getCopilotHistoriesQuery,
|
||||
getCopilotHistoryIdsQuery,
|
||||
getCopilotSessionsQuery,
|
||||
gqlFetcherFactory,
|
||||
GraphQLError,
|
||||
type GraphQLQuery,
|
||||
type QueryOptions,
|
||||
@@ -22,6 +21,26 @@ import {
|
||||
} from '@blocksuite/affine/blocks';
|
||||
import { getCurrentStore } from '@toeverything/infra';
|
||||
|
||||
/**
|
||||
* @deprecated will be removed soon
|
||||
*/
|
||||
export function getBaseUrl(): string {
|
||||
if (BUILD_CONFIG.isElectron || BUILD_CONFIG.isIOS || BUILD_CONFIG.isAndroid) {
|
||||
return BUILD_CONFIG.serverUrlPrefix;
|
||||
}
|
||||
if (typeof window === 'undefined') {
|
||||
// is nodejs
|
||||
return '';
|
||||
}
|
||||
const { protocol, hostname, port } = window.location;
|
||||
return `${protocol}//${hostname}${port ? `:${port}` : ''}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated will be removed soon
|
||||
*/
|
||||
const defaultFetcher = gqlFetcherFactory(getBaseUrl() + '/graphql');
|
||||
|
||||
type OptionsField<T extends GraphQLQuery> =
|
||||
RequestOptions<T>['variables'] extends { options: infer U } ? U : never;
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,6 @@ import { AIProvider } from '@affine/core/blocksuite/presets/ai';
|
||||
import { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onboarding/apis';
|
||||
import { authAtom } from '@affine/core/components/atoms';
|
||||
import {
|
||||
getBaseUrl,
|
||||
type getCopilotHistoriesQuery,
|
||||
type RequestOptions,
|
||||
} from '@affine/graphql';
|
||||
@@ -11,6 +10,7 @@ import { assertExists } from '@blocksuite/affine/global/utils';
|
||||
import { getCurrentStore } from '@toeverything/infra';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getBaseUrl } from './copilot-client';
|
||||
import type { PromptKey } from './prompt';
|
||||
import {
|
||||
cleanupSessions,
|
||||
|
||||
+6
-5
@@ -3,7 +3,7 @@ import {
|
||||
generateUrl,
|
||||
type UseSharingUrl,
|
||||
} from '@affine/core/components/hooks/affine/use-share-url';
|
||||
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
|
||||
import { ServerService } from '@affine/core/modules/cloud';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { copyLinkToBlockStdScopeClipboard } from '@affine/core/utils/clipboard';
|
||||
import { I18n } from '@affine/i18n';
|
||||
@@ -41,9 +41,7 @@ function createCopyLinkToBlockMenuItem(
|
||||
return mode === 'edgeless';
|
||||
},
|
||||
select: () => {
|
||||
const baseUrl = getAffineCloudBaseUrl();
|
||||
if (!baseUrl) return;
|
||||
|
||||
const serverService = framework.get(ServerService);
|
||||
const pageId = model.doc.id;
|
||||
const { editor } = framework.get(EditorService);
|
||||
const mode = editor.mode$.value;
|
||||
@@ -58,7 +56,10 @@ function createCopyLinkToBlockMenuItem(
|
||||
blockIds: [model.id],
|
||||
};
|
||||
|
||||
const str = generateUrl(options);
|
||||
const str = generateUrl({
|
||||
...options,
|
||||
baseUrl: serverService.server.baseUrl,
|
||||
});
|
||||
if (!str) return;
|
||||
|
||||
const type = model.flavour;
|
||||
|
||||
+6
-7
@@ -3,7 +3,7 @@ import {
|
||||
generateUrl,
|
||||
type UseSharingUrl,
|
||||
} from '@affine/core/components/hooks/affine/use-share-url';
|
||||
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
|
||||
import { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { copyLinkToBlockStdScopeClipboard } from '@affine/core/utils/clipboard';
|
||||
import { I18n } from '@affine/i18n';
|
||||
@@ -79,11 +79,7 @@ function createCopyLinkToBlockMenuItem(
|
||||
return {
|
||||
...item,
|
||||
action: async (ctx: MenuContext) => {
|
||||
const baseUrl = getAffineCloudBaseUrl();
|
||||
if (!baseUrl) {
|
||||
ctx.close();
|
||||
return;
|
||||
}
|
||||
const workspaceServerService = framework.get(WorkspaceServerService);
|
||||
|
||||
const { editor } = framework.get(EditorService);
|
||||
const mode = editor.mode$.value;
|
||||
@@ -109,7 +105,10 @@ function createCopyLinkToBlockMenuItem(
|
||||
}
|
||||
}
|
||||
|
||||
const str = generateUrl(options);
|
||||
const str = generateUrl({
|
||||
...options,
|
||||
baseUrl: workspaceServerService.server?.baseUrl ?? location.origin,
|
||||
});
|
||||
if (!str) {
|
||||
ctx.close();
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { notify } from '@affine/component';
|
||||
import { getAffineCloudBaseUrl } from '@affine/core/modules/cloud/services/fetch';
|
||||
import { ServerService } from '@affine/core/modules/cloud';
|
||||
import { toURLSearchParams } from '@affine/core/modules/navigation';
|
||||
import { copyTextToClipboard } from '@affine/core/utils/clipboard';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
@@ -7,6 +7,7 @@ import { track } from '@affine/track';
|
||||
import { type EditorHost } from '@blocksuite/affine/block-std';
|
||||
import { GfxBlockElementModel } from '@blocksuite/affine/block-std/gfx';
|
||||
import type { DocMode, EdgelessRootService } from '@blocksuite/affine/blocks';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export type UseSharingUrl = {
|
||||
@@ -24,17 +25,14 @@ export type UseSharingUrl = {
|
||||
* https://app.affine.pro/workspace/workspaceId/docId?mode=DocMode&elementIds=seletedElementIds&blockIds=selectedBlockIds
|
||||
*/
|
||||
export const generateUrl = ({
|
||||
baseUrl,
|
||||
workspaceId,
|
||||
pageId,
|
||||
blockIds,
|
||||
elementIds,
|
||||
shareMode: mode,
|
||||
xywh, // not needed currently
|
||||
}: UseSharingUrl) => {
|
||||
// Base URL construction
|
||||
const baseUrl = getAffineCloudBaseUrl();
|
||||
if (!baseUrl) return null;
|
||||
|
||||
}: UseSharingUrl & { baseUrl: string }) => {
|
||||
try {
|
||||
const url = new URL(`/workspace/${workspaceId}/${pageId}`, baseUrl);
|
||||
const search = toURLSearchParams({ mode, blockIds, elementIds, xywh });
|
||||
@@ -130,10 +128,12 @@ export const getSelectedNodes = (
|
||||
|
||||
export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
|
||||
const t = useI18n();
|
||||
const serverService = useService(ServerService);
|
||||
|
||||
const onClickCopyLink = useCallback(
|
||||
(shareMode?: DocMode, blockIds?: string[], elementIds?: string[]) => {
|
||||
const sharingUrl = generateUrl({
|
||||
baseUrl: serverService.server.baseUrl,
|
||||
workspaceId,
|
||||
pageId,
|
||||
blockIds,
|
||||
@@ -160,7 +160,7 @@ export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
|
||||
notify.error({ title: 'Network not available' });
|
||||
}
|
||||
},
|
||||
[pageId, t, workspaceId]
|
||||
[pageId, serverService, t, workspaceId]
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { GraphQLService } from '@affine/core/modules/cloud';
|
||||
import type {
|
||||
GraphQLQuery,
|
||||
MutationOptions,
|
||||
@@ -5,7 +6,7 @@ import type {
|
||||
QueryVariables,
|
||||
RecursiveMaybeFields,
|
||||
} from '@affine/graphql';
|
||||
import { fetcher } from '@affine/graphql';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import type { GraphQLError } from 'graphql';
|
||||
import { useMemo } from 'react';
|
||||
import type { Key } from 'swr';
|
||||
@@ -51,10 +52,15 @@ export function useMutation(
|
||||
options: Omit<MutationOptions<GraphQLQuery>, 'variables'>,
|
||||
config?: any
|
||||
) {
|
||||
const graphqlService = useService(GraphQLService);
|
||||
return useSWRMutation(
|
||||
() => ['cloud', options.mutation.id],
|
||||
(_: unknown[], { arg }: { arg: any }) =>
|
||||
fetcher({ ...options, query: options.mutation, variables: arg }),
|
||||
graphqlService.gql({
|
||||
...options,
|
||||
query: options.mutation,
|
||||
variables: arg,
|
||||
}),
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { GraphQLService } from '@affine/core/modules/cloud';
|
||||
import type {
|
||||
GraphQLQuery,
|
||||
QueryOptions,
|
||||
QueryResponse,
|
||||
} from '@affine/graphql';
|
||||
import { fetcher } from '@affine/graphql';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import type { GraphQLError } from 'graphql';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { SWRConfiguration, SWRResponse } from 'swr';
|
||||
@@ -32,7 +33,11 @@ import useSWRInfinite from 'swr/infinite';
|
||||
type useQueryFn = <Query extends GraphQLQuery>(
|
||||
options?: QueryOptions<Query>,
|
||||
config?: Omit<
|
||||
SWRConfiguration<QueryResponse<Query>, GraphQLError, typeof fetcher<Query>>,
|
||||
SWRConfiguration<
|
||||
QueryResponse<Query>,
|
||||
GraphQLError,
|
||||
(options: QueryOptions<Query>) => Promise<QueryResponse<Query>>
|
||||
>,
|
||||
'fetcher'
|
||||
>
|
||||
) => SWRResponse<
|
||||
@@ -53,11 +58,12 @@ const createUseQuery =
|
||||
}),
|
||||
[config]
|
||||
);
|
||||
const graphqlService = useService(GraphQLService);
|
||||
|
||||
const useSWRFn = immutable ? useSWRImutable : useSWR;
|
||||
return useSWRFn(
|
||||
options ? () => ['cloud', options.query.id, options.variables] : null,
|
||||
options ? () => fetcher(options) : null,
|
||||
options ? () => graphqlService.gql(options) : null,
|
||||
configWithSuspense
|
||||
);
|
||||
};
|
||||
@@ -76,7 +82,7 @@ export function useQueryInfinite<Query extends GraphQLQuery>(
|
||||
SWRConfiguration<
|
||||
QueryResponse<Query>,
|
||||
GraphQLError | GraphQLError[],
|
||||
typeof fetcher<Query>
|
||||
(options: QueryOptions<Query>) => Promise<QueryResponse<Query>>
|
||||
>,
|
||||
'fetcher'
|
||||
>
|
||||
@@ -88,6 +94,7 @@ export function useQueryInfinite<Query extends GraphQLQuery>(
|
||||
}),
|
||||
[config]
|
||||
);
|
||||
const graphqlService = useService(GraphQLService);
|
||||
|
||||
const { data, setSize, size, error } = useSWRInfinite<
|
||||
QueryResponse<Query>,
|
||||
@@ -100,7 +107,7 @@ export function useQueryInfinite<Query extends GraphQLQuery>(
|
||||
],
|
||||
async ([_, __, variables]) => {
|
||||
const params = { ...options, variables } as QueryOptions<Query>;
|
||||
return fetcher(params);
|
||||
return graphqlService.gql(params);
|
||||
},
|
||||
configWithSuspense
|
||||
);
|
||||
|
||||
@@ -23,7 +23,7 @@ import { useCallback, useEffect } from 'react';
|
||||
import {
|
||||
type AuthAccountInfo,
|
||||
AuthService,
|
||||
ServerConfigService,
|
||||
ServerService,
|
||||
SubscriptionService,
|
||||
UserCopilotQuotaService,
|
||||
UserQuotaService,
|
||||
@@ -276,10 +276,8 @@ const AIUsage = () => {
|
||||
};
|
||||
|
||||
const OperationMenu = () => {
|
||||
const serverConfigService = useService(ServerConfigService);
|
||||
const serverFeatures = useLiveData(
|
||||
serverConfigService.serverConfig.features$
|
||||
);
|
||||
const serverService = useService(ServerService);
|
||||
const serverFeatures = useLiveData(serverService.server.features$);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user