feat(core): add allowGuestDemoWorkspace flag to force login (#12779)

https://github.com/user-attachments/assets/41a659c9-6def-4492-be8e-5910eb148d6f

This PR enforces login‑first access (#8716) by disabling or enabling the
guest demo workspace via Admin Server Client Page and redirecting
unauthenticated users straight to `/sign‑in`.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a configuration option to control whether guest users can create
demo workspaces.
* Updated server and client interfaces, GraphQL schema, and queries to
support the new guest demo workspace flag.

* **Bug Fixes**
* Improved sign-out behavior to redirect users appropriately based on
guest demo workspace permissions.
* Enhanced navigation flow to handle guest demo workspace access and
user authentication state.

* **Tests**
* Added tests to verify sign-out logic when guest demo workspaces are
enabled or disabled.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: liuyi <forehalo@gmail.com>
Co-authored-by: fengmk2 <fengmk2@gmail.com>
This commit is contained in:
Richard Lora
2025-06-29 10:17:18 -04:00
committed by GitHub
parent a4680d236d
commit 82b3c0d264
25 changed files with 209 additions and 34 deletions
+10
View File
@@ -565,6 +565,11 @@
"type": "boolean", "type": "boolean",
"description": "Only allow users with early access features to access the app\n@default false", "description": "Only allow users with early access features to access the app\n@default false",
"default": false "default": false
},
"allowGuestDemoWorkspace": {
"type": "boolean",
"description": "Whether allow guest users to create demo workspaces.\n@default true",
"default": true
} }
} }
}, },
@@ -592,6 +597,11 @@
"type": "string", "type": "string",
"description": "Allowed version range of the app that allowed to access the server. Requires 'client/versionControl.enabled' to be true to take effect.\n@default \">=0.20.0\"", "description": "Allowed version range of the app that allowed to access the server. Requires 'client/versionControl.enabled' to be true to take effect.\n@default \">=0.20.0\"",
"default": ">=0.20.0" "default": ">=0.20.0"
},
"allowGuestDemoWorkspace": {
"type": "boolean",
"description": "Allow guests to access demo workspace.\n@default true",
"default": true
} }
} }
}, },
@@ -4,6 +4,7 @@ import { defineModuleConfig } from '../../base';
export interface ServerFlags { export interface ServerFlags {
earlyAccessControl: boolean; earlyAccessControl: boolean;
allowGuestDemoWorkspace: boolean;
} }
declare global { declare global {
@@ -75,4 +76,8 @@ defineModuleConfig('flags', {
desc: 'Only allow users with early access features to access the app', desc: 'Only allow users with early access features to access the app',
default: false, default: false,
}, },
allowGuestDemoWorkspace: {
desc: 'Whether allow guest users to create demo workspaces.',
default: true,
},
}); });
@@ -85,6 +85,7 @@ export class ServerConfigResolver {
baseUrl: this.url.requestBaseUrl, baseUrl: this.url.requestBaseUrl,
type: env.DEPLOYMENT_TYPE, type: env.DEPLOYMENT_TYPE,
features: this.server.features, features: this.server.features,
allowGuestDemoWorkspace: this.config.flags.allowGuestDemoWorkspace,
}; };
} }
@@ -38,4 +38,9 @@ export class ServerConfigType {
@Field(() => [ServerFeature], { description: 'enabled server features' }) @Field(() => [ServerFeature], { description: 'enabled server features' })
features!: ServerFeature[]; features!: ServerFeature[];
@Field(() => Boolean, {
description: 'Whether allow guest users to create demo workspaces.',
})
allowGuestDemoWorkspace!: boolean;
} }
@@ -5,6 +5,7 @@ export interface VersionConfig {
enabled: boolean; enabled: boolean;
requiredVersion: string; requiredVersion: string;
}; };
allowGuestDemoWorkspace?: boolean;
} }
declare global { declare global {
@@ -28,4 +29,8 @@ defineModuleConfig('client', {
desc: "Allowed version range of the app that allowed to access the server. Requires 'client/versionControl.enabled' to be true to take effect.", desc: "Allowed version range of the app that allowed to access the server. Requires 'client/versionControl.enabled' to be true to take effect.",
default: '>=0.20.0', default: '>=0.20.0',
}, },
allowGuestDemoWorkspace: {
desc: 'Allow guests to access demo workspace.',
default: true,
},
}); });
+3
View File
@@ -1585,6 +1585,9 @@ enum SearchTable {
} }
type ServerConfigType { type ServerConfigType {
"""Whether allow guest users to create demo workspaces."""
allowGuestDemoWorkspace: Boolean!
"""fetch latest available upgradable release of server""" """fetch latest available upgradable release of server"""
availableUpgrade: ReleaseVersionType availableUpgrade: ReleaseVersionType
@@ -7,6 +7,7 @@ query adminServerConfig {
baseUrl baseUrl
name name
features features
allowGuestDemoWorkspace
type type
initialized initialized
credentialsRequirement { credentialsRequirement {
@@ -32,6 +32,7 @@ export const adminServerConfigQuery = {
baseUrl baseUrl
name name
features features
allowGuestDemoWorkspace
type type
initialized initialized
credentialsRequirement { credentialsRequirement {
@@ -1822,6 +1823,7 @@ export const serverConfigQuery = {
baseUrl baseUrl
name name
features features
allowGuestDemoWorkspace
type type
initialized initialized
credentialsRequirement { credentialsRequirement {
@@ -7,6 +7,7 @@ query serverConfig {
baseUrl baseUrl
name name
features features
allowGuestDemoWorkspace
type type
initialized initialized
credentialsRequirement { credentialsRequirement {
+4
View File
@@ -2149,6 +2149,8 @@ export enum SearchTable {
export interface ServerConfigType { export interface ServerConfigType {
__typename?: 'ServerConfigType'; __typename?: 'ServerConfigType';
/** Whether allow guest users to create demo workspaces. */
allowGuestDemoWorkspace: Scalars['Boolean']['output'];
/** fetch latest available upgradable release of server */ /** fetch latest available upgradable release of server */
availableUpgrade: Maybe<ReleaseVersionType>; availableUpgrade: Maybe<ReleaseVersionType>;
/** Features for user that can be configured */ /** Features for user that can be configured */
@@ -2743,6 +2745,7 @@ export type AdminServerConfigQuery = {
baseUrl: string; baseUrl: string;
name: string; name: string;
features: Array<ServerFeature>; features: Array<ServerFeature>;
allowGuestDemoWorkspace: boolean;
type: ServerDeploymentType; type: ServerDeploymentType;
initialized: boolean; initialized: boolean;
availableUserFeatures: Array<FeatureType>; availableUserFeatures: Array<FeatureType>;
@@ -4828,6 +4831,7 @@ export type ServerConfigQuery = {
baseUrl: string; baseUrl: string;
name: string; name: string;
features: Array<ServerFeature>; features: Array<ServerFeature>;
allowGuestDemoWorkspace: boolean;
type: ServerDeploymentType; type: ServerDeploymentType;
initialized: boolean; initialized: boolean;
credentialsRequirement: { credentialsRequirement: {
+8
View File
@@ -190,6 +190,10 @@
"earlyAccessControl": { "earlyAccessControl": {
"type": "Boolean", "type": "Boolean",
"desc": "Only allow users with early access features to access the app" "desc": "Only allow users with early access features to access the app"
},
"allowGuestDemoWorkspace": {
"type": "Boolean",
"desc": "Whether allow guest users to create demo workspaces."
} }
}, },
"docService": { "docService": {
@@ -207,6 +211,10 @@
"versionControl.requiredVersion": { "versionControl.requiredVersion": {
"type": "String", "type": "String",
"desc": "Allowed version range of the app that allowed to access the server. Requires 'client/versionControl.enabled' to be true to take effect." "desc": "Allowed version range of the app that allowed to access the server. Requires 'client/versionControl.enabled' to be true to take effect."
},
"allowGuestDemoWorkspace": {
"type": "Boolean",
"desc": "Allow guests to access demo workspace."
} }
}, },
"captcha": { "captcha": {
@@ -7,6 +7,7 @@ import { useLiveData, useService } from '@toeverything/infra';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { AuthService, SubscriptionService } from '../../../modules/cloud'; import { AuthService, SubscriptionService } from '../../../modules/cloud';
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
import * as styles from './styles.css'; import * as styles from './styles.css';
const UserInfo = () => { const UserInfo = () => {
@@ -51,9 +52,11 @@ export const PublishPageUserAvatar = () => {
const user = useLiveData(authService.session.account$); const user = useLiveData(authService.session.account$);
const t = useI18n(); const t = useI18n();
const navigateHelper = useNavigateHelper();
const handleSignOut = useAsyncCallback(async () => { const handleSignOut = useAsyncCallback(async () => {
await authService.signOut(); await authService.signOut();
}, [authService]); navigateHelper.jumpToSignIn();
}, [authService, navigateHelper]);
const menuItem = useMemo(() => { const menuItem = useMemo(() => {
return ( return (
@@ -0,0 +1,92 @@
/* eslint-disable rxjs/finnish */
/**
* @vitest-environment happy-dom
*/
import { renderHook, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, test, vi } from 'vitest';
// mocks
const signOutFn = vi.fn();
const jumpToIndex = vi.fn();
const jumpToSignIn = vi.fn();
let allowGuestDemo: boolean | undefined = true;
vi.mock('@affine/core/modules/cloud', () => ({
AuthService: class {},
DefaultServerService: class {},
}));
vi.mock('@toeverything/infra', () => {
return {
useService: () => ({ signOut: signOutFn }),
useServices: () => ({
defaultServerService: {
server: {
config$: {
value: {
get allowGuestDemoWorkspace() {
return allowGuestDemo;
},
},
},
},
},
}),
};
});
vi.mock('@affine/component', () => {
return {
useConfirmModal: () => ({
openConfirmModal: ({ onConfirm }: { onConfirm?: () => unknown }) => {
return Promise.resolve(onConfirm?.());
},
}),
notify: { error: vi.fn() },
};
});
vi.mock('@affine/i18n', () => ({
useI18n: () => new Proxy({}, { get: () => () => '' }),
}));
vi.mock('../../use-navigate-helper', () => ({
useNavigateHelper: () => ({ jumpToIndex, jumpToSignIn }),
}));
import { useSignOut } from '../use-sign-out';
describe('useSignOut', () => {
beforeEach(() => {
signOutFn.mockClear();
jumpToIndex.mockClear();
jumpToSignIn.mockClear();
});
test('redirects to index when guest demo allowed', async () => {
allowGuestDemo = true;
const { result } = renderHook(() => useSignOut());
result.current();
await waitFor(() => expect(signOutFn).toHaveBeenCalled());
expect(jumpToIndex).toHaveBeenCalled();
expect(jumpToSignIn).not.toHaveBeenCalled();
});
test('redirects to index when guest demo config not provided', async () => {
allowGuestDemo = undefined;
const { result } = renderHook(() => useSignOut());
result.current();
await waitFor(() => expect(signOutFn).toHaveBeenCalled());
expect(jumpToIndex).toHaveBeenCalled();
expect(jumpToSignIn).not.toHaveBeenCalled();
});
test('redirects to sign in when guest demo disabled', async () => {
allowGuestDemo = false;
const { result } = renderHook(() => useSignOut());
result.current();
await waitFor(() => expect(signOutFn).toHaveBeenCalled());
expect(jumpToSignIn).toHaveBeenCalled();
expect(jumpToIndex).not.toHaveBeenCalled();
});
});
@@ -3,10 +3,10 @@ import {
notify, notify,
useConfirmModal, useConfirmModal,
} from '@affine/component'; } from '@affine/component';
import { AuthService } from '@affine/core/modules/cloud'; import { AuthService, DefaultServerService } from '@affine/core/modules/cloud';
import { UserFriendlyError } from '@affine/error'; import { UserFriendlyError } from '@affine/error';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { useService } from '@toeverything/infra'; import { useService, useServices } from '@toeverything/infra';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useNavigateHelper } from '../use-navigate-helper'; import { useNavigateHelper } from '../use-navigate-helper';
@@ -25,21 +25,29 @@ export const useSignOut = ({
}: ConfirmModalProps = {}) => { }: ConfirmModalProps = {}) => {
const t = useI18n(); const t = useI18n();
const { openConfirmModal } = useConfirmModal(); const { openConfirmModal } = useConfirmModal();
const { jumpToIndex } = useNavigateHelper(); const { jumpToSignIn, jumpToIndex } = useNavigateHelper();
const authService = useService(AuthService); const authService = useService(AuthService);
const { defaultServerService } = useServices({ DefaultServerService });
const signOut = useCallback(async () => { const signOut = useCallback(async () => {
onConfirm?.()?.catch(console.error); onConfirm?.()?.catch(console.error);
try { try {
await authService.signOut(); await authService.signOut();
jumpToIndex(); if (
defaultServerService.server.config$.value.allowGuestDemoWorkspace !==
false
) {
jumpToIndex();
} else {
jumpToSignIn();
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
const error = UserFriendlyError.fromAny(err); const error = UserFriendlyError.fromAny(err);
notify.error(error); notify.error(error);
} }
}, [authService, jumpToIndex, onConfirm]); }, [authService, jumpToIndex, jumpToSignIn, defaultServerService, onConfirm]);
const getDefaultText = useCallback( const getDefaultText = useCallback(
(key: SignOutConfirmModalI18NKeys) => { (key: SignOutConfirmModalI18NKeys) => {
@@ -1,5 +1,5 @@
import { MenuItem } from '@affine/component/ui/menu'; import { MenuItem } from '@affine/component/ui/menu';
import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { DefaultServerService } from '@affine/core/modules/cloud';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { ImportIcon, PlusIcon } from '@blocksuite/icons/rc'; import { ImportIcon, PlusIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
@@ -14,10 +14,11 @@ export const AddWorkspace = ({
onNewWorkspace?: () => void; onNewWorkspace?: () => void;
}) => { }) => {
const t = useI18n(); const t = useI18n();
const featureFlagService = useService(FeatureFlagService); const defaultServerService = useService(DefaultServerService);
const enableLocalWorkspace = useLiveData( const allowGuestDemo = useLiveData(
featureFlagService.flags.enable_local_workspace.$ defaultServerService.server.config$.selector(c => c.allowGuestDemoWorkspace)
); );
const guestDemoEnabled = allowGuestDemo !== false;
return ( return (
<> <>
@@ -44,7 +45,7 @@ export const AddWorkspace = ({
className={styles.ItemContainer} className={styles.ItemContainer}
> >
<div className={styles.ItemText}> <div className={styles.ItemText}>
{enableLocalWorkspace {guestDemoEnabled
? t['com.affine.workspaceList.addWorkspace.create']() ? t['com.affine.workspaceList.addWorkspace.create']()
: t['com.affine.workspaceList.addWorkspace.create-cloud']()} : t['com.affine.workspaceList.addWorkspace.create-cloud']()}
</div> </div>
@@ -1,8 +1,7 @@
import { ScrollableContainer } from '@affine/component'; import { ScrollableContainer } from '@affine/component';
import { MenuItem } from '@affine/component/ui/menu'; import { MenuItem } from '@affine/component/ui/menu';
import { AuthService } from '@affine/core/modules/cloud'; import { AuthService, DefaultServerService } from '@affine/core/modules/cloud';
import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { GlobalDialogService } from '@affine/core/modules/dialogs';
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { type WorkspaceMetadata } from '@affine/core/modules/workspace'; import { type WorkspaceMetadata } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
@@ -66,7 +65,7 @@ export const UserWithWorkspaceList = ({
}: UserWithWorkspaceListProps) => { }: UserWithWorkspaceListProps) => {
const globalDialogService = useService(GlobalDialogService); const globalDialogService = useService(GlobalDialogService);
const session = useLiveData(useService(AuthService).session.session$); const session = useLiveData(useService(AuthService).session.session$);
const featureFlagService = useService(FeatureFlagService); const defaultServerService = useService(DefaultServerService);
const isAuthenticated = session.status === 'authenticated'; const isAuthenticated = session.status === 'authenticated';
@@ -77,7 +76,8 @@ export const UserWithWorkspaceList = ({
const onNewWorkspace = useCallback(() => { const onNewWorkspace = useCallback(() => {
if ( if (
!isAuthenticated && !isAuthenticated &&
!featureFlagService.flags.enable_local_workspace.value defaultServerService.server.config$.value.allowGuestDemoWorkspace ===
false
) { ) {
return openSignInModal(); return openSignInModal();
} }
@@ -90,7 +90,7 @@ export const UserWithWorkspaceList = ({
onEventEnd?.(); onEventEnd?.();
}, [ }, [
globalDialogService, globalDialogService,
featureFlagService, defaultServerService,
isAuthenticated, isAuthenticated,
onCreatedWorkspace, onCreatedWorkspace,
onEventEnd, onEventEnd,
@@ -1,6 +1,7 @@
import { IconButton, Menu, MenuItem } from '@affine/component'; import { IconButton, Menu, MenuItem } from '@affine/component';
import { Divider } from '@affine/component/ui/divider'; import { Divider } from '@affine/component/ui/divider';
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud'; import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { useSignOut } from '@affine/core/components/hooks/affine/use-sign-out';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
import type { AuthAccountInfo, Server } from '@affine/core/modules/cloud'; import type { AuthAccountInfo, Server } from '@affine/core/modules/cloud';
@@ -161,9 +162,7 @@ const CloudWorkSpaceList = ({
workspaces, workspaces,
]); ]);
const handleSignOut = useAsyncCallback(async () => { const handleSignOut = useSignOut();
await authService.signOut();
}, [authService]);
const handleSignIn = useAsyncCallback(async () => { const handleSignIn = useAsyncCallback(async () => {
globalDialogService.open('sign-in', { globalDialogService.open('sign-in', {
@@ -1,3 +1,4 @@
import { DefaultServerService } from '@affine/core/modules/cloud';
import { DesktopApiService } from '@affine/core/modules/desktop-api'; import { DesktopApiService } from '@affine/core/modules/desktop-api';
import { WorkspacesService } from '@affine/core/modules/workspace'; import { WorkspacesService } from '@affine/core/modules/workspace';
import { import {
@@ -46,16 +47,23 @@ export const Component = ({
const [navigating, setNavigating] = useState(true); const [navigating, setNavigating] = useState(true);
const [creating, setCreating] = useState(false); const [creating, setCreating] = useState(false);
const authService = useService(AuthService); const authService = useService(AuthService);
const defaultServerService = useService(DefaultServerService);
const loggedIn = useLiveData( const loggedIn = useLiveData(
authService.session.status$.map(s => s === 'authenticated') authService.session.status$.map(s => s === 'authenticated')
); );
const allowGuestDemo =
useLiveData(
defaultServerService.server.config$.selector(
c => c.allowGuestDemoWorkspace
)
) ?? true;
const workspacesService = useService(WorkspacesService); const workspacesService = useService(WorkspacesService);
const list = useLiveData(workspacesService.list.workspaces$); const list = useLiveData(workspacesService.list.workspaces$);
const listIsLoading = useLiveData(workspacesService.list.isRevalidating$); const listIsLoading = useLiveData(workspacesService.list.isRevalidating$);
const { openPage, jumpToPage } = useNavigateHelper(); const { openPage, jumpToPage, jumpToSignIn } = useNavigateHelper();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const createOnceRef = useRef(false); const createOnceRef = useRef(false);
@@ -84,6 +92,12 @@ export const Component = ({
return; return;
} }
if (!allowGuestDemo && !loggedIn) {
localStorage.removeItem('last_workspace_id');
jumpToSignIn();
return;
}
// check is user logged in && has cloud workspace // check is user logged in && has cloud workspace
if (searchParams.get('initCloud') === 'true') { if (searchParams.get('initCloud') === 'true') {
if (loggedIn) { if (loggedIn) {
@@ -111,10 +125,12 @@ export const Component = ({
openPage(openWorkspace.id, defaultIndexRoute, RouteLogic.REPLACE); openPage(openWorkspace.id, defaultIndexRoute, RouteLogic.REPLACE);
} }
}, [ }, [
allowGuestDemo,
createCloudWorkspace, createCloudWorkspace,
list, list,
openPage, openPage,
searchParams, searchParams,
jumpToSignIn,
listIsLoading, listIsLoading,
loggedIn, loggedIn,
navigating, navigating,
@@ -128,7 +144,9 @@ export const Component = ({
}, [desktopApi]); }, [desktopApi]);
useEffect(() => { useEffect(() => {
setCreating(true); if (listIsLoading || list.length > 0) {
return;
}
createFirstAppData(workspacesService) createFirstAppData(workspacesService)
.then(createdWorkspace => { .then(createdWorkspace => {
if (createdWorkspace) { if (createdWorkspace) {
@@ -148,7 +166,15 @@ export const Component = ({
.finally(() => { .finally(() => {
setCreating(false); setCreating(false);
}); });
}, [jumpToPage, openPage, workspacesService]); }, [
jumpToPage,
jumpToSignIn,
openPage,
workspacesService,
loggedIn,
listIsLoading,
list,
]);
if (navigating || creating) { if (navigating || creating) {
return fallback ?? <AppContainer fallback />; return fallback ?? <AppContainer fallback />;
@@ -83,7 +83,8 @@ const AcceptInvite = ({ inviteId: targetInviteId }: { inviteId: string }) => {
const onSignOut = useAsyncCallback(async () => { const onSignOut = useAsyncCallback(async () => {
await authService.signOut(); await authService.signOut();
}, [authService]); navigateHelper.jumpToSignIn();
}, [authService, navigateHelper]);
if ((loading && !requestToJoinLoading) || inviteId !== targetInviteId) { if ((loading && !requestToJoinLoading) || inviteId !== targetInviteId) {
return null; return null;
@@ -228,7 +228,8 @@ const CloudWorkSpaceList = ({
const handleSignOut = useAsyncCallback(async () => { const handleSignOut = useAsyncCallback(async () => {
await authService.signOut(); await authService.signOut();
}, [authService]); navigateHelper.jumpToSignIn();
}, [authService, navigateHelper]);
const handleSignIn = useAsyncCallback(async () => { const handleSignIn = useAsyncCallback(async () => {
globalDialogService.open('sign-in', { globalDialogService.open('sign-in', {
@@ -26,6 +26,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] =
maxLength: 32, maxLength: 32,
}, },
}, },
allowGuestDemoWorkspace: true,
}, },
}, },
] ]
@@ -56,6 +57,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] =
maxLength: 32, maxLength: 32,
}, },
}, },
allowGuestDemoWorkspace: true,
}, },
}, },
] ]
@@ -88,6 +90,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] =
maxLength: 32, maxLength: 32,
}, },
}, },
allowGuestDemoWorkspace: true,
}, },
}, },
] ]
@@ -120,6 +123,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] =
maxLength: 32, maxLength: 32,
}, },
}, },
allowGuestDemoWorkspace: true,
}, },
}, },
] ]
@@ -148,6 +152,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] =
maxLength: 32, maxLength: 32,
}, },
}, },
allowGuestDemoWorkspace: true,
}, },
}, },
] ]
@@ -178,6 +183,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] =
maxLength: 32, maxLength: 32,
}, },
}, },
allowGuestDemoWorkspace: true,
}, },
}, },
] ]
@@ -82,6 +82,7 @@ export class Server extends Entity<{
credentialsRequirement: config.credentialsRequirement, credentialsRequirement: config.credentialsRequirement,
features: config.features, features: config.features,
oauthProviders: config.oauthProviders, oauthProviders: config.oauthProviders,
allowGuestDemoWorkspace: config.allowGuestDemoWorkspace,
serverName: config.name, serverName: config.name,
type: config.type, type: config.type,
version: config.version, version: config.version,
@@ -82,6 +82,7 @@ export class ServersService extends Service {
credentialsRequirement: config.credentialsRequirement, credentialsRequirement: config.credentialsRequirement,
features: config.features, features: config.features,
oauthProviders: config.oauthProviders, oauthProviders: config.oauthProviders,
allowGuestDemoWorkspace: config.allowGuestDemoWorkspace,
serverName: config.name, serverName: config.name,
type: config.type, type: config.type,
initialized: config.initialized, initialized: config.initialized,
@@ -14,6 +14,7 @@ export interface ServerMetadata {
export interface ServerConfig { export interface ServerConfig {
serverName: string; serverName: string;
features: ServerFeature[]; features: ServerFeature[];
allowGuestDemoWorkspace: boolean;
oauthProviders: OAuthProviderType[]; oauthProviders: OAuthProviderType[];
type: ServerDeploymentType; type: ServerDeploymentType;
initialized?: boolean; initialized?: boolean;
@@ -1,7 +1,6 @@
import type { FlagInfo } from './types'; import type { FlagInfo } from './types';
// const isNotStableBuild = BUILD_CONFIG.appBuildType !== 'stable'; // const isNotStableBuild = BUILD_CONFIG.appBuildType !== 'stable';
const isDesktopEnvironment = BUILD_CONFIG.isElectron;
const isCanaryBuild = BUILD_CONFIG.appBuildType === 'canary'; const isCanaryBuild = BUILD_CONFIG.appBuildType === 'canary';
const isMobile = BUILD_CONFIG.isMobileEdition; const isMobile = BUILD_CONFIG.isMobileEdition;
@@ -149,15 +148,6 @@ export const AFFINE_FLAGS = {
configurable: isCanaryBuild && !isMobile, configurable: isCanaryBuild && !isMobile,
defaultState: isCanaryBuild, defaultState: isCanaryBuild,
}, },
enable_local_workspace: {
category: 'affine',
displayName:
'com.affine.settings.workspace.experimental-features.enable-local-workspace.name',
description:
'com.affine.settings.workspace.experimental-features.enable-local-workspace.description',
configurable: isCanaryBuild,
defaultState: isDesktopEnvironment || isCanaryBuild,
},
enable_advanced_block_visibility: { enable_advanced_block_visibility: {
category: 'blocksuite', category: 'blocksuite',
bsFlag: 'enable_advanced_block_visibility', bsFlag: 'enable_advanced_block_visibility',