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:
EYHN
2024-11-27 06:44:46 +00:00
parent 3f4cb5be40
commit 6b4a1aa917
80 changed files with 1141 additions and 519 deletions
@@ -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 =>
@@ -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;
@@ -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;
@@ -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,
@@ -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;
@@ -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 (
<>