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:
@@ -10,6 +10,7 @@ import type {
|
|||||||
DocStorage,
|
DocStorage,
|
||||||
} from '../../../sync';
|
} from '../../../sync';
|
||||||
import type { WorkspaceProfileInfo } from '../entities/profile';
|
import type { WorkspaceProfileInfo } from '../entities/profile';
|
||||||
|
import type { Workspace } from '../entities/workspace';
|
||||||
import type { WorkspaceMetadata } from '../metadata';
|
import type { WorkspaceMetadata } from '../metadata';
|
||||||
|
|
||||||
export interface WorkspaceEngineProvider {
|
export interface WorkspaceEngineProvider {
|
||||||
@@ -55,6 +56,8 @@ export interface WorkspaceFlavourProvider {
|
|||||||
getWorkspaceBlob(id: string, blob: string): Promise<Blob | null>;
|
getWorkspaceBlob(id: string, blob: string): Promise<Blob | null>;
|
||||||
|
|
||||||
getEngineProvider(workspaceId: string): WorkspaceEngineProvider;
|
getEngineProvider(workspaceId: string): WorkspaceEngineProvider;
|
||||||
|
|
||||||
|
onWorkspaceInitialized?(workspace: Workspace): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceFlavourProvider =
|
export const WorkspaceFlavourProvider =
|
||||||
|
|||||||
@@ -83,11 +83,12 @@ export class WorkspaceRepositoryService extends Service {
|
|||||||
logger.info(
|
logger.info(
|
||||||
`open workspace [${openOptions.metadata.flavour}] ${openOptions.metadata.id} `
|
`open workspace [${openOptions.metadata.flavour}] ${openOptions.metadata.id} `
|
||||||
);
|
);
|
||||||
|
const flavourProvider = this.providers.find(
|
||||||
|
p => p.flavour === openOptions.metadata.flavour
|
||||||
|
);
|
||||||
const provider =
|
const provider =
|
||||||
customProvider ??
|
customProvider ??
|
||||||
this.providers
|
flavourProvider?.getEngineProvider(openOptions.metadata.id);
|
||||||
.find(p => p.flavour === openOptions.metadata.flavour)
|
|
||||||
?.getEngineProvider(openOptions.metadata.id);
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown workspace flavour: ${openOptions.metadata.flavour}`
|
`Unknown workspace flavour: ${openOptions.metadata.flavour}`
|
||||||
@@ -106,6 +107,8 @@ export class WorkspaceRepositoryService extends Service {
|
|||||||
|
|
||||||
this.framework.emitEvent(WorkspaceInitialized, workspace);
|
this.framework.emitEvent(WorkspaceInitialized, workspace);
|
||||||
|
|
||||||
|
flavourProvider?.onWorkspaceInitialized?.(workspace);
|
||||||
|
|
||||||
this.profileRepo
|
this.profileRepo
|
||||||
.getProfile(openOptions.metadata)
|
.getProfile(openOptions.metadata)
|
||||||
.syncWithWorkspace(workspace);
|
.syncWithWorkspace(workspace);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* for support arraybuffer response type
|
* for support arraybuffer response type
|
||||||
*/
|
*/
|
||||||
import { FetchProvider } from '@affine/core/modules/cloud/provider/fetch';
|
import { RawFetchProvider } from '@affine/core/modules/cloud/provider/fetch';
|
||||||
import { CapacitorHttp } from '@capacitor/core';
|
import { CapacitorHttp } from '@capacitor/core';
|
||||||
import type { Framework } from '@toeverything/infra';
|
import type { Framework } from '@toeverything/infra';
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ function base64ToUint8Array(base64: string) {
|
|||||||
return new Uint8Array(binaryArray);
|
return new Uint8Array(binaryArray);
|
||||||
}
|
}
|
||||||
export function configureFetchProvider(framework: Framework) {
|
export function configureFetchProvider(framework: Framework) {
|
||||||
framework.override(FetchProvider, {
|
framework.override(RawFetchProvider, {
|
||||||
fetch: async (input, init) => {
|
fetch: async (input, init) => {
|
||||||
const request = new Request(input, init);
|
const request = new Request(input, init);
|
||||||
const { method } = request;
|
const { method } = request;
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { useI18n } from '@affine/i18n';
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import { Button } from '../../ui/button';
|
|
||||||
import { AuthPageContainer } from './auth-page-container';
|
|
||||||
|
|
||||||
export const ConfirmChangeEmail: FC<{
|
|
||||||
onOpenAffine: () => void;
|
|
||||||
}> = ({ onOpenAffine }) => {
|
|
||||||
const t = useI18n();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthPageContainer
|
|
||||||
title={t['com.affine.auth.change.email.page.success.title']()}
|
|
||||||
subtitle={t['com.affine.auth.change.email.page.success.subtitle']()}
|
|
||||||
>
|
|
||||||
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
|
||||||
{t['com.affine.auth.open.affine']()}
|
|
||||||
</Button>
|
|
||||||
</AuthPageContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { useI18n } from '@affine/i18n';
|
|
||||||
import type { FC } from 'react';
|
|
||||||
|
|
||||||
import { Button } from '../../ui/button';
|
|
||||||
import { AuthPageContainer } from './auth-page-container';
|
|
||||||
|
|
||||||
export const ConfirmVerifiedEmail: FC<{
|
|
||||||
onOpenAffine: () => void;
|
|
||||||
}> = ({ onOpenAffine }) => {
|
|
||||||
const t = useI18n();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AuthPageContainer
|
|
||||||
title={t['com.affine.auth.change.email.page.success.title']()}
|
|
||||||
subtitle={t['com.affine.auth.change.email.page.success.subtitle']()}
|
|
||||||
>
|
|
||||||
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
|
||||||
{t['com.affine.auth.open.affine']()}
|
|
||||||
</Button>
|
|
||||||
</AuthPageContainer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -4,9 +4,7 @@ export * from './auth-page-container';
|
|||||||
export * from './back-button';
|
export * from './back-button';
|
||||||
export * from './change-email-page';
|
export * from './change-email-page';
|
||||||
export * from './change-password-page';
|
export * from './change-password-page';
|
||||||
export * from './confirm-change-email';
|
|
||||||
export * from './count-down-render';
|
export * from './count-down-render';
|
||||||
export * from './email-verified-email';
|
|
||||||
export * from './modal';
|
export * from './modal';
|
||||||
export * from './modal-header';
|
export * from './modal-header';
|
||||||
export * from './onboarding-page';
|
export * from './onboarding-page';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Skeleton } from '@affine/component';
|
import { Skeleton } from '@affine/component';
|
||||||
import { Button } from '@affine/component/ui/button';
|
import { Button } from '@affine/component/ui/button';
|
||||||
|
import { ServerService } from '@affine/core/modules/cloud';
|
||||||
import { UrlService } from '@affine/core/modules/url';
|
import { UrlService } from '@affine/core/modules/url';
|
||||||
import { OAuthProviderType } from '@affine/graphql';
|
import { OAuthProviderType } from '@affine/graphql';
|
||||||
import track from '@affine/track';
|
import track from '@affine/track';
|
||||||
@@ -7,8 +8,6 @@ import { GithubIcon, GoogleDuotoneIcon } from '@blocksuite/icons/rc';
|
|||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { type ReactElement, useCallback } from 'react';
|
import { type ReactElement, useCallback } from 'react';
|
||||||
|
|
||||||
import { ServerConfigService } from '../../../modules/cloud';
|
|
||||||
|
|
||||||
const OAuthProviderMap: Record<
|
const OAuthProviderMap: Record<
|
||||||
OAuthProviderType,
|
OAuthProviderType,
|
||||||
{
|
{
|
||||||
@@ -30,11 +29,11 @@ const OAuthProviderMap: Record<
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function OAuth({ redirectUrl }: { redirectUrl?: string }) {
|
export function OAuth({ redirectUrl }: { redirectUrl?: string }) {
|
||||||
const serverConfig = useService(ServerConfigService).serverConfig;
|
const serverService = useService(ServerService);
|
||||||
const urlService = useService(UrlService);
|
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(
|
const oauthProviders = useLiveData(
|
||||||
serverConfig.config$.map(r => r?.oauthProviders)
|
serverService.server.config$.map(r => r?.oauthProviders)
|
||||||
);
|
);
|
||||||
const scheme = urlService.getClientScheme();
|
const scheme = urlService.getClientScheme();
|
||||||
|
|
||||||
@@ -66,6 +65,7 @@ function OAuthProvider({
|
|||||||
scheme?: string;
|
scheme?: string;
|
||||||
popupWindow: (url: string) => void;
|
popupWindow: (url: string) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const serverService = useService(ServerService);
|
||||||
const { icon } = OAuthProviderMap[provider];
|
const { icon } = OAuthProviderMap[provider];
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(() => {
|
||||||
@@ -85,12 +85,12 @@ function OAuthProvider({
|
|||||||
// if (BUILD_CONFIG.isAndroid) {}
|
// if (BUILD_CONFIG.isAndroid) {}
|
||||||
|
|
||||||
const oauthUrl =
|
const oauthUrl =
|
||||||
BUILD_CONFIG.serverUrlPrefix + `/oauth/login?${params.toString()}`;
|
serverService.server.baseUrl + `/oauth/login?${params.toString()}`;
|
||||||
|
|
||||||
track.$.$.auth.signIn({ method: 'oauth', provider });
|
track.$.$.auth.signIn({ method: 'oauth', provider });
|
||||||
|
|
||||||
popupWindow(oauthUrl);
|
popupWindow(oauthUrl);
|
||||||
}, [popupWindow, provider, redirectUrl, scheme]);
|
}, [popupWindow, provider, redirectUrl, scheme, serverService]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { useLiveData, useService } from '@toeverything/infra';
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { useMutation } from '../../../components/hooks/use-mutation';
|
import { useMutation } from '../../../components/hooks/use-mutation';
|
||||||
import { ServerConfigService } from '../../../modules/cloud';
|
import { ServerService } from '../../../modules/cloud';
|
||||||
import type { AuthPanelProps } from './index';
|
import type { AuthPanelProps } from './index';
|
||||||
|
|
||||||
const useEmailTitle = (emailType: AuthPanelProps<'sendEmail'>['emailType']) => {
|
const useEmailTitle = (emailType: AuthPanelProps<'sendEmail'>['emailType']) => {
|
||||||
@@ -142,10 +142,10 @@ export const SendEmail = ({
|
|||||||
// todo(@pengx17): impl redirectUrl for sendEmail?
|
// todo(@pengx17): impl redirectUrl for sendEmail?
|
||||||
}: AuthPanelProps<'sendEmail'>) => {
|
}: AuthPanelProps<'sendEmail'>) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const serverConfig = useService(ServerConfigService).serverConfig;
|
const serverService = useService(ServerService);
|
||||||
|
|
||||||
const passwordLimits = useLiveData(
|
const passwordLimits = useLiveData(
|
||||||
serverConfig.credentialsRequirement$.map(r => r?.password)
|
serverService.server.credentialsRequirement$.map(r => r?.password)
|
||||||
);
|
);
|
||||||
const [hasSentEmail, setHasSentEmail] = useState(false);
|
const [hasSentEmail, setHasSentEmail] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import { Tooltip } from '@affine/component/ui/tooltip';
|
import { Tooltip } from '@affine/component/ui/tooltip';
|
||||||
import { SubscriptionPlan } from '@affine/graphql';
|
import { SubscriptionPlan } from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { type SyntheticEvent, useEffect } from 'react';
|
import { type SyntheticEvent, useEffect } from 'react';
|
||||||
|
|
||||||
import {
|
import { ServerService, SubscriptionService } from '../../../modules/cloud';
|
||||||
ServerConfigService,
|
|
||||||
SubscriptionService,
|
|
||||||
} from '../../../modules/cloud';
|
|
||||||
import * as styles from './style.css';
|
import * as styles from './style.css';
|
||||||
|
|
||||||
export const UserPlanButton = ({
|
export const UserPlanButton = ({
|
||||||
@@ -15,13 +12,11 @@ export const UserPlanButton = ({
|
|||||||
}: {
|
}: {
|
||||||
onClick: (e: SyntheticEvent<Element, Event>) => void;
|
onClick: (e: SyntheticEvent<Element, Event>) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { serverConfigService, subscriptionService } = useServices({
|
const serverService = useService(ServerService);
|
||||||
ServerConfigService,
|
const subscriptionService = useService(SubscriptionService);
|
||||||
SubscriptionService,
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasPayment = useLiveData(
|
const hasPayment = useLiveData(
|
||||||
serverConfigService.serverConfig.features$.map(r => r?.payment)
|
serverService.server.features$.map(r => r?.payment)
|
||||||
);
|
);
|
||||||
const plan = useLiveData(
|
const plan = useLiveData(
|
||||||
subscriptionService.subscription.pro$.map(subscription =>
|
subscriptionService.subscription.pro$.map(subscription =>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
useSharingUrl,
|
useSharingUrl,
|
||||||
} from '@affine/core/components/hooks/affine/use-share-url';
|
} from '@affine/core/components/hooks/affine/use-share-url';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
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 { GlobalDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { EditorService } from '@affine/core/modules/editor';
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||||
@@ -70,13 +70,13 @@ export const AFFiNESharePage = (props: ShareMenuProps) => {
|
|||||||
const currentMode = useLiveData(editor.mode$);
|
const currentMode = useLiveData(editor.mode$);
|
||||||
const editorContainer = useLiveData(editor.editorContainer$);
|
const editorContainer = useLiveData(editor.editorContainer$);
|
||||||
const shareInfoService = useService(ShareInfoService);
|
const shareInfoService = useService(ShareInfoService);
|
||||||
const serverConfig = useService(ServerConfigService).serverConfig;
|
const serverService = useService(ServerService);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
shareInfoService.shareInfo.revalidate();
|
shareInfoService.shareInfo.revalidate();
|
||||||
}, [shareInfoService]);
|
}, [shareInfoService]);
|
||||||
const isSharedPage = useLiveData(shareInfoService.shareInfo.isShared$);
|
const isSharedPage = useLiveData(shareInfoService.shareInfo.isShared$);
|
||||||
const sharedMode = useLiveData(shareInfoService.shareInfo.sharedMode$);
|
const sharedMode = useLiveData(shareInfoService.shareInfo.sharedMode$);
|
||||||
const baseUrl = useLiveData(serverConfig.config$.map(c => c?.baseUrl));
|
const baseUrl = serverService.server.baseUrl;
|
||||||
const isLoading =
|
const isLoading =
|
||||||
isSharedPage === null || sharedMode === null || baseUrl === null;
|
isSharedPage === null || sharedMode === null || baseUrl === null;
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import {
|
|||||||
cleanupCopilotSessionMutation,
|
cleanupCopilotSessionMutation,
|
||||||
createCopilotMessageMutation,
|
createCopilotMessageMutation,
|
||||||
createCopilotSessionMutation,
|
createCopilotSessionMutation,
|
||||||
fetcher as defaultFetcher,
|
|
||||||
forkCopilotSessionMutation,
|
forkCopilotSessionMutation,
|
||||||
getBaseUrl,
|
|
||||||
getCopilotHistoriesQuery,
|
getCopilotHistoriesQuery,
|
||||||
getCopilotHistoryIdsQuery,
|
getCopilotHistoryIdsQuery,
|
||||||
getCopilotSessionsQuery,
|
getCopilotSessionsQuery,
|
||||||
|
gqlFetcherFactory,
|
||||||
GraphQLError,
|
GraphQLError,
|
||||||
type GraphQLQuery,
|
type GraphQLQuery,
|
||||||
type QueryOptions,
|
type QueryOptions,
|
||||||
@@ -22,6 +21,26 @@ import {
|
|||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import { getCurrentStore } from '@toeverything/infra';
|
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> =
|
type OptionsField<T extends GraphQLQuery> =
|
||||||
RequestOptions<T>['variables'] extends { options: infer U } ? U : never;
|
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 { toggleGeneralAIOnboarding } from '@affine/core/components/affine/ai-onboarding/apis';
|
||||||
import { authAtom } from '@affine/core/components/atoms';
|
import { authAtom } from '@affine/core/components/atoms';
|
||||||
import {
|
import {
|
||||||
getBaseUrl,
|
|
||||||
type getCopilotHistoriesQuery,
|
type getCopilotHistoriesQuery,
|
||||||
type RequestOptions,
|
type RequestOptions,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
@@ -11,6 +10,7 @@ import { assertExists } from '@blocksuite/affine/global/utils';
|
|||||||
import { getCurrentStore } from '@toeverything/infra';
|
import { getCurrentStore } from '@toeverything/infra';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { getBaseUrl } from './copilot-client';
|
||||||
import type { PromptKey } from './prompt';
|
import type { PromptKey } from './prompt';
|
||||||
import {
|
import {
|
||||||
cleanupSessions,
|
cleanupSessions,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
generateUrl,
|
generateUrl,
|
||||||
type UseSharingUrl,
|
type UseSharingUrl,
|
||||||
} from '@affine/core/components/hooks/affine/use-share-url';
|
} 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 { EditorService } from '@affine/core/modules/editor';
|
||||||
import { copyLinkToBlockStdScopeClipboard } from '@affine/core/utils/clipboard';
|
import { copyLinkToBlockStdScopeClipboard } from '@affine/core/utils/clipboard';
|
||||||
import { I18n } from '@affine/i18n';
|
import { I18n } from '@affine/i18n';
|
||||||
@@ -41,9 +41,7 @@ function createCopyLinkToBlockMenuItem(
|
|||||||
return mode === 'edgeless';
|
return mode === 'edgeless';
|
||||||
},
|
},
|
||||||
select: () => {
|
select: () => {
|
||||||
const baseUrl = getAffineCloudBaseUrl();
|
const serverService = framework.get(ServerService);
|
||||||
if (!baseUrl) return;
|
|
||||||
|
|
||||||
const pageId = model.doc.id;
|
const pageId = model.doc.id;
|
||||||
const { editor } = framework.get(EditorService);
|
const { editor } = framework.get(EditorService);
|
||||||
const mode = editor.mode$.value;
|
const mode = editor.mode$.value;
|
||||||
@@ -58,7 +56,10 @@ function createCopyLinkToBlockMenuItem(
|
|||||||
blockIds: [model.id],
|
blockIds: [model.id],
|
||||||
};
|
};
|
||||||
|
|
||||||
const str = generateUrl(options);
|
const str = generateUrl({
|
||||||
|
...options,
|
||||||
|
baseUrl: serverService.server.baseUrl,
|
||||||
|
});
|
||||||
if (!str) return;
|
if (!str) return;
|
||||||
|
|
||||||
const type = model.flavour;
|
const type = model.flavour;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
generateUrl,
|
generateUrl,
|
||||||
type UseSharingUrl,
|
type UseSharingUrl,
|
||||||
} from '@affine/core/components/hooks/affine/use-share-url';
|
} 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 { EditorService } from '@affine/core/modules/editor';
|
||||||
import { copyLinkToBlockStdScopeClipboard } from '@affine/core/utils/clipboard';
|
import { copyLinkToBlockStdScopeClipboard } from '@affine/core/utils/clipboard';
|
||||||
import { I18n } from '@affine/i18n';
|
import { I18n } from '@affine/i18n';
|
||||||
@@ -79,11 +79,7 @@ function createCopyLinkToBlockMenuItem(
|
|||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
action: async (ctx: MenuContext) => {
|
action: async (ctx: MenuContext) => {
|
||||||
const baseUrl = getAffineCloudBaseUrl();
|
const workspaceServerService = framework.get(WorkspaceServerService);
|
||||||
if (!baseUrl) {
|
|
||||||
ctx.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { editor } = framework.get(EditorService);
|
const { editor } = framework.get(EditorService);
|
||||||
const mode = editor.mode$.value;
|
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) {
|
if (!str) {
|
||||||
ctx.close();
|
ctx.close();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { notify } from '@affine/component';
|
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 { toURLSearchParams } from '@affine/core/modules/navigation';
|
||||||
import { copyTextToClipboard } from '@affine/core/utils/clipboard';
|
import { copyTextToClipboard } from '@affine/core/utils/clipboard';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
@@ -7,6 +7,7 @@ import { track } from '@affine/track';
|
|||||||
import { type EditorHost } from '@blocksuite/affine/block-std';
|
import { type EditorHost } from '@blocksuite/affine/block-std';
|
||||||
import { GfxBlockElementModel } from '@blocksuite/affine/block-std/gfx';
|
import { GfxBlockElementModel } from '@blocksuite/affine/block-std/gfx';
|
||||||
import type { DocMode, EdgelessRootService } from '@blocksuite/affine/blocks';
|
import type { DocMode, EdgelessRootService } from '@blocksuite/affine/blocks';
|
||||||
|
import { useService } from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
export type UseSharingUrl = {
|
export type UseSharingUrl = {
|
||||||
@@ -24,17 +25,14 @@ export type UseSharingUrl = {
|
|||||||
* https://app.affine.pro/workspace/workspaceId/docId?mode=DocMode&elementIds=seletedElementIds&blockIds=selectedBlockIds
|
* https://app.affine.pro/workspace/workspaceId/docId?mode=DocMode&elementIds=seletedElementIds&blockIds=selectedBlockIds
|
||||||
*/
|
*/
|
||||||
export const generateUrl = ({
|
export const generateUrl = ({
|
||||||
|
baseUrl,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
pageId,
|
pageId,
|
||||||
blockIds,
|
blockIds,
|
||||||
elementIds,
|
elementIds,
|
||||||
shareMode: mode,
|
shareMode: mode,
|
||||||
xywh, // not needed currently
|
xywh, // not needed currently
|
||||||
}: UseSharingUrl) => {
|
}: UseSharingUrl & { baseUrl: string }) => {
|
||||||
// Base URL construction
|
|
||||||
const baseUrl = getAffineCloudBaseUrl();
|
|
||||||
if (!baseUrl) return null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = new URL(`/workspace/${workspaceId}/${pageId}`, baseUrl);
|
const url = new URL(`/workspace/${workspaceId}/${pageId}`, baseUrl);
|
||||||
const search = toURLSearchParams({ mode, blockIds, elementIds, xywh });
|
const search = toURLSearchParams({ mode, blockIds, elementIds, xywh });
|
||||||
@@ -130,10 +128,12 @@ export const getSelectedNodes = (
|
|||||||
|
|
||||||
export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
|
export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
const serverService = useService(ServerService);
|
||||||
|
|
||||||
const onClickCopyLink = useCallback(
|
const onClickCopyLink = useCallback(
|
||||||
(shareMode?: DocMode, blockIds?: string[], elementIds?: string[]) => {
|
(shareMode?: DocMode, blockIds?: string[], elementIds?: string[]) => {
|
||||||
const sharingUrl = generateUrl({
|
const sharingUrl = generateUrl({
|
||||||
|
baseUrl: serverService.server.baseUrl,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
pageId,
|
pageId,
|
||||||
blockIds,
|
blockIds,
|
||||||
@@ -160,7 +160,7 @@ export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
|
|||||||
notify.error({ title: 'Network not available' });
|
notify.error({ title: 'Network not available' });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[pageId, t, workspaceId]
|
[pageId, serverService, t, workspaceId]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { GraphQLService } from '@affine/core/modules/cloud';
|
||||||
import type {
|
import type {
|
||||||
GraphQLQuery,
|
GraphQLQuery,
|
||||||
MutationOptions,
|
MutationOptions,
|
||||||
@@ -5,7 +6,7 @@ import type {
|
|||||||
QueryVariables,
|
QueryVariables,
|
||||||
RecursiveMaybeFields,
|
RecursiveMaybeFields,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { fetcher } from '@affine/graphql';
|
import { useService } from '@toeverything/infra';
|
||||||
import type { GraphQLError } from 'graphql';
|
import type { GraphQLError } from 'graphql';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import type { Key } from 'swr';
|
import type { Key } from 'swr';
|
||||||
@@ -51,10 +52,15 @@ export function useMutation(
|
|||||||
options: Omit<MutationOptions<GraphQLQuery>, 'variables'>,
|
options: Omit<MutationOptions<GraphQLQuery>, 'variables'>,
|
||||||
config?: any
|
config?: any
|
||||||
) {
|
) {
|
||||||
|
const graphqlService = useService(GraphQLService);
|
||||||
return useSWRMutation(
|
return useSWRMutation(
|
||||||
() => ['cloud', options.mutation.id],
|
() => ['cloud', options.mutation.id],
|
||||||
(_: unknown[], { arg }: { arg: any }) =>
|
(_: unknown[], { arg }: { arg: any }) =>
|
||||||
fetcher({ ...options, query: options.mutation, variables: arg }),
|
graphqlService.gql({
|
||||||
|
...options,
|
||||||
|
query: options.mutation,
|
||||||
|
variables: arg,
|
||||||
|
}),
|
||||||
config
|
config
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { GraphQLService } from '@affine/core/modules/cloud';
|
||||||
import type {
|
import type {
|
||||||
GraphQLQuery,
|
GraphQLQuery,
|
||||||
QueryOptions,
|
QueryOptions,
|
||||||
QueryResponse,
|
QueryResponse,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { fetcher } from '@affine/graphql';
|
import { useService } from '@toeverything/infra';
|
||||||
import type { GraphQLError } from 'graphql';
|
import type { GraphQLError } from 'graphql';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import type { SWRConfiguration, SWRResponse } from 'swr';
|
import type { SWRConfiguration, SWRResponse } from 'swr';
|
||||||
@@ -32,7 +33,11 @@ import useSWRInfinite from 'swr/infinite';
|
|||||||
type useQueryFn = <Query extends GraphQLQuery>(
|
type useQueryFn = <Query extends GraphQLQuery>(
|
||||||
options?: QueryOptions<Query>,
|
options?: QueryOptions<Query>,
|
||||||
config?: Omit<
|
config?: Omit<
|
||||||
SWRConfiguration<QueryResponse<Query>, GraphQLError, typeof fetcher<Query>>,
|
SWRConfiguration<
|
||||||
|
QueryResponse<Query>,
|
||||||
|
GraphQLError,
|
||||||
|
(options: QueryOptions<Query>) => Promise<QueryResponse<Query>>
|
||||||
|
>,
|
||||||
'fetcher'
|
'fetcher'
|
||||||
>
|
>
|
||||||
) => SWRResponse<
|
) => SWRResponse<
|
||||||
@@ -53,11 +58,12 @@ const createUseQuery =
|
|||||||
}),
|
}),
|
||||||
[config]
|
[config]
|
||||||
);
|
);
|
||||||
|
const graphqlService = useService(GraphQLService);
|
||||||
|
|
||||||
const useSWRFn = immutable ? useSWRImutable : useSWR;
|
const useSWRFn = immutable ? useSWRImutable : useSWR;
|
||||||
return useSWRFn(
|
return useSWRFn(
|
||||||
options ? () => ['cloud', options.query.id, options.variables] : null,
|
options ? () => ['cloud', options.query.id, options.variables] : null,
|
||||||
options ? () => fetcher(options) : null,
|
options ? () => graphqlService.gql(options) : null,
|
||||||
configWithSuspense
|
configWithSuspense
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -76,7 +82,7 @@ export function useQueryInfinite<Query extends GraphQLQuery>(
|
|||||||
SWRConfiguration<
|
SWRConfiguration<
|
||||||
QueryResponse<Query>,
|
QueryResponse<Query>,
|
||||||
GraphQLError | GraphQLError[],
|
GraphQLError | GraphQLError[],
|
||||||
typeof fetcher<Query>
|
(options: QueryOptions<Query>) => Promise<QueryResponse<Query>>
|
||||||
>,
|
>,
|
||||||
'fetcher'
|
'fetcher'
|
||||||
>
|
>
|
||||||
@@ -88,6 +94,7 @@ export function useQueryInfinite<Query extends GraphQLQuery>(
|
|||||||
}),
|
}),
|
||||||
[config]
|
[config]
|
||||||
);
|
);
|
||||||
|
const graphqlService = useService(GraphQLService);
|
||||||
|
|
||||||
const { data, setSize, size, error } = useSWRInfinite<
|
const { data, setSize, size, error } = useSWRInfinite<
|
||||||
QueryResponse<Query>,
|
QueryResponse<Query>,
|
||||||
@@ -100,7 +107,7 @@ export function useQueryInfinite<Query extends GraphQLQuery>(
|
|||||||
],
|
],
|
||||||
async ([_, __, variables]) => {
|
async ([_, __, variables]) => {
|
||||||
const params = { ...options, variables } as QueryOptions<Query>;
|
const params = { ...options, variables } as QueryOptions<Query>;
|
||||||
return fetcher(params);
|
return graphqlService.gql(params);
|
||||||
},
|
},
|
||||||
configWithSuspense
|
configWithSuspense
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { useCallback, useEffect } from 'react';
|
|||||||
import {
|
import {
|
||||||
type AuthAccountInfo,
|
type AuthAccountInfo,
|
||||||
AuthService,
|
AuthService,
|
||||||
ServerConfigService,
|
ServerService,
|
||||||
SubscriptionService,
|
SubscriptionService,
|
||||||
UserCopilotQuotaService,
|
UserCopilotQuotaService,
|
||||||
UserQuotaService,
|
UserQuotaService,
|
||||||
@@ -276,10 +276,8 @@ const AIUsage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const OperationMenu = () => {
|
const OperationMenu = () => {
|
||||||
const serverConfigService = useService(ServerConfigService);
|
const serverService = useService(ServerService);
|
||||||
const serverFeatures = useLiveData(
|
const serverFeatures = useLiveData(serverService.server.features$);
|
||||||
serverConfigService.serverConfig.features$
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Button, ErrorMessage, Skeleton } from '@affine/component';
|
import { Button, ErrorMessage, Skeleton } from '@affine/component';
|
||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import {
|
import {
|
||||||
ServerConfigService,
|
ServerService,
|
||||||
SubscriptionService,
|
SubscriptionService,
|
||||||
UserCopilotQuotaService,
|
UserCopilotQuotaService,
|
||||||
} from '@affine/core/modules/cloud';
|
} from '@affine/core/modules/cloud';
|
||||||
@@ -22,9 +22,9 @@ export const AIUsagePanel = ({
|
|||||||
onChangeSettingState?: (settingState: SettingState) => void;
|
onChangeSettingState?: (settingState: SettingState) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const serverConfigService = useService(ServerConfigService);
|
const serverService = useService(ServerService);
|
||||||
const hasPaymentFeature = useLiveData(
|
const hasPaymentFeature = useLiveData(
|
||||||
serverConfigService.serverConfig.features$.map(f => f?.payment)
|
serverService.server.features$.map(f => f?.payment)
|
||||||
);
|
);
|
||||||
const subscriptionService = useService(SubscriptionService);
|
const subscriptionService = useService(SubscriptionService);
|
||||||
const aiSubscription = useLiveData(subscriptionService.subscription.ai$);
|
const aiSubscription = useLiveData(subscriptionService.subscription.ai$);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { AuthService, ServerConfigService } from '../../../../modules/cloud';
|
import { AuthService, ServerService } from '../../../../modules/cloud';
|
||||||
import type { SettingState } from '../types';
|
import type { SettingState } from '../types';
|
||||||
import { AIUsagePanel } from './ai-usage-panel';
|
import { AIUsagePanel } from './ai-usage-panel';
|
||||||
import { StorageProgress } from './storage-progress';
|
import { StorageProgress } from './storage-progress';
|
||||||
@@ -178,13 +178,11 @@ export const AccountSetting = ({
|
|||||||
}: {
|
}: {
|
||||||
onChangeSettingState?: (settingState: SettingState) => void;
|
onChangeSettingState?: (settingState: SettingState) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { authService, serverConfigService } = useServices({
|
const { authService, serverService } = useServices({
|
||||||
AuthService,
|
AuthService,
|
||||||
ServerConfigService,
|
ServerService,
|
||||||
});
|
});
|
||||||
const serverFeatures = useLiveData(
|
const serverFeatures = useLiveData(serverService.server.features$);
|
||||||
serverConfigService.serverConfig.features$
|
|
||||||
);
|
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const session = authService.session;
|
const session = authService.session;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { cssVar } from '@toeverything/theme';
|
|||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ServerConfigService,
|
ServerService,
|
||||||
SubscriptionService,
|
SubscriptionService,
|
||||||
UserQuotaService,
|
UserQuotaService,
|
||||||
} from '../../../../modules/cloud';
|
} from '../../../../modules/cloud';
|
||||||
@@ -34,9 +34,9 @@ export const StorageProgress = ({ onUpgrade }: StorageProgressProgress) => {
|
|||||||
const maxFormatted = useLiveData(quota.maxFormatted$);
|
const maxFormatted = useLiveData(quota.maxFormatted$);
|
||||||
const percent = useLiveData(quota.percent$);
|
const percent = useLiveData(quota.percent$);
|
||||||
|
|
||||||
const serverConfigService = useService(ServerConfigService);
|
const serverService = useService(ServerService);
|
||||||
const hasPaymentFeature = useLiveData(
|
const hasPaymentFeature = useLiveData(
|
||||||
serverConfigService.serverConfig.features$.map(f => f?.payment)
|
serverService.server.features$.map(f => f?.payment)
|
||||||
);
|
);
|
||||||
const subscription = useService(SubscriptionService).subscription;
|
const subscription = useService(SubscriptionService).subscription;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
SettingWrapper,
|
SettingWrapper,
|
||||||
} from '@affine/component/setting-components';
|
} from '@affine/component/setting-components';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
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 { DesktopApiService } from '@affine/core/modules/desktop-api';
|
import { DesktopApiService } from '@affine/core/modules/desktop-api';
|
||||||
import {
|
import {
|
||||||
EditorSettingService,
|
EditorSettingService,
|
||||||
@@ -358,13 +358,11 @@ const NewDocDefaultModeSettings = () => {
|
|||||||
const AISettings = () => {
|
const AISettings = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const { openConfirmModal } = useConfirmModal();
|
const { openConfirmModal } = useConfirmModal();
|
||||||
const { featureFlagService, serverConfigService } = useServices({
|
const { featureFlagService, serverService } = useServices({
|
||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
ServerConfigService,
|
ServerService,
|
||||||
});
|
});
|
||||||
const serverFeatures = useLiveData(
|
const serverFeatures = useLiveData(serverService.server.features$);
|
||||||
serverConfigService.serverConfig.features$
|
|
||||||
);
|
|
||||||
const enableAI = useLiveData(featureFlagService.flags.enable_ai.$);
|
const enableAI = useLiveData(featureFlagService.flags.enable_ai.$);
|
||||||
|
|
||||||
const onAIChange = useCallback(
|
const onAIChange = useCallback(
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
import type { ReactElement, SVGProps } from 'react';
|
import type { ReactElement, SVGProps } from 'react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import { AuthService, ServerConfigService } from '../../../../modules/cloud';
|
import { AuthService, ServerService } from '../../../../modules/cloud';
|
||||||
import type { SettingState } from '../types';
|
import type { SettingState } from '../types';
|
||||||
import { AboutAffine } from './about';
|
import { AboutAffine } from './about';
|
||||||
import { AppearanceSettings } from './appearance';
|
import { AppearanceSettings } from './appearance';
|
||||||
@@ -38,20 +38,16 @@ export type GeneralSettingList = GeneralSettingListItem[];
|
|||||||
|
|
||||||
export const useGeneralSettingList = (): GeneralSettingList => {
|
export const useGeneralSettingList = (): GeneralSettingList => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const {
|
const { authService, serverService, userFeatureService, featureFlagService } =
|
||||||
authService,
|
useServices({
|
||||||
serverConfigService,
|
AuthService,
|
||||||
userFeatureService,
|
ServerService,
|
||||||
featureFlagService,
|
UserFeatureService,
|
||||||
} = useServices({
|
FeatureFlagService,
|
||||||
AuthService,
|
});
|
||||||
ServerConfigService,
|
|
||||||
UserFeatureService,
|
|
||||||
FeatureFlagService,
|
|
||||||
});
|
|
||||||
const status = useLiveData(authService.session.status$);
|
const status = useLiveData(authService.session.status$);
|
||||||
const hasPaymentFeature = useLiveData(
|
const hasPaymentFeature = useLiveData(
|
||||||
serverConfigService.serverConfig.features$.map(f => f?.payment)
|
serverService.server.features$.map(f => f?.payment)
|
||||||
);
|
);
|
||||||
const enableEditorSettings = useLiveData(
|
const enableEditorSettings = useLiveData(
|
||||||
featureFlagService.flags.enable_editor_settings.$
|
featureFlagService.flags.enable_editor_settings.$
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import {
|
import {
|
||||||
type AuthAccountInfo,
|
type AuthAccountInfo,
|
||||||
AuthService,
|
AuthService,
|
||||||
ServerConfigService,
|
ServerService,
|
||||||
SubscriptionService,
|
SubscriptionService,
|
||||||
} from '../../../../../modules/cloud';
|
} from '../../../../../modules/cloud';
|
||||||
import type { SettingState } from '../../types';
|
import type { SettingState } from '../../types';
|
||||||
@@ -65,9 +65,9 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
}: {
|
}: {
|
||||||
onChangeSettingState: (settingState: SettingState) => void;
|
onChangeSettingState: (settingState: SettingState) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const serverConfig = useService(ServerConfigService).serverConfig;
|
const serverService = useService(ServerService);
|
||||||
const hasPaymentFeature = useLiveData(
|
const hasPaymentFeature = useLiveData(
|
||||||
serverConfig.features$.map(f => f?.payment)
|
serverService.server.features$.map(f => f?.payment)
|
||||||
);
|
);
|
||||||
const workspace = useService(WorkspaceService).workspace;
|
const workspace = useService(WorkspaceService).workspace;
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,14 @@ import { notify } from '@affine/component';
|
|||||||
import {
|
import {
|
||||||
ChangeEmailPage,
|
ChangeEmailPage,
|
||||||
ChangePasswordPage,
|
ChangePasswordPage,
|
||||||
ConfirmChangeEmail,
|
|
||||||
ConfirmVerifiedEmail,
|
|
||||||
OnboardingPage,
|
OnboardingPage,
|
||||||
SetPasswordPage,
|
SetPasswordPage,
|
||||||
SignInSuccessPage,
|
SignInSuccessPage,
|
||||||
SignUpPage,
|
SignUpPage,
|
||||||
} from '@affine/component/auth-components';
|
} from '@affine/component/auth-components';
|
||||||
import {
|
import {
|
||||||
changeEmailMutation,
|
|
||||||
changePasswordMutation,
|
changePasswordMutation,
|
||||||
fetcher,
|
|
||||||
sendVerifyChangeEmailMutation,
|
sendVerifyChangeEmailMutation,
|
||||||
verifyEmailMutation,
|
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
@@ -28,7 +23,10 @@ import {
|
|||||||
RouteLogic,
|
RouteLogic,
|
||||||
useNavigateHelper,
|
useNavigateHelper,
|
||||||
} from '../../../components/hooks/use-navigate-helper';
|
} from '../../../components/hooks/use-navigate-helper';
|
||||||
import { AuthService, ServerConfigService } from '../../../modules/cloud';
|
import { AuthService, ServerService } from '../../../modules/cloud';
|
||||||
|
import { AppContainer } from '../../components/app-container';
|
||||||
|
import { ConfirmChangeEmail } from './confirm-change-email';
|
||||||
|
import { ConfirmVerifiedEmail } from './email-verified-email';
|
||||||
|
|
||||||
const authTypeSchema = z.enum([
|
const authTypeSchema = z.enum([
|
||||||
'onboarding',
|
'onboarding',
|
||||||
@@ -46,9 +44,9 @@ export const Component = () => {
|
|||||||
const authService = useService(AuthService);
|
const authService = useService(AuthService);
|
||||||
const account = useLiveData(authService.session.account$);
|
const account = useLiveData(authService.session.account$);
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const serverConfig = useService(ServerConfigService).serverConfig;
|
const serverService = useService(ServerService);
|
||||||
const passwordLimits = useLiveData(
|
const passwordLimits = useLiveData(
|
||||||
serverConfig.credentialsRequirement$.map(r => r?.password)
|
serverService.server.credentialsRequirement$.map(r => r?.password)
|
||||||
);
|
);
|
||||||
|
|
||||||
const { authType } = useParams();
|
const { authType } = useParams();
|
||||||
@@ -103,8 +101,7 @@ export const Component = () => {
|
|||||||
}, [jumpToIndex]);
|
}, [jumpToIndex]);
|
||||||
|
|
||||||
if (!passwordLimits) {
|
if (!passwordLimits) {
|
||||||
// TODO(@eyhn): loading UI
|
return <AppContainer fallback />;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (authType) {
|
switch (authType) {
|
||||||
@@ -171,36 +168,5 @@ export const loader: LoaderFunction = async args => {
|
|||||||
return redirect('/404');
|
return redirect('/404');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.params.authType === 'confirm-change-email') {
|
|
||||||
const url = new URL(args.request.url);
|
|
||||||
const searchParams = url.searchParams;
|
|
||||||
const token = searchParams.get('token') ?? '';
|
|
||||||
const email = decodeURIComponent(searchParams.get('email') ?? '');
|
|
||||||
const res = await fetcher({
|
|
||||||
query: changeEmailMutation,
|
|
||||||
variables: {
|
|
||||||
token: token,
|
|
||||||
email: email,
|
|
||||||
},
|
|
||||||
}).catch(console.error);
|
|
||||||
// TODO(@eyhn): Add error handling
|
|
||||||
if (!res?.changeEmail) {
|
|
||||||
return redirect('/expired');
|
|
||||||
}
|
|
||||||
} else if (args.params.authType === 'verify-email') {
|
|
||||||
const url = new URL(args.request.url);
|
|
||||||
const searchParams = url.searchParams;
|
|
||||||
const token = searchParams.get('token') ?? '';
|
|
||||||
const res = await fetcher({
|
|
||||||
query: verifyEmailMutation,
|
|
||||||
variables: {
|
|
||||||
token: token,
|
|
||||||
},
|
|
||||||
}).catch(console.error);
|
|
||||||
|
|
||||||
if (!res?.verifyEmail) {
|
|
||||||
return redirect('/expired');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { Button } from '@affine/component';
|
||||||
|
import { AuthPageContainer } from '@affine/component/auth-components';
|
||||||
|
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||||
|
import { BackendError, GraphQLService } from '@affine/core/modules/cloud';
|
||||||
|
import { changeEmailMutation, ErrorNames } from '@affine/graphql';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import { useService } from '@toeverything/infra';
|
||||||
|
import { type FC, useEffect, useState } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { AppContainer } from '../../components/app-container';
|
||||||
|
|
||||||
|
export const ConfirmChangeEmail: FC<{
|
||||||
|
onOpenAffine: () => void;
|
||||||
|
}> = ({ onOpenAffine }) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const navigateHelper = useNavigateHelper();
|
||||||
|
const graphqlService = useService(GraphQLService);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const token = searchParams.get('token') ?? '';
|
||||||
|
const email = decodeURIComponent(searchParams.get('email') ?? '');
|
||||||
|
setIsLoading(true);
|
||||||
|
await graphqlService
|
||||||
|
.gql({
|
||||||
|
query: changeEmailMutation,
|
||||||
|
variables: {
|
||||||
|
token: token,
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (err instanceof BackendError) {
|
||||||
|
const userFriendlyError = err.originError;
|
||||||
|
if (userFriendlyError.name === ErrorNames.INVALID_EMAIL_TOKEN) {
|
||||||
|
return navigateHelper.jumpToExpired();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
})().catch(err => {
|
||||||
|
// TODO(@eyhn): Add error handling
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [graphqlService, navigateHelper, searchParams]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <AppContainer fallback />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthPageContainer
|
||||||
|
title={t['com.affine.auth.change.email.page.success.title']()}
|
||||||
|
subtitle={t['com.affine.auth.change.email.page.success.subtitle']()}
|
||||||
|
>
|
||||||
|
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
||||||
|
{t['com.affine.auth.open.affine']()}
|
||||||
|
</Button>
|
||||||
|
</AuthPageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { Button } from '@affine/component';
|
||||||
|
import { AuthPageContainer } from '@affine/component/auth-components';
|
||||||
|
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||||
|
import { GraphQLService } from '@affine/core/modules/cloud';
|
||||||
|
import {
|
||||||
|
ErrorNames,
|
||||||
|
UserFriendlyError,
|
||||||
|
verifyEmailMutation,
|
||||||
|
} from '@affine/graphql';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import { useService } from '@toeverything/infra';
|
||||||
|
import { type FC, useEffect, useState } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { AppContainer } from '../../components/app-container';
|
||||||
|
|
||||||
|
export const ConfirmVerifiedEmail: FC<{
|
||||||
|
onOpenAffine: () => void;
|
||||||
|
}> = ({ onOpenAffine }) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const graphqlService = useService(GraphQLService);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const navigateHelper = useNavigateHelper();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const token = searchParams.get('token') ?? '';
|
||||||
|
setIsLoading(true);
|
||||||
|
await graphqlService
|
||||||
|
.gql({
|
||||||
|
query: verifyEmailMutation,
|
||||||
|
variables: {
|
||||||
|
token: token,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
const userFriendlyError = UserFriendlyError.fromAnyError(error);
|
||||||
|
if (userFriendlyError.name === ErrorNames.INVALID_EMAIL_TOKEN) {
|
||||||
|
return navigateHelper.jumpToExpired();
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
})().catch(err => {
|
||||||
|
// TODO(@eyhn): Add error handling
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [graphqlService, navigateHelper, searchParams]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <AppContainer fallback />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthPageContainer
|
||||||
|
title={t['com.affine.auth.change.email.page.success.title']()}
|
||||||
|
subtitle={t['com.affine.auth.change.email.page.success.subtitle']()}
|
||||||
|
>
|
||||||
|
<Button variant="primary" size="large" onClick={onOpenAffine}>
|
||||||
|
{t['com.affine.auth.open.affine']()}
|
||||||
|
</Button>
|
||||||
|
</AuthPageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -2,56 +2,29 @@ import { AcceptInvitePage } from '@affine/component/member-components';
|
|||||||
import type { GetInviteInfoQuery } from '@affine/graphql';
|
import type { GetInviteInfoQuery } from '@affine/graphql';
|
||||||
import {
|
import {
|
||||||
acceptInviteByInviteIdMutation,
|
acceptInviteByInviteIdMutation,
|
||||||
fetcher,
|
|
||||||
getInviteInfoQuery,
|
getInviteInfoQuery,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import type { LoaderFunction } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { redirect, useLoaderData } from 'react-router-dom';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RouteLogic,
|
RouteLogic,
|
||||||
useNavigateHelper,
|
useNavigateHelper,
|
||||||
} from '../../../components/hooks/use-navigate-helper';
|
} from '../../../components/hooks/use-navigate-helper';
|
||||||
import { AuthService } from '../../../modules/cloud';
|
import { AuthService, GraphQLService } from '../../../modules/cloud';
|
||||||
|
import { AppContainer } from '../../components/app-container';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* /invite/:inviteId page
|
* /invite/:inviteId page
|
||||||
*
|
*
|
||||||
* only for web
|
* only for web
|
||||||
*/
|
*/
|
||||||
export const loader: LoaderFunction = async args => {
|
const AcceptInvite = ({
|
||||||
const inviteId = args.params.inviteId || '';
|
inviteInfo,
|
||||||
const res = await fetcher({
|
}: {
|
||||||
query: getInviteInfoQuery,
|
inviteInfo: GetInviteInfoQuery['getInviteInfo'];
|
||||||
variables: {
|
}) => {
|
||||||
inviteId,
|
|
||||||
},
|
|
||||||
}).catch(console.error);
|
|
||||||
|
|
||||||
// If the inviteId is invalid, redirect to 404 page
|
|
||||||
if (!res || !res?.getInviteInfo) {
|
|
||||||
return redirect('/404');
|
|
||||||
}
|
|
||||||
|
|
||||||
// No mater sign in or not, we need to accept the invite
|
|
||||||
await fetcher({
|
|
||||||
query: acceptInviteByInviteIdMutation,
|
|
||||||
variables: {
|
|
||||||
workspaceId: res.getInviteInfo.workspace.id,
|
|
||||||
inviteId,
|
|
||||||
sendAcceptMail: true,
|
|
||||||
},
|
|
||||||
}).catch(console.error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
inviteId,
|
|
||||||
inviteInfo: res.getInviteInfo,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Component = () => {
|
|
||||||
const authService = useService(AuthService);
|
const authService = useService(AuthService);
|
||||||
const isRevalidating = useLiveData(authService.session.isRevalidating$);
|
const isRevalidating = useLiveData(authService.session.isRevalidating$);
|
||||||
const loginStatus = useLiveData(authService.session.status$);
|
const loginStatus = useLiveData(authService.session.status$);
|
||||||
@@ -63,11 +36,6 @@ export const Component = () => {
|
|||||||
const { jumpToSignIn } = useNavigateHelper();
|
const { jumpToSignIn } = useNavigateHelper();
|
||||||
const { jumpToPage } = useNavigateHelper();
|
const { jumpToPage } = useNavigateHelper();
|
||||||
|
|
||||||
const { inviteInfo } = useLoaderData() as {
|
|
||||||
inviteId: string;
|
|
||||||
inviteInfo: GetInviteInfoQuery['getInviteInfo'];
|
|
||||||
};
|
|
||||||
|
|
||||||
const openWorkspace = useCallback(() => {
|
const openWorkspace = useCallback(() => {
|
||||||
jumpToPage(inviteInfo.workspace.id, 'all', RouteLogic.REPLACE);
|
jumpToPage(inviteInfo.workspace.id, 'all', RouteLogic.REPLACE);
|
||||||
}, [inviteInfo.workspace.id, jumpToPage]);
|
}, [inviteInfo.workspace.id, jumpToPage]);
|
||||||
@@ -99,3 +67,57 @@ export const Component = () => {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Component = () => {
|
||||||
|
const graphqlService = useService(GraphQLService);
|
||||||
|
const params = useParams<{ inviteId: string }>();
|
||||||
|
const navigateHelper = useNavigateHelper();
|
||||||
|
|
||||||
|
const [data, setData] = useState<{
|
||||||
|
inviteId: string;
|
||||||
|
inviteInfo: GetInviteInfoQuery['getInviteInfo'];
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
setData(null);
|
||||||
|
const inviteId = params.inviteId || '';
|
||||||
|
const res = await graphqlService.gql({
|
||||||
|
query: getInviteInfoQuery,
|
||||||
|
variables: {
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the inviteId is invalid, redirect to 404 page
|
||||||
|
if (!res || !res?.getInviteInfo) {
|
||||||
|
return navigateHelper.jumpTo404();
|
||||||
|
}
|
||||||
|
|
||||||
|
// No mater sign in or not, we need to accept the invite
|
||||||
|
await graphqlService.gql({
|
||||||
|
query: acceptInviteByInviteIdMutation,
|
||||||
|
variables: {
|
||||||
|
workspaceId: res.getInviteInfo.workspace.id,
|
||||||
|
inviteId,
|
||||||
|
sendAcceptMail: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setData({
|
||||||
|
inviteId,
|
||||||
|
inviteInfo: res.getInviteInfo,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
})().catch(err => {
|
||||||
|
// TODO: handle error
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}, [graphqlService, navigateHelper, params.inviteId]);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return <AppContainer fallback />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <AcceptInvite inviteInfo={data.inviteInfo} />;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
|
import { GraphQLService } from '@affine/core/modules/cloud';
|
||||||
import { OpenInAppPage } from '@affine/core/modules/open-in-app/views/open-in-app-page';
|
import { OpenInAppPage } from '@affine/core/modules/open-in-app/views/open-in-app-page';
|
||||||
import { appSchemes, channelToScheme } from '@affine/core/utils/channel';
|
import { appSchemes, channelToScheme } from '@affine/core/utils/channel';
|
||||||
import type { GetCurrentUserQuery } from '@affine/graphql';
|
import type { GetCurrentUserQuery } from '@affine/graphql';
|
||||||
import { fetcher, getCurrentUserQuery } from '@affine/graphql';
|
import { getCurrentUserQuery } from '@affine/graphql';
|
||||||
import type { LoaderFunction } from 'react-router-dom';
|
import { useService } from '@toeverything/infra';
|
||||||
import { useLoaderData, useSearchParams } from 'react-router-dom';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
interface LoaderData {
|
import { AppContainer } from '../../components/app-container';
|
||||||
action: 'url' | 'signin-redirect';
|
|
||||||
currentUser?: GetCurrentUserQuery['currentUser'];
|
|
||||||
}
|
|
||||||
|
|
||||||
const OpenUrl = () => {
|
const OpenUrl = () => {
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
@@ -33,8 +32,11 @@ const OpenUrl = () => {
|
|||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
const OpenOAuthJwt = () => {
|
const OpenOAuthJwt = () => {
|
||||||
const { currentUser } = useLoaderData() as LoaderData;
|
const [currentUser, setCurrentUser] = useState<
|
||||||
|
GetCurrentUserQuery['currentUser'] | null
|
||||||
|
>(null);
|
||||||
const [params] = useSearchParams();
|
const [params] = useSearchParams();
|
||||||
|
const graphqlService = useService(GraphQLService);
|
||||||
|
|
||||||
const maybeScheme = appSchemes.safeParse(params.get('scheme'));
|
const maybeScheme = appSchemes.safeParse(params.get('scheme'));
|
||||||
const scheme = maybeScheme.success
|
const scheme = maybeScheme.success
|
||||||
@@ -42,8 +44,19 @@ const OpenOAuthJwt = () => {
|
|||||||
: channelToScheme[BUILD_CONFIG.appBuildType];
|
: channelToScheme[BUILD_CONFIG.appBuildType];
|
||||||
const next = params.get('next');
|
const next = params.get('next');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
graphqlService
|
||||||
|
.gql({
|
||||||
|
query: getCurrentUserQuery,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
setCurrentUser(res?.currentUser || null);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}, [graphqlService]);
|
||||||
|
|
||||||
if (!currentUser || !currentUser?.token?.sessionToken) {
|
if (!currentUser || !currentUser?.token?.sessionToken) {
|
||||||
return null;
|
return <AppContainer fallback />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const urlToOpen = `${scheme}://signin-redirect?token=${
|
const urlToOpen = `${scheme}://signin-redirect?token=${
|
||||||
@@ -54,7 +67,8 @@ const OpenOAuthJwt = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const { action } = useLoaderData() as LoaderData;
|
const params = useParams<{ action: string }>();
|
||||||
|
const action = params.action || '';
|
||||||
|
|
||||||
if (action === 'url') {
|
if (action === 'url') {
|
||||||
return <OpenUrl />;
|
return <OpenUrl />;
|
||||||
@@ -63,22 +77,3 @@ export const Component = () => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loader: LoaderFunction = async args => {
|
|
||||||
const action = args.params.action || '';
|
|
||||||
|
|
||||||
if (action === 'signin-redirect') {
|
|
||||||
const res = await fetcher({
|
|
||||||
query: getCurrentUserQuery,
|
|
||||||
}).catch(console.error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
action,
|
|
||||||
currentUser: res?.currentUser || null,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
action,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { NotificationCenter } from '@affine/component';
|
import { NotificationCenter } from '@affine/component';
|
||||||
|
import { DefaultServerService } from '@affine/core/modules/cloud';
|
||||||
|
import { FrameworkScope, useService } from '@toeverything/infra';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { Outlet } from 'react-router-dom';
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
import { GlobalDialogs } from '../../dialogs';
|
import { GlobalDialogs } from '../../dialogs';
|
||||||
@@ -6,13 +9,34 @@ import { CustomThemeModifier } from './custom-theme';
|
|||||||
import { FindInPageModal } from './find-in-page/find-in-page-modal';
|
import { FindInPageModal } from './find-in-page/find-in-page-modal';
|
||||||
|
|
||||||
export const RootWrapper = () => {
|
export const RootWrapper = () => {
|
||||||
|
const defaultServerService = useService(DefaultServerService);
|
||||||
|
const [isServerReady, setIsServerReady] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isServerReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const abortController = new AbortController();
|
||||||
|
defaultServerService.server
|
||||||
|
.waitForConfigRevalidation(abortController.signal)
|
||||||
|
.then(() => {
|
||||||
|
setIsServerReady(true);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
abortController.abort();
|
||||||
|
};
|
||||||
|
}, [defaultServerService, isServerReady]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<FrameworkScope scope={defaultServerService.server.scope}>
|
||||||
<GlobalDialogs />
|
<GlobalDialogs />
|
||||||
<NotificationCenter />
|
<NotificationCenter />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<CustomThemeModifier />
|
<CustomThemeModifier />
|
||||||
{BUILD_CONFIG.isElectron && <FindInPageModal />}
|
{BUILD_CONFIG.isElectron && <FindInPageModal />}
|
||||||
</>
|
</FrameworkScope>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import {
|
import { ServerService, SubscriptionService } from '@affine/core/modules/cloud';
|
||||||
ServerConfigService,
|
|
||||||
SubscriptionService,
|
|
||||||
} from '@affine/core/modules/cloud';
|
|
||||||
import { SubscriptionPlan } from '@affine/graphql';
|
import { SubscriptionPlan } from '@affine/graphql';
|
||||||
import { useLiveData, useServices } from '@toeverything/infra';
|
import { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@@ -13,12 +10,12 @@ export const UserPlanTag = forwardRef<
|
|||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
HTMLProps<HTMLDivElement>
|
HTMLProps<HTMLDivElement>
|
||||||
>(function UserPlanTag({ className, ...attrs }, ref) {
|
>(function UserPlanTag({ className, ...attrs }, ref) {
|
||||||
const { serverConfigService, subscriptionService } = useServices({
|
const { serverService, subscriptionService } = useServices({
|
||||||
ServerConfigService,
|
ServerService,
|
||||||
SubscriptionService,
|
SubscriptionService,
|
||||||
});
|
});
|
||||||
const hasPayment = useLiveData(
|
const hasPayment = useLiveData(
|
||||||
serverConfigService.serverConfig.features$.map(r => r?.payment)
|
serverService.server.features$.map(r => r?.payment)
|
||||||
);
|
);
|
||||||
const plan = useLiveData(
|
const plan = useLiveData(
|
||||||
subscriptionService.subscription.pro$.map(subscription =>
|
subscriptionService.subscription.pro$.map(subscription =>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Skeleton } from '@affine/component';
|
import { Skeleton } from '@affine/component';
|
||||||
import {
|
import {
|
||||||
AuthService,
|
AuthService,
|
||||||
ServerConfigService,
|
ServerService,
|
||||||
UserCopilotQuotaService,
|
UserCopilotQuotaService,
|
||||||
UserQuotaService,
|
UserQuotaService,
|
||||||
} from '@affine/core/modules/cloud';
|
} from '@affine/core/modules/cloud';
|
||||||
@@ -71,10 +71,8 @@ const Loading = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const UsagePanel = () => {
|
const UsagePanel = () => {
|
||||||
const serverConfigService = useService(ServerConfigService);
|
const serverService = useService(ServerService);
|
||||||
const serverFeatures = useLiveData(
|
const serverFeatures = useLiveData(serverService.server.features$);
|
||||||
serverConfigService.serverConfig.features$
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingGroup title="Storage" contentStyle={{ padding: '10px 16px' }}>
|
<SettingGroup title="Storage" contentStyle={{ padding: '10px 16px' }}>
|
||||||
|
|||||||
40
packages/frontend/core/src/mobile/pages/root/index.tsx
Normal file
40
packages/frontend/core/src/mobile/pages/root/index.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { NotificationCenter } from '@affine/component';
|
||||||
|
import { DefaultServerService } from '@affine/core/modules/cloud';
|
||||||
|
import { FrameworkScope, useService } from '@toeverything/infra';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { GlobalDialogs } from '../../dialogs';
|
||||||
|
import { MobileSignInModal } from '../../views/sign-in/modal';
|
||||||
|
|
||||||
|
export const RootWrapper = () => {
|
||||||
|
const defaultServerService = useService(DefaultServerService);
|
||||||
|
const [isServerReady, setIsServerReady] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isServerReady) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const abortController = new AbortController();
|
||||||
|
defaultServerService.server
|
||||||
|
.waitForConfigRevalidation(abortController.signal)
|
||||||
|
.then(() => {
|
||||||
|
setIsServerReady(true);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
abortController.abort();
|
||||||
|
};
|
||||||
|
}, [defaultServerService, isServerReady]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FrameworkScope scope={defaultServerService.server.scope}>
|
||||||
|
<GlobalDialogs />
|
||||||
|
<NotificationCenter />
|
||||||
|
<MobileSignInModal />
|
||||||
|
<Outlet />
|
||||||
|
</FrameworkScope>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
import { NotificationCenter } from '@affine/component';
|
|
||||||
import { NavigateContext } from '@affine/core/components/hooks/use-navigate-helper';
|
import { NavigateContext } from '@affine/core/components/hooks/use-navigate-helper';
|
||||||
import { wrapCreateBrowserRouter } from '@sentry/react';
|
import { wrapCreateBrowserRouter } from '@sentry/react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import type { RouteObject } from 'react-router-dom';
|
import type { RouteObject } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
createBrowserRouter as reactRouterCreateBrowserRouter,
|
createBrowserRouter as reactRouterCreateBrowserRouter,
|
||||||
Outlet,
|
|
||||||
redirect,
|
redirect,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
useNavigate,
|
useNavigate,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
|
|
||||||
import { GlobalDialogs } from './dialogs';
|
import { RootWrapper } from './pages/root';
|
||||||
import { MobileSignInModal } from './views/sign-in/modal';
|
|
||||||
|
|
||||||
function RootRouter() {
|
function RootRouter() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -25,10 +22,7 @@ function RootRouter() {
|
|||||||
return (
|
return (
|
||||||
ready && (
|
ready && (
|
||||||
<NavigateContext.Provider value={navigate}>
|
<NavigateContext.Provider value={navigate}>
|
||||||
<GlobalDialogs />
|
<RootWrapper />
|
||||||
<NotificationCenter />
|
|
||||||
<MobileSignInModal />
|
|
||||||
<Outlet />
|
|
||||||
</NavigateContext.Provider>
|
</NavigateContext.Provider>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
152
packages/frontend/core/src/modules/cloud/constant.ts
Normal file
152
packages/frontend/core/src/modules/cloud/constant.ts
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import {
|
||||||
|
OAuthProviderType,
|
||||||
|
ServerDeploymentType,
|
||||||
|
ServerFeature,
|
||||||
|
} from '@affine/graphql';
|
||||||
|
|
||||||
|
import type { ServerConfig, ServerMetadata } from './types';
|
||||||
|
|
||||||
|
export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] =
|
||||||
|
environment.isSelfHosted
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 'affine-cloud',
|
||||||
|
baseUrl: location.origin,
|
||||||
|
// selfhosted baseUrl is `location.origin`
|
||||||
|
// this is ok for web app, but not for desktop app
|
||||||
|
// since we never build desktop app in selfhosted mode, so it's fine
|
||||||
|
config: {
|
||||||
|
serverName: 'Affine Selfhost',
|
||||||
|
features: [],
|
||||||
|
oauthProviders: [],
|
||||||
|
type: ServerDeploymentType.Selfhosted,
|
||||||
|
credentialsRequirement: {
|
||||||
|
password: {
|
||||||
|
minLength: 8,
|
||||||
|
maxLength: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: BUILD_CONFIG.debug
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 'affine-cloud',
|
||||||
|
baseUrl: 'http://localhost:8080',
|
||||||
|
config: {
|
||||||
|
serverName: 'Affine Cloud',
|
||||||
|
features: [
|
||||||
|
ServerFeature.Captcha,
|
||||||
|
ServerFeature.Copilot,
|
||||||
|
ServerFeature.OAuth,
|
||||||
|
ServerFeature.Payment,
|
||||||
|
],
|
||||||
|
oauthProviders: [OAuthProviderType.Google],
|
||||||
|
type: ServerDeploymentType.Affine,
|
||||||
|
credentialsRequirement: {
|
||||||
|
password: {
|
||||||
|
minLength: 8,
|
||||||
|
maxLength: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: BUILD_CONFIG.appBuildType === 'stable'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 'affine-cloud',
|
||||||
|
baseUrl: 'https://app.affine.pro',
|
||||||
|
config: {
|
||||||
|
serverName: 'Affine Cloud',
|
||||||
|
features: [
|
||||||
|
ServerFeature.Captcha,
|
||||||
|
ServerFeature.Copilot,
|
||||||
|
ServerFeature.OAuth,
|
||||||
|
ServerFeature.Payment,
|
||||||
|
],
|
||||||
|
oauthProviders: [OAuthProviderType.Google],
|
||||||
|
type: ServerDeploymentType.Affine,
|
||||||
|
credentialsRequirement: {
|
||||||
|
password: {
|
||||||
|
minLength: 8,
|
||||||
|
maxLength: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: BUILD_CONFIG.appBuildType === 'beta'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 'affine-cloud',
|
||||||
|
baseUrl: 'https://insider.affine.pro',
|
||||||
|
config: {
|
||||||
|
serverName: 'Affine Cloud',
|
||||||
|
features: [
|
||||||
|
ServerFeature.Captcha,
|
||||||
|
ServerFeature.Copilot,
|
||||||
|
ServerFeature.OAuth,
|
||||||
|
ServerFeature.Payment,
|
||||||
|
],
|
||||||
|
oauthProviders: [OAuthProviderType.Google],
|
||||||
|
type: ServerDeploymentType.Affine,
|
||||||
|
credentialsRequirement: {
|
||||||
|
password: {
|
||||||
|
minLength: 8,
|
||||||
|
maxLength: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: BUILD_CONFIG.appBuildType === 'internal'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 'affine-cloud',
|
||||||
|
baseUrl: 'https://insider.affine.pro',
|
||||||
|
config: {
|
||||||
|
serverName: 'Affine Cloud',
|
||||||
|
features: [
|
||||||
|
ServerFeature.Captcha,
|
||||||
|
ServerFeature.Copilot,
|
||||||
|
ServerFeature.OAuth,
|
||||||
|
ServerFeature.Payment,
|
||||||
|
],
|
||||||
|
oauthProviders: [OAuthProviderType.Google],
|
||||||
|
type: ServerDeploymentType.Affine,
|
||||||
|
credentialsRequirement: {
|
||||||
|
password: {
|
||||||
|
minLength: 8,
|
||||||
|
maxLength: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: BUILD_CONFIG.appBuildType === 'canary'
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: 'affine-cloud',
|
||||||
|
baseUrl: 'https://affine.fail',
|
||||||
|
config: {
|
||||||
|
serverName: 'Affine Cloud',
|
||||||
|
features: [
|
||||||
|
ServerFeature.Captcha,
|
||||||
|
ServerFeature.Copilot,
|
||||||
|
ServerFeature.OAuth,
|
||||||
|
ServerFeature.Payment,
|
||||||
|
],
|
||||||
|
oauthProviders: [OAuthProviderType.Google],
|
||||||
|
type: ServerDeploymentType.Affine,
|
||||||
|
credentialsRequirement: {
|
||||||
|
password: {
|
||||||
|
minLength: 8,
|
||||||
|
maxLength: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import type {
|
|
||||||
OauthProvidersQuery,
|
|
||||||
ServerConfigQuery,
|
|
||||||
ServerFeature,
|
|
||||||
} from '@affine/graphql';
|
|
||||||
import {
|
|
||||||
backoffRetry,
|
|
||||||
effect,
|
|
||||||
Entity,
|
|
||||||
fromPromise,
|
|
||||||
LiveData,
|
|
||||||
} from '@toeverything/infra';
|
|
||||||
import { EMPTY, exhaustMap, mergeMap } from 'rxjs';
|
|
||||||
|
|
||||||
import type { ServerConfigStore } from '../stores/server-config';
|
|
||||||
|
|
||||||
type LowercaseServerFeature = Lowercase<ServerFeature>;
|
|
||||||
type ServerFeatureRecord = {
|
|
||||||
[key in LowercaseServerFeature]: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ServerConfigType = ServerConfigQuery['serverConfig'] &
|
|
||||||
OauthProvidersQuery['serverConfig'];
|
|
||||||
|
|
||||||
export class ServerConfig extends Entity {
|
|
||||||
readonly config$ = new LiveData<ServerConfigType | null>(null);
|
|
||||||
|
|
||||||
readonly features$ = this.config$.map(config => {
|
|
||||||
return config
|
|
||||||
? Array.from(new Set(config.features)).reduce((acc, cur) => {
|
|
||||||
acc[cur.toLowerCase() as LowercaseServerFeature] = true;
|
|
||||||
return acc;
|
|
||||||
}, {} as ServerFeatureRecord)
|
|
||||||
: null;
|
|
||||||
});
|
|
||||||
|
|
||||||
readonly credentialsRequirement$ = this.config$.map(config => {
|
|
||||||
return config ? config.credentialsRequirement : null;
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(private readonly store: ServerConfigStore) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
revalidate = effect(
|
|
||||||
exhaustMap(() => {
|
|
||||||
return fromPromise<ServerConfigType>(signal =>
|
|
||||||
this.store.fetchServerConfig(signal)
|
|
||||||
).pipe(
|
|
||||||
backoffRetry({
|
|
||||||
count: Infinity,
|
|
||||||
}),
|
|
||||||
mergeMap(config => {
|
|
||||||
this.config$.next(config);
|
|
||||||
return EMPTY;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
revalidateIfNeeded = () => {
|
|
||||||
if (!this.config$.value) {
|
|
||||||
this.revalidate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
override dispose(): void {
|
|
||||||
this.revalidate.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
110
packages/frontend/core/src/modules/cloud/entities/server.ts
Normal file
110
packages/frontend/core/src/modules/cloud/entities/server.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import type { ServerFeature } from '@affine/graphql';
|
||||||
|
import {
|
||||||
|
backoffRetry,
|
||||||
|
effect,
|
||||||
|
Entity,
|
||||||
|
fromPromise,
|
||||||
|
LiveData,
|
||||||
|
onComplete,
|
||||||
|
onStart,
|
||||||
|
} from '@toeverything/infra';
|
||||||
|
import { EMPTY, exhaustMap, map, mergeMap } from 'rxjs';
|
||||||
|
|
||||||
|
import { ServerScope } from '../scopes/server';
|
||||||
|
import { FetchService } from '../services/fetch';
|
||||||
|
import { GraphQLService } from '../services/graphql';
|
||||||
|
import { ServerConfigStore } from '../stores/server-config';
|
||||||
|
import type { ServerListStore } from '../stores/server-list';
|
||||||
|
import type { ServerConfig, ServerMetadata } from '../types';
|
||||||
|
|
||||||
|
type LowercaseServerFeature = Lowercase<ServerFeature>;
|
||||||
|
type ServerFeatureRecord = {
|
||||||
|
[key in LowercaseServerFeature]: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Server extends Entity<{
|
||||||
|
serverMetadata: ServerMetadata;
|
||||||
|
}> {
|
||||||
|
readonly id = this.props.serverMetadata.id;
|
||||||
|
readonly baseUrl = this.props.serverMetadata.baseUrl;
|
||||||
|
readonly scope = this.framework.createScope(ServerScope, {
|
||||||
|
server: this as Server,
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly serverConfigStore = this.scope.framework.get(ServerConfigStore);
|
||||||
|
readonly fetch = this.scope.framework.get(FetchService).fetch;
|
||||||
|
readonly gql = this.scope.framework.get(GraphQLService).gql;
|
||||||
|
readonly serverMetadata = this.props.serverMetadata;
|
||||||
|
|
||||||
|
constructor(private readonly serverListStore: ServerListStore) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly config$ = LiveData.from<ServerConfig>(
|
||||||
|
this.serverListStore.watchServerConfig(this.serverMetadata.id).pipe(
|
||||||
|
map(config => {
|
||||||
|
if (!config) {
|
||||||
|
throw new Error('Failed to load server config');
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
})
|
||||||
|
),
|
||||||
|
null as any
|
||||||
|
);
|
||||||
|
|
||||||
|
readonly isConfigRevalidating$ = new LiveData(false);
|
||||||
|
|
||||||
|
readonly features$ = this.config$.map(config => {
|
||||||
|
return Array.from(new Set(config.features)).reduce((acc, cur) => {
|
||||||
|
acc[cur.toLowerCase() as LowercaseServerFeature] = true;
|
||||||
|
return acc;
|
||||||
|
}, {} as ServerFeatureRecord);
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly credentialsRequirement$ = this.config$.map(config => {
|
||||||
|
return config ? config.credentialsRequirement : null;
|
||||||
|
});
|
||||||
|
|
||||||
|
readonly revalidateConfig = effect(
|
||||||
|
exhaustMap(() => {
|
||||||
|
return fromPromise(signal =>
|
||||||
|
this.serverConfigStore.fetchServerConfig(signal)
|
||||||
|
).pipe(
|
||||||
|
backoffRetry({
|
||||||
|
count: Infinity,
|
||||||
|
}),
|
||||||
|
mergeMap(config => {
|
||||||
|
this.serverListStore.updateServerConfig(this.serverMetadata.id, {
|
||||||
|
credentialsRequirement: config.credentialsRequirement,
|
||||||
|
features: config.features,
|
||||||
|
oauthProviders: config.oauthProviders,
|
||||||
|
serverName: config.name,
|
||||||
|
type: config.type,
|
||||||
|
version: config.version,
|
||||||
|
initialized: config.initialized,
|
||||||
|
});
|
||||||
|
return EMPTY;
|
||||||
|
}),
|
||||||
|
onStart(() => {
|
||||||
|
this.isConfigRevalidating$.next(true);
|
||||||
|
}),
|
||||||
|
onComplete(() => {
|
||||||
|
this.isConfigRevalidating$.next(false);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
async waitForConfigRevalidation(signal?: AbortSignal) {
|
||||||
|
this.revalidateConfig();
|
||||||
|
await this.isConfigRevalidating$.waitFor(
|
||||||
|
isRevalidating => !isRevalidating,
|
||||||
|
signal
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override dispose(): void {
|
||||||
|
this.scope.dispose();
|
||||||
|
this.revalidateConfig.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { exhaustMap } from 'rxjs';
|
import { exhaustMap } from 'rxjs';
|
||||||
|
|
||||||
import { isBackendError, isNetworkError } from '../error';
|
import { isBackendError, isNetworkError } from '../error';
|
||||||
import type { ServerConfigService } from '../services/server-config';
|
import type { ServerService } from '../services/server';
|
||||||
import type { SubscriptionStore } from '../stores/subscription';
|
import type { SubscriptionStore } from '../stores/subscription';
|
||||||
|
|
||||||
export class SubscriptionPrices extends Entity {
|
export class SubscriptionPrices extends Entity {
|
||||||
@@ -35,7 +35,7 @@ export class SubscriptionPrices extends Entity {
|
|||||||
);
|
);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly serverConfigService: ServerConfigService,
|
private readonly serverService: ServerService,
|
||||||
private readonly store: SubscriptionStore
|
private readonly store: SubscriptionStore
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -44,13 +44,7 @@ export class SubscriptionPrices extends Entity {
|
|||||||
revalidate = effect(
|
revalidate = effect(
|
||||||
exhaustMap(() => {
|
exhaustMap(() => {
|
||||||
return fromPromise(async signal => {
|
return fromPromise(async signal => {
|
||||||
// ensure server config is loaded
|
const serverConfig = this.serverService.server.features$.value;
|
||||||
this.serverConfigService.serverConfig.revalidateIfNeeded();
|
|
||||||
|
|
||||||
const serverConfig =
|
|
||||||
await this.serverConfigService.serverConfig.features$.waitForNonNull(
|
|
||||||
signal
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!serverConfig.payment) {
|
if (!serverConfig.payment) {
|
||||||
// No payment feature, no subscription
|
// No payment feature, no subscription
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { EMPTY, map, mergeMap } from 'rxjs';
|
|||||||
|
|
||||||
import { isBackendError, isNetworkError } from '../error';
|
import { isBackendError, isNetworkError } from '../error';
|
||||||
import type { AuthService } from '../services/auth';
|
import type { AuthService } from '../services/auth';
|
||||||
import type { ServerConfigService } from '../services/server-config';
|
import type { ServerService } from '../services/server';
|
||||||
import type { SubscriptionStore } from '../stores/subscription';
|
import type { SubscriptionStore } from '../stores/subscription';
|
||||||
|
|
||||||
export type SubscriptionType = NonNullable<
|
export type SubscriptionType = NonNullable<
|
||||||
@@ -54,7 +54,7 @@ export class Subscription extends Entity {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
private readonly serverConfigService: ServerConfigService,
|
private readonly serverService: ServerService,
|
||||||
private readonly store: SubscriptionStore
|
private readonly store: SubscriptionStore
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
@@ -100,9 +100,7 @@ export class Subscription extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const serverConfig =
|
const serverConfig =
|
||||||
await this.serverConfigService.serverConfig.features$.waitForNonNull(
|
await this.serverService.server.features$.waitForNonNull(signal);
|
||||||
signal
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!serverConfig.payment) {
|
if (!serverConfig.payment) {
|
||||||
// No payment feature, no subscription
|
// No payment feature, no subscription
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { EMPTY, map, mergeMap } from 'rxjs';
|
|||||||
|
|
||||||
import { isBackendError, isNetworkError } from '../error';
|
import { isBackendError, isNetworkError } from '../error';
|
||||||
import type { AuthService } from '../services/auth';
|
import type { AuthService } from '../services/auth';
|
||||||
import type { ServerConfigService } from '../services/server-config';
|
import type { ServerService } from '../services/server';
|
||||||
import type { UserCopilotQuotaStore } from '../stores/user-copilot-quota';
|
import type { UserCopilotQuotaStore } from '../stores/user-copilot-quota';
|
||||||
|
|
||||||
export class UserCopilotQuota extends Entity {
|
export class UserCopilotQuota extends Entity {
|
||||||
@@ -26,7 +26,7 @@ export class UserCopilotQuota extends Entity {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
private readonly store: UserCopilotQuotaStore,
|
private readonly store: UserCopilotQuotaStore,
|
||||||
private readonly serverConfigService: ServerConfigService
|
private readonly serverService: ServerService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -44,9 +44,7 @@ export class UserCopilotQuota extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const serverConfig =
|
const serverConfig =
|
||||||
await this.serverConfigService.serverConfig.features$.waitForNonNull(
|
await this.serverService.server.features$.waitForNonNull(signal);
|
||||||
signal
|
|
||||||
);
|
|
||||||
|
|
||||||
let aiQuota = null;
|
let aiQuota = null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export type { Invoice } from './entities/invoices';
|
export type { Invoice } from './entities/invoices';
|
||||||
|
export { Server } from './entities/server';
|
||||||
export type { AuthAccountInfo } from './entities/session';
|
export type { AuthAccountInfo } from './entities/session';
|
||||||
export {
|
export {
|
||||||
BackendError,
|
BackendError,
|
||||||
@@ -6,19 +7,23 @@ export {
|
|||||||
isNetworkError,
|
isNetworkError,
|
||||||
NetworkError,
|
NetworkError,
|
||||||
} from './error';
|
} from './error';
|
||||||
|
export { RawFetchProvider } from './provider/fetch';
|
||||||
export { ValidatorProvider } from './provider/validator';
|
export { ValidatorProvider } from './provider/validator';
|
||||||
export { WebSocketAuthProvider } from './provider/websocket-auth';
|
export { WebSocketAuthProvider } from './provider/websocket-auth';
|
||||||
export { AccountChanged, AuthService } from './services/auth';
|
export { AccountChanged, AuthService } from './services/auth';
|
||||||
export { CaptchaService } from './services/captcha';
|
export { CaptchaService } from './services/captcha';
|
||||||
|
export { DefaultServerService } from './services/default-server';
|
||||||
export { FetchService } from './services/fetch';
|
export { FetchService } from './services/fetch';
|
||||||
export { GraphQLService } from './services/graphql';
|
export { GraphQLService } from './services/graphql';
|
||||||
export { InvoicesService } from './services/invoices';
|
export { InvoicesService } from './services/invoices';
|
||||||
export { ServerConfigService } from './services/server-config';
|
export { ServerService } from './services/server';
|
||||||
|
export { ServersService } from './services/servers';
|
||||||
export { SubscriptionService } from './services/subscription';
|
export { SubscriptionService } from './services/subscription';
|
||||||
export { UserCopilotQuotaService } from './services/user-copilot-quota';
|
export { UserCopilotQuotaService } from './services/user-copilot-quota';
|
||||||
export { UserFeatureService } from './services/user-feature';
|
export { UserFeatureService } from './services/user-feature';
|
||||||
export { UserQuotaService } from './services/user-quota';
|
export { UserQuotaService } from './services/user-quota';
|
||||||
export { WebSocketService } from './services/websocket';
|
export { WebSocketService } from './services/websocket';
|
||||||
|
export { WorkspaceServerService } from './services/workspace-server';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DocScope,
|
DocScope,
|
||||||
@@ -26,38 +31,44 @@ import {
|
|||||||
type Framework,
|
type Framework,
|
||||||
GlobalCache,
|
GlobalCache,
|
||||||
GlobalState,
|
GlobalState,
|
||||||
|
GlobalStateService,
|
||||||
WorkspaceScope,
|
WorkspaceScope,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
import { UrlService } from '../url';
|
import { UrlService } from '../url';
|
||||||
import { CloudDocMeta } from './entities/cloud-doc-meta';
|
import { CloudDocMeta } from './entities/cloud-doc-meta';
|
||||||
import { Invoices } from './entities/invoices';
|
import { Invoices } from './entities/invoices';
|
||||||
import { ServerConfig } from './entities/server-config';
|
import { Server } from './entities/server';
|
||||||
import { AuthSession } from './entities/session';
|
import { AuthSession } from './entities/session';
|
||||||
import { Subscription } from './entities/subscription';
|
import { Subscription } from './entities/subscription';
|
||||||
import { SubscriptionPrices } from './entities/subscription-prices';
|
import { SubscriptionPrices } from './entities/subscription-prices';
|
||||||
import { UserCopilotQuota } from './entities/user-copilot-quota';
|
import { UserCopilotQuota } from './entities/user-copilot-quota';
|
||||||
import { UserFeature } from './entities/user-feature';
|
import { UserFeature } from './entities/user-feature';
|
||||||
import { UserQuota } from './entities/user-quota';
|
import { UserQuota } from './entities/user-quota';
|
||||||
import { DefaultFetchProvider, FetchProvider } from './provider/fetch';
|
import { DefaultRawFetchProvider, RawFetchProvider } from './provider/fetch';
|
||||||
import { ValidatorProvider } from './provider/validator';
|
import { ValidatorProvider } from './provider/validator';
|
||||||
import { WebSocketAuthProvider } from './provider/websocket-auth';
|
import { WebSocketAuthProvider } from './provider/websocket-auth';
|
||||||
|
import { ServerScope } from './scopes/server';
|
||||||
import { AuthService } from './services/auth';
|
import { AuthService } from './services/auth';
|
||||||
import { CaptchaService } from './services/captcha';
|
import { CaptchaService } from './services/captcha';
|
||||||
import { CloudDocMetaService } from './services/cloud-doc-meta';
|
import { CloudDocMetaService } from './services/cloud-doc-meta';
|
||||||
|
import { DefaultServerService } from './services/default-server';
|
||||||
import { FetchService } from './services/fetch';
|
import { FetchService } from './services/fetch';
|
||||||
import { GraphQLService } from './services/graphql';
|
import { GraphQLService } from './services/graphql';
|
||||||
import { InvoicesService } from './services/invoices';
|
import { InvoicesService } from './services/invoices';
|
||||||
import { ServerConfigService } from './services/server-config';
|
import { ServerService } from './services/server';
|
||||||
|
import { ServersService } from './services/servers';
|
||||||
import { SubscriptionService } from './services/subscription';
|
import { SubscriptionService } from './services/subscription';
|
||||||
import { UserCopilotQuotaService } from './services/user-copilot-quota';
|
import { UserCopilotQuotaService } from './services/user-copilot-quota';
|
||||||
import { UserFeatureService } from './services/user-feature';
|
import { UserFeatureService } from './services/user-feature';
|
||||||
import { UserQuotaService } from './services/user-quota';
|
import { UserQuotaService } from './services/user-quota';
|
||||||
import { WebSocketService } from './services/websocket';
|
import { WebSocketService } from './services/websocket';
|
||||||
|
import { WorkspaceServerService } from './services/workspace-server';
|
||||||
import { AuthStore } from './stores/auth';
|
import { AuthStore } from './stores/auth';
|
||||||
import { CloudDocMetaStore } from './stores/cloud-doc-meta';
|
import { CloudDocMetaStore } from './stores/cloud-doc-meta';
|
||||||
import { InvoicesStore } from './stores/invoices';
|
import { InvoicesStore } from './stores/invoices';
|
||||||
import { ServerConfigStore } from './stores/server-config';
|
import { ServerConfigStore } from './stores/server-config';
|
||||||
|
import { ServerListStore } from './stores/server-list';
|
||||||
import { SubscriptionStore } from './stores/subscription';
|
import { SubscriptionStore } from './stores/subscription';
|
||||||
import { UserCopilotQuotaStore } from './stores/user-copilot-quota';
|
import { UserCopilotQuotaStore } from './stores/user-copilot-quota';
|
||||||
import { UserFeatureStore } from './stores/user-feature';
|
import { UserFeatureStore } from './stores/user-feature';
|
||||||
@@ -65,23 +76,28 @@ import { UserQuotaStore } from './stores/user-quota';
|
|||||||
|
|
||||||
export function configureCloudModule(framework: Framework) {
|
export function configureCloudModule(framework: Framework) {
|
||||||
framework
|
framework
|
||||||
.service(FetchService, [FetchProvider])
|
.impl(RawFetchProvider, DefaultRawFetchProvider)
|
||||||
.impl(FetchProvider, DefaultFetchProvider)
|
.service(ServersService, [ServerListStore])
|
||||||
|
.service(DefaultServerService, [ServersService])
|
||||||
|
.store(ServerListStore, [GlobalStateService])
|
||||||
|
.entity(Server, [ServerListStore])
|
||||||
|
.scope(ServerScope)
|
||||||
|
.service(ServerService, [ServerScope])
|
||||||
|
.service(FetchService, [RawFetchProvider, ServerService])
|
||||||
.service(GraphQLService, [FetchService])
|
.service(GraphQLService, [FetchService])
|
||||||
.service(
|
.service(
|
||||||
WebSocketService,
|
WebSocketService,
|
||||||
f =>
|
f =>
|
||||||
new WebSocketService(
|
new WebSocketService(
|
||||||
|
f.get(ServerService),
|
||||||
f.get(AuthService),
|
f.get(AuthService),
|
||||||
f.getOptional(WebSocketAuthProvider)
|
f.getOptional(WebSocketAuthProvider)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.service(ServerConfigService)
|
|
||||||
.entity(ServerConfig, [ServerConfigStore])
|
|
||||||
.store(ServerConfigStore, [GraphQLService])
|
.store(ServerConfigStore, [GraphQLService])
|
||||||
.service(CaptchaService, f => {
|
.service(CaptchaService, f => {
|
||||||
return new CaptchaService(
|
return new CaptchaService(
|
||||||
f.get(ServerConfigService),
|
f.get(ServerService),
|
||||||
f.get(FetchService),
|
f.get(FetchService),
|
||||||
f.getOptional(ValidatorProvider)
|
f.getOptional(ValidatorProvider)
|
||||||
);
|
);
|
||||||
@@ -90,9 +106,14 @@ export function configureCloudModule(framework: Framework) {
|
|||||||
.store(AuthStore, [FetchService, GraphQLService, GlobalState])
|
.store(AuthStore, [FetchService, GraphQLService, GlobalState])
|
||||||
.entity(AuthSession, [AuthStore])
|
.entity(AuthSession, [AuthStore])
|
||||||
.service(SubscriptionService, [SubscriptionStore])
|
.service(SubscriptionService, [SubscriptionStore])
|
||||||
.store(SubscriptionStore, [GraphQLService, GlobalCache, UrlService])
|
.store(SubscriptionStore, [
|
||||||
.entity(Subscription, [AuthService, ServerConfigService, SubscriptionStore])
|
GraphQLService,
|
||||||
.entity(SubscriptionPrices, [ServerConfigService, SubscriptionStore])
|
GlobalCache,
|
||||||
|
UrlService,
|
||||||
|
ServerService,
|
||||||
|
])
|
||||||
|
.entity(Subscription, [AuthService, ServerService, SubscriptionStore])
|
||||||
|
.entity(SubscriptionPrices, [ServerService, SubscriptionStore])
|
||||||
.service(UserQuotaService)
|
.service(UserQuotaService)
|
||||||
.store(UserQuotaStore, [GraphQLService])
|
.store(UserQuotaStore, [GraphQLService])
|
||||||
.entity(UserQuota, [AuthService, UserQuotaStore])
|
.entity(UserQuota, [AuthService, UserQuotaStore])
|
||||||
@@ -101,7 +122,7 @@ export function configureCloudModule(framework: Framework) {
|
|||||||
.entity(UserCopilotQuota, [
|
.entity(UserCopilotQuota, [
|
||||||
AuthService,
|
AuthService,
|
||||||
UserCopilotQuotaStore,
|
UserCopilotQuotaStore,
|
||||||
ServerConfigService,
|
ServerService,
|
||||||
])
|
])
|
||||||
.service(UserFeatureService)
|
.service(UserFeatureService)
|
||||||
.entity(UserFeature, [AuthService, UserFeatureStore])
|
.entity(UserFeature, [AuthService, UserFeatureStore])
|
||||||
@@ -114,4 +135,6 @@ export function configureCloudModule(framework: Framework) {
|
|||||||
.service(CloudDocMetaService)
|
.service(CloudDocMetaService)
|
||||||
.entity(CloudDocMeta, [CloudDocMetaStore, DocService, GlobalCache])
|
.entity(CloudDocMeta, [CloudDocMetaStore, DocService, GlobalCache])
|
||||||
.store(CloudDocMetaStore, [GraphQLService]);
|
.store(CloudDocMetaStore, [GraphQLService]);
|
||||||
|
|
||||||
|
framework.scope(WorkspaceScope).service(WorkspaceServerService);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ import { createIdentifier } from '@toeverything/infra';
|
|||||||
|
|
||||||
import type { FetchInit } from '../services/fetch';
|
import type { FetchInit } from '../services/fetch';
|
||||||
|
|
||||||
export interface FetchProvider {
|
export interface RawFetchProvider {
|
||||||
/**
|
/**
|
||||||
* standard fetch, in ios&android, we can use native fetch to implement this
|
* standard fetch, in ios&android, we can use native fetch to implement this
|
||||||
*/
|
*/
|
||||||
fetch: (input: string | URL, init?: FetchInit) => Promise<Response>;
|
fetch: (input: string | URL, init?: FetchInit) => Promise<Response>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const FetchProvider = createIdentifier<FetchProvider>('FetchProvider');
|
export const RawFetchProvider =
|
||||||
|
createIdentifier<RawFetchProvider>('FetchProvider');
|
||||||
|
|
||||||
export const DefaultFetchProvider = {
|
export const DefaultRawFetchProvider = {
|
||||||
fetch: globalThis.fetch.bind(globalThis),
|
fetch: globalThis.fetch.bind(globalThis),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Scope } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { Server } from '../entities/server';
|
||||||
|
|
||||||
|
export class ServerScope extends Scope<{ server: Server }> {
|
||||||
|
readonly server = this.props.server;
|
||||||
|
}
|
||||||
@@ -11,10 +11,10 @@ import { EMPTY, exhaustMap, mergeMap } from 'rxjs';
|
|||||||
|
|
||||||
import type { ValidatorProvider } from '../provider/validator';
|
import type { ValidatorProvider } from '../provider/validator';
|
||||||
import type { FetchService } from './fetch';
|
import type { FetchService } from './fetch';
|
||||||
import type { ServerConfigService } from './server-config';
|
import type { ServerService } from './server';
|
||||||
|
|
||||||
export class CaptchaService extends Service {
|
export class CaptchaService extends Service {
|
||||||
needCaptcha$ = this.serverConfigService.serverConfig.features$.map(
|
needCaptcha$ = this.serverService.server.features$.map(
|
||||||
r => r?.captcha || false
|
r => r?.captcha || false
|
||||||
);
|
);
|
||||||
challenge$ = new LiveData<string | undefined>(undefined);
|
challenge$ = new LiveData<string | undefined>(undefined);
|
||||||
@@ -23,7 +23,7 @@ export class CaptchaService extends Service {
|
|||||||
error$ = new LiveData<any | undefined>(undefined);
|
error$ = new LiveData<any | undefined>(undefined);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly serverConfigService: ServerConfigService,
|
private readonly serverService: ServerService,
|
||||||
private readonly fetchService: FetchService,
|
private readonly fetchService: FetchService,
|
||||||
public readonly validatorProvider?: ValidatorProvider
|
public readonly validatorProvider?: ValidatorProvider
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { ServerDeploymentType } from '@affine/graphql';
|
||||||
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { Server } from '../entities/server';
|
||||||
|
import type { ServersService } from './servers';
|
||||||
|
|
||||||
|
export class DefaultServerService extends Service {
|
||||||
|
readonly server: Server;
|
||||||
|
|
||||||
|
constructor(private readonly serversService: ServersService) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
// global server is always affine-cloud
|
||||||
|
const server = this.serversService.server$('affine-cloud').value;
|
||||||
|
if (!server) {
|
||||||
|
throw new Error('No server found');
|
||||||
|
}
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
async waitForSelfhostedServerConfig() {
|
||||||
|
if (this.server.config$.value.type === ServerDeploymentType.Selfhosted) {
|
||||||
|
await this.server.waitForConfigRevalidation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,22 +3,18 @@ import { UserFriendlyError } from '@affine/graphql';
|
|||||||
import { fromPromise, Service } from '@toeverything/infra';
|
import { fromPromise, Service } from '@toeverything/infra';
|
||||||
|
|
||||||
import { BackendError, NetworkError } from '../error';
|
import { BackendError, NetworkError } from '../error';
|
||||||
import type { FetchProvider } from '../provider/fetch';
|
import type { RawFetchProvider } from '../provider/fetch';
|
||||||
|
import type { ServerService } from './server';
|
||||||
export function getAffineCloudBaseUrl(): string {
|
|
||||||
if (BUILD_CONFIG.isElectron || BUILD_CONFIG.isIOS || BUILD_CONFIG.isAndroid) {
|
|
||||||
return BUILD_CONFIG.serverUrlPrefix;
|
|
||||||
}
|
|
||||||
const { protocol, hostname, port } = window.location;
|
|
||||||
return `${protocol}//${hostname}${port ? `:${port}` : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const logger = new DebugLogger('affine:fetch');
|
const logger = new DebugLogger('affine:fetch');
|
||||||
|
|
||||||
export type FetchInit = RequestInit & { timeout?: number };
|
export type FetchInit = RequestInit & { timeout?: number };
|
||||||
|
|
||||||
export class FetchService extends Service {
|
export class FetchService extends Service {
|
||||||
constructor(private readonly fetchProvider: FetchProvider) {
|
constructor(
|
||||||
|
private readonly fetchProvider: RawFetchProvider,
|
||||||
|
private readonly serverService: ServerService
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
rxFetch = (
|
rxFetch = (
|
||||||
@@ -55,7 +51,7 @@ export class FetchService extends Service {
|
|||||||
}, timeout);
|
}, timeout);
|
||||||
|
|
||||||
const res = await this.fetchProvider
|
const res = await this.fetchProvider
|
||||||
.fetch(new URL(input, getAffineCloudBaseUrl()), {
|
.fetch(new URL(input, this.serverService.server.serverMetadata.baseUrl), {
|
||||||
...init,
|
...init,
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { ApplicationStarted, OnEvent, Service } from '@toeverything/infra';
|
|
||||||
|
|
||||||
import { ServerConfig } from '../entities/server-config';
|
|
||||||
|
|
||||||
@OnEvent(ApplicationStarted, e => e.onApplicationStart)
|
|
||||||
export class ServerConfigService extends Service {
|
|
||||||
serverConfig = this.framework.createEntity(ServerConfig);
|
|
||||||
|
|
||||||
private onApplicationStart() {
|
|
||||||
this.serverConfig.revalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
10
packages/frontend/core/src/modules/cloud/services/server.ts
Normal file
10
packages/frontend/core/src/modules/cloud/services/server.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { ServerScope } from '../scopes/server';
|
||||||
|
|
||||||
|
export class ServerService extends Service {
|
||||||
|
readonly server = this.serverScope.server;
|
||||||
|
constructor(private readonly serverScope: ServerScope) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
55
packages/frontend/core/src/modules/cloud/services/servers.ts
Normal file
55
packages/frontend/core/src/modules/cloud/services/servers.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { LiveData, ObjectPool, Service } from '@toeverything/infra';
|
||||||
|
import { finalize, of, switchMap } from 'rxjs';
|
||||||
|
|
||||||
|
import { Server } from '../entities/server';
|
||||||
|
import type { ServerListStore } from '../stores/server-list';
|
||||||
|
import type { ServerConfig, ServerMetadata } from '../types';
|
||||||
|
|
||||||
|
export class ServersService extends Service {
|
||||||
|
constructor(private readonly serverListStore: ServerListStore) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
servers$ = LiveData.from<Server[]>(
|
||||||
|
this.serverListStore.watchServerList().pipe(
|
||||||
|
switchMap(metadatas => {
|
||||||
|
const refs = metadatas.map(metadata => {
|
||||||
|
const exists = this.serverPool.get(metadata.id);
|
||||||
|
if (exists) {
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
const server = this.framework.createEntity(Server, {
|
||||||
|
serverMetadata: metadata,
|
||||||
|
});
|
||||||
|
const ref = this.serverPool.put(metadata.id, server);
|
||||||
|
return ref;
|
||||||
|
});
|
||||||
|
|
||||||
|
return of(refs.map(ref => ref.obj)).pipe(
|
||||||
|
finalize(() => {
|
||||||
|
refs.forEach(ref => {
|
||||||
|
ref.release();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
[] as any
|
||||||
|
);
|
||||||
|
|
||||||
|
server$(id: string) {
|
||||||
|
return this.servers$.map(servers =>
|
||||||
|
servers.find(server => server.id === id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly serverPool = new ObjectPool<string, Server>({
|
||||||
|
onDelete(obj) {
|
||||||
|
obj.dispose();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
addServer(metadata: ServerMetadata, config: ServerConfig) {
|
||||||
|
this.serverListStore.addServer(metadata, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,14 +2,14 @@ import { ApplicationStarted, OnEvent, Service } from '@toeverything/infra';
|
|||||||
import { Manager } from 'socket.io-client';
|
import { Manager } from 'socket.io-client';
|
||||||
|
|
||||||
import type { WebSocketAuthProvider } from '../provider/websocket-auth';
|
import type { WebSocketAuthProvider } from '../provider/websocket-auth';
|
||||||
import { getAffineCloudBaseUrl } from '../services/fetch';
|
|
||||||
import type { AuthService } from './auth';
|
import type { AuthService } from './auth';
|
||||||
import { AccountChanged } from './auth';
|
import { AccountChanged } from './auth';
|
||||||
|
import type { ServerService } from './server';
|
||||||
|
|
||||||
@OnEvent(AccountChanged, e => e.update)
|
@OnEvent(AccountChanged, e => e.update)
|
||||||
@OnEvent(ApplicationStarted, e => e.update)
|
@OnEvent(ApplicationStarted, e => e.update)
|
||||||
export class WebSocketService extends Service {
|
export class WebSocketService extends Service {
|
||||||
ioManager: Manager = new Manager(`${getAffineCloudBaseUrl()}/`, {
|
ioManager: Manager = new Manager(`${this.serverService.server.baseUrl}/`, {
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
secure: location.protocol === 'https:',
|
secure: location.protocol === 'https:',
|
||||||
@@ -18,7 +18,7 @@ export class WebSocketService extends Service {
|
|||||||
auth: this.webSocketAuthProvider
|
auth: this.webSocketAuthProvider
|
||||||
? cb => {
|
? cb => {
|
||||||
this.webSocketAuthProvider
|
this.webSocketAuthProvider
|
||||||
?.getAuthToken(`${getAffineCloudBaseUrl()}/`)
|
?.getAuthToken(`${this.serverService.server.baseUrl}/`)
|
||||||
.then(v => {
|
.then(v => {
|
||||||
cb(v ?? {});
|
cb(v ?? {});
|
||||||
})
|
})
|
||||||
@@ -31,6 +31,7 @@ export class WebSocketService extends Service {
|
|||||||
refCount = 0;
|
refCount = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private readonly serverService: ServerService,
|
||||||
private readonly authService: AuthService,
|
private readonly authService: AuthService,
|
||||||
private readonly webSocketAuthProvider?: WebSocketAuthProvider
|
private readonly webSocketAuthProvider?: WebSocketAuthProvider
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { Server } from '../entities/server';
|
||||||
|
|
||||||
|
export class WorkspaceServerService extends Service {
|
||||||
|
server: Server | null = null;
|
||||||
|
|
||||||
|
bindServer(server: Server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
|
type OauthProvidersQuery,
|
||||||
oauthProvidersQuery,
|
oauthProvidersQuery,
|
||||||
|
type ServerConfigQuery,
|
||||||
serverConfigQuery,
|
serverConfigQuery,
|
||||||
ServerFeature,
|
ServerFeature,
|
||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { Store } from '@toeverything/infra';
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
import type { ServerConfigType } from '../entities/server-config';
|
|
||||||
import type { GraphQLService } from '../services/graphql';
|
import type { GraphQLService } from '../services/graphql';
|
||||||
|
|
||||||
|
export type ServerConfigType = ServerConfigQuery['serverConfig'] &
|
||||||
|
OauthProvidersQuery['serverConfig'];
|
||||||
|
|
||||||
export class ServerConfigStore extends Store {
|
export class ServerConfigStore extends Store {
|
||||||
constructor(private readonly gqlService: GraphQLService) {
|
constructor(private readonly gqlService: GraphQLService) {
|
||||||
super();
|
super();
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import type { GlobalStateService } from '@toeverything/infra';
|
||||||
|
import { Store } from '@toeverything/infra';
|
||||||
|
import { map } from 'rxjs';
|
||||||
|
|
||||||
|
import { BUILD_IN_SERVERS } from '../constant';
|
||||||
|
import type { ServerConfig, ServerMetadata } from '../types';
|
||||||
|
|
||||||
|
export class ServerListStore extends Store {
|
||||||
|
constructor(private readonly globalStateService: GlobalStateService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
watchServerList() {
|
||||||
|
return this.globalStateService.globalState
|
||||||
|
.watch<ServerMetadata[]>('serverList')
|
||||||
|
.pipe(
|
||||||
|
map(servers => {
|
||||||
|
const serverList = [...BUILD_IN_SERVERS, ...(servers ?? [])];
|
||||||
|
return serverList;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getServerList() {
|
||||||
|
return [
|
||||||
|
...BUILD_IN_SERVERS,
|
||||||
|
...(this.globalStateService.globalState.get<ServerMetadata[]>(
|
||||||
|
'serverList'
|
||||||
|
) ?? []),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
addServer(server: ServerMetadata, serverConfig: ServerConfig) {
|
||||||
|
this.updateServerConfig(server.id, serverConfig);
|
||||||
|
const oldServers =
|
||||||
|
this.globalStateService.globalState.get<ServerMetadata[]>('serverList') ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
this.globalStateService.globalState.set<ServerMetadata[]>('serverList', [
|
||||||
|
...oldServers,
|
||||||
|
server,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeServer(serverId: string) {
|
||||||
|
const oldServers =
|
||||||
|
this.globalStateService.globalState.get<ServerMetadata[]>('serverList') ??
|
||||||
|
[];
|
||||||
|
|
||||||
|
this.globalStateService.globalState.set<ServerMetadata[]>(
|
||||||
|
'serverList',
|
||||||
|
oldServers.filter(server => server.id !== serverId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
watchServerConfig(serverId: string) {
|
||||||
|
return this.globalStateService.globalState
|
||||||
|
.watch<ServerConfig>(`serverConfig:${serverId}`)
|
||||||
|
.pipe(
|
||||||
|
map(config => {
|
||||||
|
if (!config) {
|
||||||
|
return BUILD_IN_SERVERS.find(server => server.id === serverId)
|
||||||
|
?.config;
|
||||||
|
} else {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getServerConfig(serverId: string) {
|
||||||
|
return (
|
||||||
|
this.globalStateService.globalState.get<ServerConfig>(
|
||||||
|
`serverConfig:${serverId}`
|
||||||
|
) ?? BUILD_IN_SERVERS.find(server => server.id === serverId)?.config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateServerConfig(serverId: string, config: ServerConfig) {
|
||||||
|
this.globalStateService.globalState.set<ServerConfig>(
|
||||||
|
`serverConfig:${serverId}`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,18 +16,19 @@ import { Store } from '@toeverything/infra';
|
|||||||
|
|
||||||
import type { UrlService } from '../../url';
|
import type { UrlService } from '../../url';
|
||||||
import type { SubscriptionType } from '../entities/subscription';
|
import type { SubscriptionType } from '../entities/subscription';
|
||||||
import { getAffineCloudBaseUrl } from '../services/fetch';
|
|
||||||
import type { GraphQLService } from '../services/graphql';
|
import type { GraphQLService } from '../services/graphql';
|
||||||
|
import type { ServerService } from '../services/server';
|
||||||
|
|
||||||
const SUBSCRIPTION_CACHE_KEY = 'subscription:';
|
const SUBSCRIPTION_CACHE_KEY = 'subscription:';
|
||||||
|
|
||||||
const getDefaultSubscriptionSuccessCallbackLink = (
|
const getDefaultSubscriptionSuccessCallbackLink = (
|
||||||
|
baseUrl: string,
|
||||||
plan: SubscriptionPlan | null,
|
plan: SubscriptionPlan | null,
|
||||||
scheme?: string
|
scheme?: string
|
||||||
) => {
|
) => {
|
||||||
const path =
|
const path =
|
||||||
plan === SubscriptionPlan.AI ? '/ai-upgrade-success' : '/upgrade-success';
|
plan === SubscriptionPlan.AI ? '/ai-upgrade-success' : '/upgrade-success';
|
||||||
const urlString = getAffineCloudBaseUrl() + path;
|
const urlString = baseUrl + path;
|
||||||
const url = new URL(urlString);
|
const url = new URL(urlString);
|
||||||
if (scheme) {
|
if (scheme) {
|
||||||
url.searchParams.set('scheme', scheme);
|
url.searchParams.set('scheme', scheme);
|
||||||
@@ -39,7 +40,8 @@ export class SubscriptionStore extends Store {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly gqlService: GraphQLService,
|
private readonly gqlService: GraphQLService,
|
||||||
private readonly globalCache: GlobalCache,
|
private readonly globalCache: GlobalCache,
|
||||||
private readonly urlService: UrlService
|
private readonly urlService: UrlService,
|
||||||
|
private readonly serverService: ServerService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -132,6 +134,7 @@ export class SubscriptionStore extends Store {
|
|||||||
successCallbackLink:
|
successCallbackLink:
|
||||||
input.successCallbackLink ||
|
input.successCallbackLink ||
|
||||||
getDefaultSubscriptionSuccessCallbackLink(
|
getDefaultSubscriptionSuccessCallbackLink(
|
||||||
|
this.serverService.server.baseUrl,
|
||||||
input.plan,
|
input.plan,
|
||||||
this.urlService.getClientScheme()
|
this.urlService.getClientScheme()
|
||||||
),
|
),
|
||||||
|
|||||||
22
packages/frontend/core/src/modules/cloud/types.ts
Normal file
22
packages/frontend/core/src/modules/cloud/types.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type {
|
||||||
|
CredentialsRequirementType,
|
||||||
|
OAuthProviderType,
|
||||||
|
ServerDeploymentType,
|
||||||
|
ServerFeature,
|
||||||
|
} from '@affine/graphql';
|
||||||
|
|
||||||
|
export interface ServerMetadata {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
baseUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerConfig {
|
||||||
|
serverName: string;
|
||||||
|
features: ServerFeature[];
|
||||||
|
oauthProviders: OAuthProviderType[];
|
||||||
|
type: ServerDeploymentType;
|
||||||
|
initialized?: boolean;
|
||||||
|
version?: string;
|
||||||
|
credentialsRequirement: CredentialsRequirementType;
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
useNavigationType,
|
useNavigationType,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
|
|
||||||
import { AuthService } from '../../cloud';
|
import { AuthService, ServersService } from '../../cloud';
|
||||||
import type { DesktopApi } from '../entities/electron-api';
|
import type { DesktopApi } from '../entities/electron-api';
|
||||||
|
|
||||||
@OnEvent(ApplicationStarted, e => e.setupStartListener)
|
@OnEvent(ApplicationStarted, e => e.setupStartListener)
|
||||||
@@ -140,10 +140,19 @@ export class DesktopApiService extends Service {
|
|||||||
private setupAuthRequestEvent() {
|
private setupAuthRequestEvent() {
|
||||||
this.events.ui.onAuthenticationRequest(({ method, payload }) => {
|
this.events.ui.onAuthenticationRequest(({ method, payload }) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const authService = this.framework.get(AuthService);
|
|
||||||
if (!(await this.api.handler.ui.isActiveTab())) {
|
if (!(await this.api.handler.ui.isActiveTab())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: support multiple servers
|
||||||
|
const affineCloudServer = this.framework
|
||||||
|
.get(ServersService)
|
||||||
|
.server$('affine-cloud').value;
|
||||||
|
if (!affineCloudServer) {
|
||||||
|
throw new Error('Affine Cloud server not found');
|
||||||
|
}
|
||||||
|
const authService = affineCloudServer.scope.get(AuthService);
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'magic-link': {
|
case 'magic-link': {
|
||||||
const { email, token } = payload;
|
const { email, token } = payload;
|
||||||
|
|||||||
@@ -2,21 +2,29 @@ import type { GlobalState } from '@toeverything/infra';
|
|||||||
import { Service } from '@toeverything/infra';
|
import { Service } from '@toeverything/infra';
|
||||||
import { map, type Observable, switchMap } from 'rxjs';
|
import { map, type Observable, switchMap } from 'rxjs';
|
||||||
|
|
||||||
import type { UserDBService } from '../../userspace';
|
import type { ServersService } from '../../cloud';
|
||||||
|
import { UserDBService } from '../../userspace';
|
||||||
import type { EditorSettingProvider } from '../provider/editor-setting-provider';
|
import type { EditorSettingProvider } from '../provider/editor-setting-provider';
|
||||||
|
|
||||||
export class CurrentUserDBEditorSettingProvider
|
export class CurrentUserDBEditorSettingProvider
|
||||||
extends Service
|
extends Service
|
||||||
implements EditorSettingProvider
|
implements EditorSettingProvider
|
||||||
{
|
{
|
||||||
currentUserDB$ = this.userDBService.currentUserDB.db$;
|
private readonly currentUserDB$;
|
||||||
fallback = new GlobalStateEditorSettingProvider(this.globalState);
|
fallback = new GlobalStateEditorSettingProvider(this.globalState);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly userDBService: UserDBService,
|
public readonly serversService: ServersService,
|
||||||
public readonly globalState: GlobalState
|
public readonly globalState: GlobalState
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
const affineCloudServer = this.serversService.server$('affine-cloud').value; // TODO: support multiple servers
|
||||||
|
if (!affineCloudServer) {
|
||||||
|
throw new Error('affine-cloud server not found');
|
||||||
|
}
|
||||||
|
const userDBService = affineCloudServer.scope.get(UserDBService);
|
||||||
|
this.currentUserDB$ = userDBService.currentUserDB.db$;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(key: string, value: string): void {
|
set(key: string, value: string): void {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import {
|
|||||||
GlobalStateService,
|
GlobalStateService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { ServersService } from '../cloud';
|
||||||
import { DesktopApiService } from '../desktop-api';
|
import { DesktopApiService } from '../desktop-api';
|
||||||
import { I18n } from '../i18n';
|
import { I18n } from '../i18n';
|
||||||
import { UserDBService } from '../userspace';
|
|
||||||
import { EditorSetting } from './entities/editor-setting';
|
import { EditorSetting } from './entities/editor-setting';
|
||||||
import { CurrentUserDBEditorSettingProvider } from './impls/user-db';
|
import { CurrentUserDBEditorSettingProvider } from './impls/user-db';
|
||||||
import { EditorSettingProvider } from './provider/editor-setting-provider';
|
import { EditorSettingProvider } from './provider/editor-setting-provider';
|
||||||
@@ -21,7 +21,7 @@ export function configureEditorSettingModule(framework: Framework) {
|
|||||||
.service(EditorSettingService)
|
.service(EditorSettingService)
|
||||||
.entity(EditorSetting, [EditorSettingProvider])
|
.entity(EditorSetting, [EditorSettingProvider])
|
||||||
.impl(EditorSettingProvider, CurrentUserDBEditorSettingProvider, [
|
.impl(EditorSettingProvider, CurrentUserDBEditorSettingProvider, [
|
||||||
UserDBService,
|
ServersService,
|
||||||
GlobalState,
|
GlobalState,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
import { AuthService } from '../cloud';
|
import { WorkspaceServerService } from '../cloud';
|
||||||
import { FavoriteList } from './entities/favorite-list';
|
import { FavoriteList } from './entities/favorite-list';
|
||||||
import { FavoriteService } from './services/favorite';
|
import { FavoriteService } from './services/favorite';
|
||||||
import {
|
import {
|
||||||
@@ -27,7 +27,11 @@ export function configureFavoriteModule(framework: Framework) {
|
|||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.service(FavoriteService)
|
.service(FavoriteService)
|
||||||
.entity(FavoriteList, [FavoriteStore])
|
.entity(FavoriteList, [FavoriteStore])
|
||||||
.store(FavoriteStore, [AuthService, WorkspaceDBService, WorkspaceService])
|
.store(FavoriteStore, [
|
||||||
|
WorkspaceDBService,
|
||||||
|
WorkspaceService,
|
||||||
|
WorkspaceServerService,
|
||||||
|
])
|
||||||
.service(MigrationFavoriteItemsAdapter, [WorkspaceService])
|
.service(MigrationFavoriteItemsAdapter, [WorkspaceService])
|
||||||
.service(CompatibleFavoriteItemsAdapter, [FavoriteService]);
|
.service(CompatibleFavoriteItemsAdapter, [FavoriteService]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { WorkspaceDBService, WorkspaceService } from '@toeverything/infra';
|
|||||||
import { LiveData, Store } from '@toeverything/infra';
|
import { LiveData, Store } from '@toeverything/infra';
|
||||||
import { map } from 'rxjs';
|
import { map } from 'rxjs';
|
||||||
|
|
||||||
import type { AuthService } from '../../cloud';
|
import { AuthService, type WorkspaceServerService } from '../../cloud';
|
||||||
import type { FavoriteSupportType } from '../constant';
|
import type { FavoriteSupportType } from '../constant';
|
||||||
import { isFavoriteSupportType } from '../constant';
|
import { isFavoriteSupportType } from '../constant';
|
||||||
|
|
||||||
@@ -14,27 +14,31 @@ export interface FavoriteRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class FavoriteStore extends Store {
|
export class FavoriteStore extends Store {
|
||||||
|
authService = this.workspaceServerService.server?.scope.get(AuthService);
|
||||||
constructor(
|
constructor(
|
||||||
private readonly authService: AuthService,
|
|
||||||
private readonly workspaceDBService: WorkspaceDBService,
|
private readonly workspaceDBService: WorkspaceDBService,
|
||||||
private readonly workspaceService: WorkspaceService
|
private readonly workspaceService: WorkspaceService,
|
||||||
|
private readonly workspaceServerService: WorkspaceServerService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
private get userdataDB$() {
|
private get userdataDB$() {
|
||||||
return this.authService.session.account$.map(account => {
|
// if is local workspace or no account, use __local__ userdata
|
||||||
// if is local workspace or no account, use __local__ userdata
|
// sometimes we may have cloud workspace but no account for a short time, we also use __local__ userdata
|
||||||
// sometimes we may have cloud workspace but no account for a short time, we also use __local__ userdata
|
if (
|
||||||
if (
|
this.workspaceService.workspace.meta.flavour === WorkspaceFlavour.LOCAL ||
|
||||||
this.workspaceService.workspace.meta.flavour ===
|
!this.authService
|
||||||
WorkspaceFlavour.LOCAL ||
|
) {
|
||||||
!account
|
return new LiveData(this.workspaceDBService.userdataDB('__local__'));
|
||||||
) {
|
} else {
|
||||||
return this.workspaceDBService.userdataDB('__local__');
|
return this.authService.session.account$.map(account => {
|
||||||
}
|
if (!account) {
|
||||||
return this.workspaceDBService.userdataDB(account.id);
|
return this.workspaceDBService.userdataDB('__local__');
|
||||||
});
|
}
|
||||||
|
return this.workspaceDBService.userdataDB(account.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watchIsLoading() {
|
watchIsLoading() {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ export type { Member } from './entities/members';
|
|||||||
export { WorkspaceMembersService } from './services/members';
|
export { WorkspaceMembersService } from './services/members';
|
||||||
export { WorkspacePermissionService } from './services/permission';
|
export { WorkspacePermissionService } from './services/permission';
|
||||||
|
|
||||||
import { GraphQLService } from '@affine/core/modules/cloud';
|
|
||||||
import {
|
import {
|
||||||
type Framework,
|
type Framework,
|
||||||
WorkspaceScope,
|
WorkspaceScope,
|
||||||
@@ -10,6 +9,7 @@ import {
|
|||||||
WorkspacesService,
|
WorkspacesService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { WorkspaceServerService } from '../cloud';
|
||||||
import { WorkspaceMembers } from './entities/members';
|
import { WorkspaceMembers } from './entities/members';
|
||||||
import { WorkspacePermission } from './entities/permission';
|
import { WorkspacePermission } from './entities/permission';
|
||||||
import { WorkspaceMembersService } from './services/members';
|
import { WorkspaceMembersService } from './services/members';
|
||||||
@@ -25,9 +25,9 @@ export function configurePermissionsModule(framework: Framework) {
|
|||||||
WorkspacesService,
|
WorkspacesService,
|
||||||
WorkspacePermissionStore,
|
WorkspacePermissionStore,
|
||||||
])
|
])
|
||||||
.store(WorkspacePermissionStore, [GraphQLService])
|
.store(WorkspacePermissionStore, [WorkspaceServerService])
|
||||||
.entity(WorkspacePermission, [WorkspaceService, WorkspacePermissionStore])
|
.entity(WorkspacePermission, [WorkspaceService, WorkspacePermissionStore])
|
||||||
.service(WorkspaceMembersService)
|
.service(WorkspaceMembersService)
|
||||||
.store(WorkspaceMembersStore, [GraphQLService])
|
.store(WorkspaceMembersStore, [WorkspaceServerService])
|
||||||
.entity(WorkspaceMembers, [WorkspaceMembersStore, WorkspaceService]);
|
.entity(WorkspaceMembers, [WorkspaceMembersStore, WorkspaceService]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { getMembersByWorkspaceIdQuery } from '@affine/graphql';
|
import { getMembersByWorkspaceIdQuery } from '@affine/graphql';
|
||||||
import { Store } from '@toeverything/infra';
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
import type { GraphQLService } from '../../cloud';
|
import type { WorkspaceServerService } from '../../cloud';
|
||||||
|
|
||||||
export class WorkspaceMembersStore extends Store {
|
export class WorkspaceMembersStore extends Store {
|
||||||
constructor(private readonly graphqlService: GraphQLService) {
|
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +14,10 @@ export class WorkspaceMembersStore extends Store {
|
|||||||
take: number,
|
take: number,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
) {
|
) {
|
||||||
const data = await this.graphqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
const data = await this.workspaceServerService.server.gql({
|
||||||
query: getMembersByWorkspaceIdQuery,
|
query: getMembersByWorkspaceIdQuery,
|
||||||
variables: {
|
variables: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import type { GraphQLService } from '@affine/core/modules/cloud';
|
import type { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||||
import { getIsOwnerQuery, leaveWorkspaceMutation } from '@affine/graphql';
|
import { getIsOwnerQuery, leaveWorkspaceMutation } from '@affine/graphql';
|
||||||
import { Store } from '@toeverything/infra';
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
export class WorkspacePermissionStore extends Store {
|
export class WorkspacePermissionStore extends Store {
|
||||||
constructor(private readonly graphqlService: GraphQLService) {
|
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchIsOwner(workspaceId: string, signal?: AbortSignal) {
|
async fetchIsOwner(workspaceId: string, signal?: AbortSignal) {
|
||||||
const isOwner = await this.graphqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
const isOwner = await this.workspaceServerService.server.gql({
|
||||||
query: getIsOwnerQuery,
|
query: getIsOwnerQuery,
|
||||||
variables: {
|
variables: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -23,7 +26,10 @@ export class WorkspacePermissionStore extends Store {
|
|||||||
* @param workspaceName for send email
|
* @param workspaceName for send email
|
||||||
*/
|
*/
|
||||||
async leaveWorkspace(workspaceId: string, workspaceName: string) {
|
async leaveWorkspace(workspaceId: string, workspaceName: string) {
|
||||||
await this.graphqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
await this.workspaceServerService.server.gql({
|
||||||
query: leaveWorkspaceMutation,
|
query: leaveWorkspaceMutation,
|
||||||
variables: {
|
variables: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
export { WorkspaceQuotaService } from './services/quota';
|
export { WorkspaceQuotaService } from './services/quota';
|
||||||
|
|
||||||
import { GraphQLService } from '@affine/core/modules/cloud';
|
|
||||||
import {
|
import {
|
||||||
type Framework,
|
type Framework,
|
||||||
WorkspaceScope,
|
WorkspaceScope,
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { WorkspaceServerService } from '../cloud';
|
||||||
import { WorkspaceQuota } from './entities/quota';
|
import { WorkspaceQuota } from './entities/quota';
|
||||||
import { WorkspaceQuotaService } from './services/quota';
|
import { WorkspaceQuotaService } from './services/quota';
|
||||||
import { WorkspaceQuotaStore } from './stores/quota';
|
import { WorkspaceQuotaStore } from './stores/quota';
|
||||||
@@ -15,6 +15,6 @@ export function configureQuotaModule(framework: Framework) {
|
|||||||
framework
|
framework
|
||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.service(WorkspaceQuotaService)
|
.service(WorkspaceQuotaService)
|
||||||
.store(WorkspaceQuotaStore, [GraphQLService])
|
.store(WorkspaceQuotaStore, [WorkspaceServerService])
|
||||||
.entity(WorkspaceQuota, [WorkspaceService, WorkspaceQuotaStore]);
|
.entity(WorkspaceQuota, [WorkspaceService, WorkspaceQuotaStore]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import type { GraphQLService } from '@affine/core/modules/cloud';
|
import type { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||||
import { workspaceQuotaQuery } from '@affine/graphql';
|
import { workspaceQuotaQuery } from '@affine/graphql';
|
||||||
import { Store } from '@toeverything/infra';
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
export class WorkspaceQuotaStore extends Store {
|
export class WorkspaceQuotaStore extends Store {
|
||||||
constructor(private readonly graphqlService: GraphQLService) {
|
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchWorkspaceQuota(workspaceId: string, signal?: AbortSignal) {
|
async fetchWorkspaceQuota(workspaceId: string, signal?: AbortSignal) {
|
||||||
const data = await this.graphqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
const data = await this.workspaceServerService.server.gql({
|
||||||
query: workspaceQuotaQuery,
|
query: workspaceQuotaQuery,
|
||||||
variables: {
|
variables: {
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
import { FetchService, GraphQLService } from '../cloud';
|
import { RawFetchProvider, WorkspaceServerService } from '../cloud';
|
||||||
import { ShareDocsList } from './entities/share-docs-list';
|
import { ShareDocsList } from './entities/share-docs-list';
|
||||||
import { ShareInfo } from './entities/share-info';
|
import { ShareInfo } from './entities/share-info';
|
||||||
import { ShareReader } from './entities/share-reader';
|
import { ShareReader } from './entities/share-reader';
|
||||||
@@ -27,10 +27,10 @@ export function configureShareDocsModule(framework: Framework) {
|
|||||||
framework
|
framework
|
||||||
.service(ShareReaderService)
|
.service(ShareReaderService)
|
||||||
.entity(ShareReader, [ShareReaderStore])
|
.entity(ShareReader, [ShareReaderStore])
|
||||||
.store(ShareReaderStore, [FetchService])
|
.store(ShareReaderStore, [RawFetchProvider])
|
||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.service(ShareDocsListService, [WorkspaceService])
|
.service(ShareDocsListService, [WorkspaceService])
|
||||||
.store(ShareDocsStore, [GraphQLService])
|
.store(ShareDocsStore, [WorkspaceServerService])
|
||||||
.entity(ShareDocsList, [
|
.entity(ShareDocsList, [
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
ShareDocsStore,
|
ShareDocsStore,
|
||||||
@@ -39,5 +39,5 @@ export function configureShareDocsModule(framework: Framework) {
|
|||||||
.scope(DocScope)
|
.scope(DocScope)
|
||||||
.service(ShareInfoService)
|
.service(ShareInfoService)
|
||||||
.entity(ShareInfo, [WorkspaceService, DocService, ShareStore])
|
.entity(ShareInfo, [WorkspaceService, DocService, ShareStore])
|
||||||
.store(ShareStore, [GraphQLService]);
|
.store(ShareStore, [WorkspaceServerService]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import type { GraphQLService } from '@affine/core/modules/cloud';
|
import type { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||||
import { getWorkspacePublicPagesQuery } from '@affine/graphql';
|
import { getWorkspacePublicPagesQuery } from '@affine/graphql';
|
||||||
import { Store } from '@toeverything/infra';
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
export class ShareDocsStore extends Store {
|
export class ShareDocsStore extends Store {
|
||||||
constructor(private readonly graphqlService: GraphQLService) {
|
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getWorkspacesShareDocs(workspaceId: string, signal?: AbortSignal) {
|
async getWorkspacesShareDocs(workspaceId: string, signal?: AbortSignal) {
|
||||||
const data = await this.graphqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
const data = await this.workspaceServerService.server.gql({
|
||||||
query: getWorkspacePublicPagesQuery,
|
query: getWorkspacePublicPagesQuery,
|
||||||
variables: {
|
variables: {
|
||||||
workspaceId: workspaceId,
|
workspaceId: workspaceId,
|
||||||
|
|||||||
@@ -2,16 +2,20 @@ import { ErrorNames, UserFriendlyError } from '@affine/graphql';
|
|||||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||||
import { Store } from '@toeverything/infra';
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
import { type FetchService, isBackendError } from '../../cloud';
|
import type { RawFetchProvider } from '../../cloud';
|
||||||
|
import { isBackendError } from '../../cloud';
|
||||||
|
|
||||||
export class ShareReaderStore extends Store {
|
export class ShareReaderStore extends Store {
|
||||||
constructor(private readonly fetchService: FetchService) {
|
constructor(private readonly rawFetch?: RawFetchProvider) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadShare(workspaceId: string, docId: string) {
|
async loadShare(workspaceId: string, docId: string) {
|
||||||
|
if (!this.rawFetch) {
|
||||||
|
throw new Error('No Fetch Service');
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const docResponse = await this.fetchService.fetch(
|
const docResponse = await this.rawFetch.fetch(
|
||||||
`/api/workspaces/${workspaceId}/docs/${docId}`
|
`/api/workspaces/${workspaceId}/docs/${docId}`
|
||||||
);
|
);
|
||||||
const publishMode = docResponse.headers.get(
|
const publishMode = docResponse.headers.get(
|
||||||
@@ -19,7 +23,7 @@ export class ShareReaderStore extends Store {
|
|||||||
) as DocMode | null;
|
) as DocMode | null;
|
||||||
const docBinary = await docResponse.arrayBuffer();
|
const docBinary = await docResponse.arrayBuffer();
|
||||||
|
|
||||||
const workspaceResponse = await this.fetchService.fetch(
|
const workspaceResponse = await this.rawFetch.fetch(
|
||||||
`/api/workspaces/${workspaceId}/docs/${workspaceId}`
|
`/api/workspaces/${workspaceId}/docs/${workspaceId}`
|
||||||
);
|
);
|
||||||
const workspaceBinary = await workspaceResponse.arrayBuffer();
|
const workspaceBinary = await workspaceResponse.arrayBuffer();
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import {
|
|||||||
} from '@affine/graphql';
|
} from '@affine/graphql';
|
||||||
import { Store } from '@toeverything/infra';
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
import type { GraphQLService } from '../../cloud';
|
import type { WorkspaceServerService } from '../../cloud';
|
||||||
|
|
||||||
export class ShareStore extends Store {
|
export class ShareStore extends Store {
|
||||||
constructor(private readonly gqlService: GraphQLService) {
|
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,7 +18,10 @@ export class ShareStore extends Store {
|
|||||||
docId: string,
|
docId: string,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
) {
|
) {
|
||||||
const data = await this.gqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
const data = await this.workspaceServerService.server.gql({
|
||||||
query: getWorkspacePublicPageByIdQuery,
|
query: getWorkspacePublicPageByIdQuery,
|
||||||
variables: {
|
variables: {
|
||||||
pageId: docId,
|
pageId: docId,
|
||||||
@@ -37,7 +40,10 @@ export class ShareStore extends Store {
|
|||||||
docMode?: PublicPageMode,
|
docMode?: PublicPageMode,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
) {
|
) {
|
||||||
await this.gqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
await this.workspaceServerService.server.gql({
|
||||||
query: publishPageMutation,
|
query: publishPageMutation,
|
||||||
variables: {
|
variables: {
|
||||||
pageId,
|
pageId,
|
||||||
@@ -55,7 +61,10 @@ export class ShareStore extends Store {
|
|||||||
pageId: string,
|
pageId: string,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
) {
|
) {
|
||||||
await this.gqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
await this.workspaceServerService.server.gql({
|
||||||
query: revokePublicPageMutation,
|
query: revokePublicPageMutation,
|
||||||
variables: {
|
variables: {
|
||||||
pageId,
|
pageId,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
export { WorkspaceShareSettingService } from './services/share-setting';
|
export { WorkspaceShareSettingService } from './services/share-setting';
|
||||||
|
|
||||||
import { GraphQLService } from '@affine/core/modules/cloud';
|
|
||||||
import {
|
import {
|
||||||
type Framework,
|
type Framework,
|
||||||
WorkspaceScope,
|
WorkspaceScope,
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { WorkspaceServerService } from '../cloud';
|
||||||
import { WorkspaceShareSetting } from './entities/share-setting';
|
import { WorkspaceShareSetting } from './entities/share-setting';
|
||||||
import { WorkspaceShareSettingService } from './services/share-setting';
|
import { WorkspaceShareSettingService } from './services/share-setting';
|
||||||
import { WorkspaceShareSettingStore } from './stores/share-setting';
|
import { WorkspaceShareSettingStore } from './stores/share-setting';
|
||||||
@@ -15,7 +15,7 @@ export function configureShareSettingModule(framework: Framework) {
|
|||||||
framework
|
framework
|
||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
.service(WorkspaceShareSettingService)
|
.service(WorkspaceShareSettingService)
|
||||||
.store(WorkspaceShareSettingStore, [GraphQLService])
|
.store(WorkspaceShareSettingStore, [WorkspaceServerService])
|
||||||
.entity(WorkspaceShareSetting, [
|
.entity(WorkspaceShareSetting, [
|
||||||
WorkspaceService,
|
WorkspaceService,
|
||||||
WorkspaceShareSettingStore,
|
WorkspaceShareSettingStore,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { GraphQLService } from '@affine/core/modules/cloud';
|
import type { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||||
import {
|
import {
|
||||||
getEnableUrlPreviewQuery,
|
getEnableUrlPreviewQuery,
|
||||||
setEnableUrlPreviewMutation,
|
setEnableUrlPreviewMutation,
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
import { Store } from '@toeverything/infra';
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
export class WorkspaceShareSettingStore extends Store {
|
export class WorkspaceShareSettingStore extends Store {
|
||||||
constructor(private readonly graphqlService: GraphQLService) {
|
constructor(private readonly workspaceServerService: WorkspaceServerService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +14,10 @@ export class WorkspaceShareSettingStore extends Store {
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
) {
|
) {
|
||||||
const data = await this.graphqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
const data = await this.workspaceServerService.server.gql({
|
||||||
query: getEnableUrlPreviewQuery,
|
query: getEnableUrlPreviewQuery,
|
||||||
variables: {
|
variables: {
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
@@ -31,7 +34,10 @@ export class WorkspaceShareSettingStore extends Store {
|
|||||||
enableUrlPreview: boolean,
|
enableUrlPreview: boolean,
|
||||||
signal?: AbortSignal
|
signal?: AbortSignal
|
||||||
) {
|
) {
|
||||||
await this.graphqlService.gql({
|
if (!this.workspaceServerService.server) {
|
||||||
|
throw new Error('No Server');
|
||||||
|
}
|
||||||
|
await this.workspaceServerService.server.gql({
|
||||||
query: setEnableUrlPreviewMutation,
|
query: setEnableUrlPreviewMutation,
|
||||||
variables: {
|
variables: {
|
||||||
id: workspaceId,
|
id: workspaceId,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { type Framework, GlobalContextService } from '@toeverything/infra';
|
import { type Framework, GlobalContextService } from '@toeverything/infra';
|
||||||
|
|
||||||
import { AuthService } from '../cloud';
|
import { ServersService } from '../cloud/services/servers';
|
||||||
import { TelemetryService } from './services/telemetry';
|
import { TelemetryService } from './services/telemetry';
|
||||||
|
|
||||||
export function configureTelemetryModule(framework: Framework) {
|
export function configureTelemetryModule(framework: Framework) {
|
||||||
framework.service(TelemetryService, [AuthService, GlobalContextService]);
|
framework.service(TelemetryService, [ServersService, GlobalContextService]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,31 @@ import { mixpanel } from '@affine/track';
|
|||||||
import type { GlobalContextService } from '@toeverything/infra';
|
import type { GlobalContextService } from '@toeverything/infra';
|
||||||
import { ApplicationStarted, OnEvent, Service } from '@toeverything/infra';
|
import { ApplicationStarted, OnEvent, Service } from '@toeverything/infra';
|
||||||
|
|
||||||
import {
|
import { AccountChanged, type AuthAccountInfo, AuthService } from '../../cloud';
|
||||||
AccountChanged,
|
|
||||||
type AuthAccountInfo,
|
|
||||||
type AuthService,
|
|
||||||
} from '../../cloud';
|
|
||||||
import { AccountLoggedOut } from '../../cloud/services/auth';
|
import { AccountLoggedOut } from '../../cloud/services/auth';
|
||||||
|
import type { ServersService } from '../../cloud/services/servers';
|
||||||
|
|
||||||
@OnEvent(ApplicationStarted, e => e.onApplicationStart)
|
@OnEvent(ApplicationStarted, e => e.onApplicationStart)
|
||||||
@OnEvent(AccountChanged, e => e.updateIdentity)
|
@OnEvent(AccountChanged, e => e.updateIdentity)
|
||||||
@OnEvent(AccountLoggedOut, e => e.onAccountLoggedOut)
|
@OnEvent(AccountLoggedOut, e => e.onAccountLoggedOut)
|
||||||
export class TelemetryService extends Service {
|
export class TelemetryService extends Service {
|
||||||
|
private readonly authService;
|
||||||
constructor(
|
constructor(
|
||||||
private readonly auth: AuthService,
|
serversService: ServersService,
|
||||||
private readonly globalContextService: GlobalContextService
|
private readonly globalContextService: GlobalContextService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
// TODO: support multiple servers
|
||||||
|
const affineCloudServer = serversService.server$('affine-cloud').value;
|
||||||
|
if (!affineCloudServer) {
|
||||||
|
throw new Error('affine-cloud server not found');
|
||||||
|
}
|
||||||
|
this.authService = affineCloudServer.scope.get(AuthService);
|
||||||
}
|
}
|
||||||
|
|
||||||
onApplicationStart() {
|
onApplicationStart() {
|
||||||
const account = this.auth.session.account$.value;
|
const account = this.authService.session.account$.value;
|
||||||
this.updateIdentity(account);
|
this.updateIdentity(account);
|
||||||
this.registerMiddlewares();
|
this.registerMiddlewares();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export { UserspaceService as UserDBService } from './services/userspace';
|
|||||||
import type { Framework } from '@toeverything/infra';
|
import type { Framework } from '@toeverything/infra';
|
||||||
|
|
||||||
import { AuthService, WebSocketService } from '../cloud';
|
import { AuthService, WebSocketService } from '../cloud';
|
||||||
|
import { ServerScope } from '../cloud/scopes/server';
|
||||||
import { DesktopApiService } from '../desktop-api/service/desktop-api';
|
import { DesktopApiService } from '../desktop-api/service/desktop-api';
|
||||||
import { CurrentUserDB } from './entities/current-user-db';
|
import { CurrentUserDB } from './entities/current-user-db';
|
||||||
import { UserDB } from './entities/user-db';
|
import { UserDB } from './entities/user-db';
|
||||||
@@ -15,6 +16,7 @@ import { UserspaceService } from './services/userspace';
|
|||||||
|
|
||||||
export function configureUserspaceModule(framework: Framework) {
|
export function configureUserspaceModule(framework: Framework) {
|
||||||
framework
|
framework
|
||||||
|
.scope(ServerScope)
|
||||||
.service(UserspaceService)
|
.service(UserspaceService)
|
||||||
.entity(CurrentUserDB, [UserspaceService, AuthService])
|
.entity(CurrentUserDB, [UserspaceService, AuthService])
|
||||||
.entity(UserDB)
|
.entity(UserDB)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
onComplete,
|
onComplete,
|
||||||
OnEvent,
|
OnEvent,
|
||||||
onStart,
|
onStart,
|
||||||
|
type Workspace,
|
||||||
type WorkspaceEngineProvider,
|
type WorkspaceEngineProvider,
|
||||||
type WorkspaceFlavourProvider,
|
type WorkspaceFlavourProvider,
|
||||||
type WorkspaceMetadata,
|
type WorkspaceMetadata,
|
||||||
@@ -30,13 +31,16 @@ import { nanoid } from 'nanoid';
|
|||||||
import { EMPTY, map, mergeMap } from 'rxjs';
|
import { EMPTY, map, mergeMap } from 'rxjs';
|
||||||
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
import { applyUpdate, encodeStateAsUpdate } from 'yjs';
|
||||||
|
|
||||||
import type {
|
import type { Server } from '../../cloud';
|
||||||
|
import {
|
||||||
|
AccountChanged,
|
||||||
AuthService,
|
AuthService,
|
||||||
FetchService,
|
FetchService,
|
||||||
GraphQLService,
|
GraphQLService,
|
||||||
WebSocketService,
|
WebSocketService,
|
||||||
|
WorkspaceServerService,
|
||||||
} from '../../cloud';
|
} from '../../cloud';
|
||||||
import { AccountChanged } from '../../cloud';
|
import type { ServersService } from '../../cloud/services/servers';
|
||||||
import type { WorkspaceEngineStorageProvider } from '../providers/engine';
|
import type { WorkspaceEngineStorageProvider } from '../providers/engine';
|
||||||
import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel';
|
import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel';
|
||||||
import { CloudAwarenessConnection } from './engine/awareness-cloud';
|
import { CloudAwarenessConnection } from './engine/awareness-cloud';
|
||||||
@@ -55,16 +59,30 @@ export class CloudWorkspaceFlavourProviderService
|
|||||||
extends Service
|
extends Service
|
||||||
implements WorkspaceFlavourProvider
|
implements WorkspaceFlavourProvider
|
||||||
{
|
{
|
||||||
|
private readonly authService: AuthService;
|
||||||
|
private readonly webSocketService: WebSocketService;
|
||||||
|
private readonly fetchService: FetchService;
|
||||||
|
private readonly graphqlService: GraphQLService;
|
||||||
|
private readonly affineCloudServer: Server;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly globalState: GlobalState,
|
private readonly globalState: GlobalState,
|
||||||
private readonly authService: AuthService,
|
|
||||||
private readonly storageProvider: WorkspaceEngineStorageProvider,
|
private readonly storageProvider: WorkspaceEngineStorageProvider,
|
||||||
private readonly graphqlService: GraphQLService,
|
serversService: ServersService
|
||||||
private readonly webSocketService: WebSocketService,
|
|
||||||
private readonly fetchService: FetchService
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
// TODO: support multiple servers
|
||||||
|
const affineCloudServer = serversService.server$('affine-cloud').value;
|
||||||
|
if (!affineCloudServer) {
|
||||||
|
throw new Error('affine-cloud server not found');
|
||||||
|
}
|
||||||
|
this.affineCloudServer = affineCloudServer;
|
||||||
|
this.authService = affineCloudServer.scope.get(AuthService);
|
||||||
|
this.webSocketService = affineCloudServer.scope.get(WebSocketService);
|
||||||
|
this.fetchService = affineCloudServer.scope.get(FetchService);
|
||||||
|
this.graphqlService = affineCloudServer.scope.get(GraphQLService);
|
||||||
}
|
}
|
||||||
|
|
||||||
flavour: WorkspaceFlavour = WorkspaceFlavour.AFFINE_CLOUD;
|
flavour: WorkspaceFlavour = WorkspaceFlavour.AFFINE_CLOUD;
|
||||||
|
|
||||||
async deleteWorkspace(id: string): Promise<void> {
|
async deleteWorkspace(id: string): Promise<void> {
|
||||||
@@ -244,6 +262,7 @@ export class CloudWorkspaceFlavourProviderService
|
|||||||
);
|
);
|
||||||
return await cloudBlob.get(blob);
|
return await cloudBlob.get(blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEngineProvider(workspaceId: string): WorkspaceEngineProvider {
|
getEngineProvider(workspaceId: string): WorkspaceEngineProvider {
|
||||||
return {
|
return {
|
||||||
getAwarenessConnections: () => {
|
getAwarenessConnections: () => {
|
||||||
@@ -274,6 +293,13 @@ export class CloudWorkspaceFlavourProviderService
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onWorkspaceInitialized(workspace: Workspace): void {
|
||||||
|
// bind the workspace to the affine cloud server
|
||||||
|
workspace.scope
|
||||||
|
.get(WorkspaceServerService)
|
||||||
|
.bindServer(this.affineCloudServer);
|
||||||
|
}
|
||||||
|
|
||||||
private async getIsOwner(workspaceId: string, signal?: AbortSignal) {
|
private async getIsOwner(workspaceId: string, signal?: AbortSignal) {
|
||||||
return (
|
return (
|
||||||
await this.graphqlService.gql({
|
await this.graphqlService.gql({
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import {
|
|
||||||
AuthService,
|
|
||||||
FetchService,
|
|
||||||
GraphQLService,
|
|
||||||
WebSocketService,
|
|
||||||
} from '@affine/core/modules/cloud';
|
|
||||||
import {
|
import {
|
||||||
type Framework,
|
type Framework,
|
||||||
GlobalState,
|
GlobalState,
|
||||||
WorkspaceFlavourProvider,
|
WorkspaceFlavourProvider,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { ServersService } from '../cloud/services/servers';
|
||||||
import { DesktopApiService } from '../desktop-api';
|
import { DesktopApiService } from '../desktop-api';
|
||||||
import { CloudWorkspaceFlavourProviderService } from './impls/cloud';
|
import { CloudWorkspaceFlavourProviderService } from './impls/cloud';
|
||||||
import { IndexedDBBlobStorage } from './impls/engine/blob-indexeddb';
|
import { IndexedDBBlobStorage } from './impls/engine/blob-indexeddb';
|
||||||
@@ -31,11 +26,8 @@ export function configureBrowserWorkspaceFlavours(framework: Framework) {
|
|||||||
])
|
])
|
||||||
.service(CloudWorkspaceFlavourProviderService, [
|
.service(CloudWorkspaceFlavourProviderService, [
|
||||||
GlobalState,
|
GlobalState,
|
||||||
AuthService,
|
|
||||||
WorkspaceEngineStorageProvider,
|
WorkspaceEngineStorageProvider,
|
||||||
GraphQLService,
|
ServersService,
|
||||||
WebSocketService,
|
|
||||||
FetchService,
|
|
||||||
])
|
])
|
||||||
.impl(WorkspaceFlavourProvider('CLOUD'), p =>
|
.impl(WorkspaceFlavourProvider('CLOUD'), p =>
|
||||||
p.get(CloudWorkspaceFlavourProviderService)
|
p.get(CloudWorkspaceFlavourProviderService)
|
||||||
|
|||||||
@@ -998,6 +998,7 @@ query serverConfig {
|
|||||||
name
|
name
|
||||||
features
|
features
|
||||||
type
|
type
|
||||||
|
initialized
|
||||||
credentialsRequirement {
|
credentialsRequirement {
|
||||||
...CredentialsRequirement
|
...CredentialsRequirement
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ query serverConfig {
|
|||||||
name
|
name
|
||||||
features
|
features
|
||||||
type
|
type
|
||||||
|
initialized
|
||||||
credentialsRequirement {
|
credentialsRequirement {
|
||||||
...CredentialsRequirement
|
...CredentialsRequirement
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,23 +2,3 @@ export * from './error';
|
|||||||
export * from './fetcher';
|
export * from './fetcher';
|
||||||
export * from './graphql';
|
export * from './graphql';
|
||||||
export * from './schema';
|
export * from './schema';
|
||||||
|
|
||||||
import { setupGlobal } from '@affine/env/global';
|
|
||||||
|
|
||||||
import { gqlFetcherFactory } from './fetcher';
|
|
||||||
|
|
||||||
setupGlobal();
|
|
||||||
|
|
||||||
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}` : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetcher = gqlFetcherFactory(getBaseUrl() + '/graphql');
|
|
||||||
|
|||||||
@@ -2175,6 +2175,7 @@ export type ServerConfigQuery = {
|
|||||||
name: string;
|
name: string;
|
||||||
features: Array<ServerFeature>;
|
features: Array<ServerFeature>;
|
||||||
type: ServerDeploymentType;
|
type: ServerDeploymentType;
|
||||||
|
initialized: boolean;
|
||||||
credentialsRequirement: {
|
credentialsRequirement: {
|
||||||
__typename?: 'CredentialsRequirementType';
|
__typename?: 'CredentialsRequirementType';
|
||||||
password: {
|
password: {
|
||||||
|
|||||||
Reference in New Issue
Block a user