refactor(core): auth (#7999)

closes AF-753 AF-1227
This commit is contained in:
forehalo
2024-09-03 09:03:43 +00:00
parent 8b0afd6eeb
commit e33aa35f7e
18 changed files with 286 additions and 166 deletions
@@ -126,6 +126,8 @@ export const OnboardingPage = ({
return null; return null;
} }
// deprecated
// TODO(@forehalo): remove
if (callbackUrl?.startsWith('/open-app/signin-redirect')) { if (callbackUrl?.startsWith('/open-app/signin-redirect')) {
const url = new URL(callbackUrl, window.location.origin); const url = new URL(callbackUrl, window.location.origin);
url.searchParams.set('next', 'onboarding'); url.searchParams.set('next', 'onboarding');
@@ -30,7 +30,7 @@ const OAuthProviderMap: Record<
}, },
}; };
export function OAuth({ redirectUri }: { redirectUri?: string | null }) { export function OAuth() {
const serverConfig = useService(ServerConfigService).serverConfig; const serverConfig = useService(ServerConfigService).serverConfig;
const oauth = useLiveData(serverConfig.features$.map(r => r?.oauth)); const oauth = useLiveData(serverConfig.features$.map(r => r?.oauth));
const oauthProviders = useLiveData( const oauthProviders = useLiveData(
@@ -47,21 +47,11 @@ export function OAuth({ redirectUri }: { redirectUri?: string | null }) {
} }
return oauthProviders?.map(provider => ( return oauthProviders?.map(provider => (
<OAuthProvider <OAuthProvider key={provider} provider={provider} />
key={provider}
provider={provider}
redirectUri={redirectUri}
/>
)); ));
} }
function OAuthProvider({ function OAuthProvider({ provider }: { provider: OAuthProviderType }) {
provider,
redirectUri,
}: {
provider: OAuthProviderType;
redirectUri?: string | null;
}) {
const { icon } = OAuthProviderMap[provider]; const { icon } = OAuthProviderMap[provider];
const authService = useService(AuthService); const authService = useService(AuthService);
const [isConnecting, setIsConnecting] = useState(false); const [isConnecting, setIsConnecting] = useState(false);
@@ -69,7 +59,7 @@ function OAuthProvider({
const onClick = useAsyncCallback(async () => { const onClick = useAsyncCallback(async () => {
try { try {
setIsConnecting(true); setIsConnecting(true);
await authService.signInOauth(provider, redirectUri); await authService.signInOauth(provider);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
notify.error({ title: 'Failed to sign in, please try again.' }); notify.error({ title: 'Failed to sign in, please try again.' });
@@ -77,7 +67,7 @@ function OAuthProvider({
setIsConnecting(false); setIsConnecting(false);
track.$.$.auth.oauth({ provider }); track.$.$.auth.oauth({ provider });
} }
}, [authService, provider, redirectUri]); }, [authService, provider]);
return ( return (
<Button <Button
@@ -38,6 +38,7 @@ export const SignIn: FC<AuthPanelProps> = ({
const [isValidEmail, setIsValidEmail] = useState(true); const [isValidEmail, setIsValidEmail] = useState(true);
const { openModal } = useAtomValue(authAtom); const { openModal } = useAtomValue(authAtom);
const errorMsg = searchParams.get('error');
useEffect(() => { useEffect(() => {
const timeout = setInterval(() => { const timeout = setInterval(() => {
@@ -65,32 +66,22 @@ export const SignIn: FC<AuthPanelProps> = ({
setAuthEmail(email); setAuthEmail(email);
try { try {
const { hasPassword, isExist: isUserExist } = const { hasPassword, registered } =
await authService.checkUserByEmail(email); await authService.checkUserByEmail(email);
if (verifyToken) { if (verifyToken) {
if (isUserExist) { if (registered) {
// provider password sign-in if user has by default // provider password sign-in if user has by default
// If with payment, onl support email sign in to avoid redirect to affine app // If with payment, onl support email sign in to avoid redirect to affine app
if (hasPassword) { if (hasPassword) {
setAuthState('signInWithPassword'); setAuthState('signInWithPassword');
} else { } else {
track.$.$.auth.signIn(); track.$.$.auth.signIn();
await authService.sendEmailMagicLink( await authService.sendEmailMagicLink(email, verifyToken, challenge);
email,
verifyToken,
challenge,
searchParams.get('redirect_uri')
);
setAuthState('afterSignInSendEmail'); setAuthState('afterSignInSendEmail');
} }
} else { } else {
await authService.sendEmailMagicLink( await authService.sendEmailMagicLink(email, verifyToken, challenge);
email,
verifyToken,
challenge,
searchParams.get('redirect_uri')
);
track.$.$.auth.signUp(); track.$.$.auth.signUp();
setAuthState('afterSignUpSendEmail'); setAuthState('afterSignUpSendEmail');
} }
@@ -105,15 +96,7 @@ export const SignIn: FC<AuthPanelProps> = ({
} }
setIsMutating(false); setIsMutating(false);
}, [ }, [authService, challenge, email, setAuthEmail, setAuthState, verifyToken]);
authService,
challenge,
email,
searchParams,
setAuthEmail,
setAuthState,
verifyToken,
]);
return ( return (
<> <>
@@ -122,7 +105,7 @@ export const SignIn: FC<AuthPanelProps> = ({
subTitle={t['com.affine.brand.affineCloud']()} subTitle={t['com.affine.brand.affineCloud']()}
/> />
<OAuth redirectUri={searchParams.get('redirect_uri')} /> <OAuth />
<div className={style.authModalContent}> <div className={style.authModalContent}>
<AuthInput <AuthInput
@@ -142,8 +125,6 @@ export const SignIn: FC<AuthPanelProps> = ({
onEnter={onContinue} onEnter={onContinue}
/> />
{verifyToken ? null : <Captcha />}
{verifyToken ? ( {verifyToken ? (
<Button <Button
style={{ width: '100%' }} style={{ width: '100%' }}
@@ -157,7 +138,11 @@ export const SignIn: FC<AuthPanelProps> = ({
> >
{t['com.affine.auth.sign.email.continue']()} {t['com.affine.auth.sign.email.continue']()}
</Button> </Button>
) : null} ) : (
<Captcha />
)}
{errorMsg && <div className={style.errorMessage}>{errorMsg}</div>}
<div className={style.authMessage}> <div className={style.authMessage}>
{/*prettier-ignore*/} {/*prettier-ignore*/}
@@ -14,6 +14,14 @@ export const authMessage = style({
fontSize: cssVar('fontXs'), fontSize: cssVar('fontXs'),
lineHeight: 1.5, lineHeight: 1.5,
}); });
export const errorMessage = style({
marginTop: '30px',
color: cssVar('textHighlightForegroundRed'),
fontSize: cssVar('fontXs'),
lineHeight: 1.5,
});
globalStyle(`${authMessage} a`, { globalStyle(`${authMessage} a`, {
color: cssVar('linkColor'), color: cssVar('linkColor'),
}); });
@@ -1,4 +1,5 @@
import { AIProvider } from '@affine/core/blocksuite/presets/ai'; import { AIProvider } from '@affine/core/blocksuite/presets/ai';
import { buildAppUrl, popupWindow } from '@affine/core/utils';
import { apis, appInfo } from '@affine/electron-api'; import { apis, appInfo } from '@affine/electron-api';
import type { OAuthProviderType } from '@affine/graphql'; import type { OAuthProviderType } from '@affine/graphql';
import { import {
@@ -80,72 +81,82 @@ export class AuthService extends Service {
async sendEmailMagicLink( async sendEmailMagicLink(
email: string, email: string,
verifyToken: string, verifyToken: string,
challenge?: string, challenge?: string
redirectUri?: string | null
) { ) {
const searchParams = new URLSearchParams(); const searchParams = new URLSearchParams();
if (challenge) { if (challenge) {
searchParams.set('challenge', challenge); searchParams.set('challenge', challenge);
} }
searchParams.set('token', verifyToken); searchParams.set('token', verifyToken);
const redirect = environment.isDesktop
? this.buildRedirectUri('/open-app/signin-redirect')
: (redirectUri ?? location.href);
searchParams.set('redirect_uri', redirect.toString());
const res = await this.fetchService.fetch( const res = await this.fetchService.fetch(
'/api/auth/sign-in?' + searchParams.toString(), '/api/auth/sign-in?' + searchParams.toString(),
{ {
method: 'POST', method: 'POST',
body: JSON.stringify({ email }), body: JSON.stringify({
email,
// we call it [callbackUrl] instead of [redirect_uri]
// to make it clear the url is used to finish the sign-in process instead of redirect after signed-in
callbackUrl: buildAppUrl('/magic-link', {
desktop: environment.isDesktop,
openInHiddenWindow: true,
redirectFromWeb: true,
}),
}),
headers: { headers: {
'content-type': 'application/json', 'content-type': 'application/json',
}, },
} }
); );
if (!res?.ok) { if (!res.ok) {
throw new Error('Failed to send email'); throw new Error('Failed to send email');
} }
} }
async signInOauth(provider: OAuthProviderType, redirectUri?: string | null) { async signInOauth(provider: OAuthProviderType) {
const res = await this.fetchService.fetch('/api/oauth/preflight', {
method: 'POST',
body: JSON.stringify({ provider }),
headers: {
'content-type': 'application/json',
},
});
if (!res.ok) {
throw new Error(`Failed to sign in with ${provider}`);
}
let { url } = await res.json();
// change `state=xxx` to `state={state:xxx,native:true}`
// so we could know the callback should be redirect to native app
const oauthUrl = new URL(url);
oauthUrl.searchParams.set(
'state',
JSON.stringify({
state: oauthUrl.searchParams.get('state'),
client: environment.isDesktop ? appInfo?.schema : 'web',
})
);
url = oauthUrl.toString();
if (environment.isDesktop) { if (environment.isDesktop) {
await apis?.ui.openExternal( await apis?.ui.openExternal(url);
`${
runtimeConfig.serverUrlPrefix
}/desktop-signin?provider=${provider}&redirect_uri=${this.buildRedirectUri(
'/open-app/signin-redirect'
)}`
);
} else { } else {
location.href = `${ popupWindow(url);
runtimeConfig.serverUrlPrefix
}/oauth/login?provider=${provider}&redirect_uri=${encodeURIComponent(
redirectUri ?? location.pathname
)}`;
} }
return; return;
} }
async signInPassword(credential: { email: string; password: string }) { async signInPassword(credential: { email: string; password: string }) {
const searchParams = new URLSearchParams(); const res = await this.fetchService.fetch('/api/auth/sign-in', {
const redirectUri = new URL(location.href); method: 'POST',
if (environment.isDesktop) { body: JSON.stringify(credential),
redirectUri.pathname = this.buildRedirectUri('/open-app/signin-redirect'); headers: {
} 'content-type': 'application/json',
searchParams.set('redirect_uri', redirectUri.toString()); },
});
const res = await this.fetchService.fetch(
'/api/auth/sign-in?' + searchParams.toString(),
{
method: 'POST',
body: JSON.stringify(credential),
headers: {
'content-type': 'application/json',
},
}
);
if (!res.ok) { if (!res.ok) {
throw new Error('Failed to sign in'); throw new Error('Failed to sign in');
} }
@@ -158,19 +169,6 @@ export class AuthService extends Service {
this.session.revalidate(); this.session.revalidate();
} }
private buildRedirectUri(callbackUrl: string) {
const params: string[][] = [];
if (environment.isDesktop && appInfo?.schema) {
params.push(['schema', appInfo.schema]);
}
const query =
params.length > 0
? '?' +
params.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&')
: '';
return callbackUrl + query;
}
checkUserByEmail(email: string) { checkUserByEmail(email: string) {
return this.store.checkUserByEmail(email); return this.store.checkUserByEmail(email);
} }
@@ -1,5 +1,4 @@
import { import {
getUserQuery,
removeAvatarMutation, removeAvatarMutation,
updateUserProfileMutation, updateUserProfileMutation,
uploadAvatarMutation, uploadAvatarMutation,
@@ -81,15 +80,23 @@ export class AuthStore extends Store {
} }
async checkUserByEmail(email: string) { async checkUserByEmail(email: string) {
const data = await this.gqlService.gql({ const res = await this.fetchService.fetch('/api/auth/preflight', {
query: getUserQuery, method: 'POST',
variables: { body: JSON.stringify({ email }),
email, headers: {
'content-type': 'application/json',
}, },
}); });
return {
isExist: !!data.user, if (!res.ok) {
hasPassword: !!data.user?.hasPassword, throw new Error(`Failed to check user by email: ${email}`);
}
const data = (await res.json()) as {
registered: boolean;
hasPassword: boolean;
}; };
return data;
} }
} }
@@ -1,5 +1,9 @@
import { useLiveData, useService } from '@toeverything/infra';
import { useEffect } from 'react';
import { type LoaderFunction, redirect } from 'react-router-dom'; import { type LoaderFunction, redirect } from 'react-router-dom';
import { AuthService } from '../modules/cloud';
export const loader: LoaderFunction = async ({ request }) => { export const loader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url); const url = new URL(request.url);
const queries = url.searchParams; const queries = url.searchParams;
@@ -35,6 +39,17 @@ export const loader: LoaderFunction = async ({ request }) => {
}; };
export const Component = () => { export const Component = () => {
const service = useService(AuthService);
const user = useLiveData(service.session.account$);
useEffect(() => {
service.session.revalidate();
}, [service]);
// TODO(@pengx17): window.close() in electron hidden window will close main window as well
if (!environment.isDesktop && user) {
window.close();
}
// TODO(@eyhn): loading ui // TODO(@eyhn): loading ui
return null; return null;
}; };
@@ -0,0 +1,74 @@
import { useLiveData, useService } from '@toeverything/infra';
import { useEffect } from 'react';
import { type LoaderFunction, redirect } from 'react-router-dom';
import { AuthService } from '../modules/cloud';
export const loader: LoaderFunction = async ({ request }) => {
const url = new URL(request.url);
const queries = url.searchParams;
const code = queries.get('code');
let stateStr = queries.get('state') ?? '{}';
let error: string | undefined;
try {
const { state, client } = JSON.parse(stateStr);
stateStr = state;
// bypass code & state to redirect_uri
if (!environment.isDesktop && client && client !== 'web') {
url.searchParams.set('state', JSON.stringify({ state }));
return redirect(
`/open-app/url?url=${encodeURIComponent(`${client}://${url.pathname}${url.search}`)}&hidden=true`
);
}
} catch {
error = 'Invalid oauth callback parameters';
}
const res = await fetch('/api/oauth/callback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ code, state: stateStr }),
});
if (!res.ok) {
try {
const { message } = await res.json();
error = message;
} catch {
error = 'failed to verify sign-in token';
}
}
if (error) {
// TODO(@pengx17): in desktop app, the callback page will be opened in a hidden window
// how could we tell the main window to show the error message?
return redirect(`/signIn?error=${encodeURIComponent(error)}`);
} else {
const body = await res.json();
/* @deprecated handle for old client */
if (body.redirect_uri) {
return redirect(body.redirect_uri);
}
}
return null;
};
export const Component = () => {
const service = useService(AuthService);
const user = useLiveData(service.session.account$);
useEffect(() => {
service.session.revalidate();
}, [service]);
// TODO(@pengx17): window.close() in electron hidden window will close main window as well
if (!environment.isDesktop && user) {
window.close();
}
return null;
};
+19 -12
View File
@@ -148,24 +148,31 @@ const OpenAppImpl = ({ urlToOpen, channel }: OpenAppProps) => {
const OpenUrl = () => { const OpenUrl = () => {
const [params] = useSearchParams(); const [params] = useSearchParams();
const urlToOpen = useMemo(() => params.get('url'), [params]); const urlToOpen = params.get('url');
const channel = useMemo(() => { params.delete('url');
const urlObj = new URL(urlToOpen || '');
const maybeSchema = appSchemas.safeParse(urlObj.protocol.replace(':', ''));
return schemaToChanel[maybeSchema.success ? maybeSchema.data : 'affine'];
}, [urlToOpen]);
return <OpenAppImpl urlToOpen={urlToOpen} channel={channel} />; const urlObj = new URL(urlToOpen || '');
const maybeSchema = appSchemas.safeParse(urlObj.protocol.replace(':', ''));
const channel =
schemaToChanel[maybeSchema.success ? maybeSchema.data : 'affine'];
params.forEach((v, k) => {
urlObj.searchParams.set(k, v);
});
return <OpenAppImpl urlToOpen={urlObj.toString()} channel={channel} />;
}; };
/**
* @deprecated
*/
const OpenOAuthJwt = () => { const OpenOAuthJwt = () => {
const { currentUser } = useLoaderData() as LoaderData; const { currentUser } = useLoaderData() as LoaderData;
const [params] = useSearchParams(); const [params] = useSearchParams();
const schema = useMemo(() => {
const maybeSchema = appSchemas.safeParse(params.get('schema')); const maybeSchema = appSchemas.safeParse(params.get('schema'));
return maybeSchema.success ? maybeSchema.data : 'affine'; const schema = maybeSchema.success ? maybeSchema.data : 'affine';
}, [params]); const next = params.get('next');
const next = useMemo(() => params.get('next'), [params]);
const channel = schemaToChanel[schema as Schema]; const channel = schemaToChanel[schema as Schema];
if (!currentUser || !currentUser?.token?.sessionToken) { if (!currentUser || !currentUser?.token?.sessionToken) {
@@ -2,6 +2,7 @@ import { DebugLogger } from '@affine/debug';
import { type LoaderFunction, Navigate, useLoaderData } from 'react-router-dom'; import { type LoaderFunction, Navigate, useLoaderData } from 'react-router-dom';
const trustedDomain = [ const trustedDomain = [
'google.com',
'stripe.com', 'stripe.com',
'github.com', 'github.com',
'twitter.com', 'twitter.com',
+14 -8
View File
@@ -74,10 +74,6 @@ export const topLevelRoutes = [
path: '/magic-link', path: '/magic-link',
lazy: () => import('./pages/magic-link'), lazy: () => import('./pages/magic-link'),
}, },
{
path: '/open-app/:action',
lazy: () => import('./pages/open-app'),
},
{ {
path: '/upgrade-success', path: '/upgrade-success',
lazy: () => import('./pages/upgrade-success'), lazy: () => import('./pages/upgrade-success'),
@@ -86,10 +82,6 @@ export const topLevelRoutes = [
path: '/ai-upgrade-success', path: '/ai-upgrade-success',
lazy: () => import('./pages/ai-upgrade-success'), lazy: () => import('./pages/ai-upgrade-success'),
}, },
{
path: '/desktop-signin',
lazy: () => import('./pages/desktop-signin'),
},
{ {
path: '/onboarding', path: '/onboarding',
lazy: () => import('./pages/onboarding'), lazy: () => import('./pages/onboarding'),
@@ -118,6 +110,20 @@ export const topLevelRoutes = [
path: '/template/import', path: '/template/import',
lazy: () => import('./pages/import-template'), lazy: () => import('./pages/import-template'),
}, },
{
path: '/oauth/callback',
lazy: () => import('./pages/oauth-callback'),
},
{
path: '/open-app/:action',
lazy: () => import('./pages/open-app'),
},
// deprecated, keep for old client compatibility
// TODO(@forehalo): remove
{
path: '/desktop-signin',
lazy: () => import('./pages/desktop-signin'),
},
{ {
path: '*', path: '*',
lazy: () => import('./pages/404'), lazy: () => import('./pages/404'),
@@ -6,3 +6,4 @@ export * from './popup';
export * from './string2color'; export * from './string2color';
export * from './toast'; export * from './toast';
export * from './unflatten-object'; export * from './unflatten-object';
export * from './url';
+33
View File
@@ -0,0 +1,33 @@
import { appInfo } from '@affine/electron-api';
interface AppUrlOptions {
desktop?: boolean | string;
openInHiddenWindow?: boolean;
redirectFromWeb?: boolean;
}
export function buildAppUrl(path: string, opts: AppUrlOptions = {}) {
// TODO(@EYHN): should use server base url
const webBase = runtimeConfig.serverUrlPrefix;
// TODO(@pengx17): how could we know the corresponding app schema in web environment
if (opts.desktop && appInfo?.schema) {
const urlCtor = new URL(path, webBase);
if (opts.openInHiddenWindow) {
urlCtor.searchParams.set('hidden', 'true');
}
const url = `${appInfo.schema}://${urlCtor.pathname}${urlCtor.search}`;
if (opts.redirectFromWeb) {
const redirect_uri = new URL('/open-app/url', webBase);
redirect_uri.searchParams.set('url', url);
return redirect_uri.toString();
}
return url;
} else {
return new URL(path, webBase).toString();
}
}
@@ -39,6 +39,8 @@ const desktopWhiteList = [
'/upgrade-success', '/upgrade-success',
'/ai-upgrade-success', '/ai-upgrade-success',
'/share', '/share',
'/oauth',
'/magic-link',
]; ];
if ( if (
!environment.isDesktop && !environment.isDesktop &&
@@ -2,13 +2,13 @@ import path from 'node:path';
import type { App } from 'electron'; import type { App } from 'electron';
import { buildType, CLOUD_BASE_URL, isDev } from './config'; import { buildType, isDev } from './config';
import { mainWindowOrigin } from './constants'; import { mainWindowOrigin } from './constants';
import { logger } from './logger'; import { logger } from './logger';
import { import {
getMainWindow, getMainWindow,
handleOpenUrlInHiddenWindow, openUrlInHiddenWindow,
setCookie, openUrlInMainWindow,
} from './windows-manager'; } from './windows-manager';
let protocol = buildType === 'stable' ? 'affine' : `affine-${buildType}`; let protocol = buildType === 'stable' ? 'affine' : `affine-${buildType}`;
@@ -61,51 +61,28 @@ async function handleAffineUrl(url: string) {
logger.info('open affine url', url); logger.info('open affine url', url);
const urlObj = new URL(url); const urlObj = new URL(url);
logger.info('handle affine schema action', urlObj.hostname); logger.info('handle affine schema action', urlObj.hostname);
// handle more actions here
// hostname is the action name
if (urlObj.hostname === 'signin-redirect') {
await handleOauthJwt(url);
}
if (urlObj.hostname === 'bring-to-front') { if (urlObj.hostname === 'bring-to-front') {
const mainWindow = await getMainWindow(); const mainWindow = await getMainWindow();
if (mainWindow) { if (mainWindow) {
mainWindow.show(); mainWindow.show();
} }
} else {
await openUrl(urlObj);
} }
} }
async function handleOauthJwt(url: string) { async function openUrl(urlObj: URL) {
const mainWindow = await getMainWindow(); const params = urlObj.searchParams;
if (url && mainWindow) {
try {
mainWindow.show();
const urlObj = new URL(url);
const token = urlObj.searchParams.get('token');
if (!token) { const openInHiddenWindow = params.get('hidden');
logger.error('no token in url', url); params.delete('hidden');
return;
}
// set token to cookie const url = mainWindowOrigin + urlObj.pathname + '?' + params.toString();
await setCookie({ if (!openInHiddenWindow) {
url: CLOUD_BASE_URL, await openUrlInHiddenWindow(url);
httpOnly: true, } else {
value: token, // TODO(@pengx17): somehow the page won't load the url passed, help needed
secure: true, await openUrlInMainWindow(url);
name: 'affine_session',
expirationDate: Math.floor(
Date.now() / 1000 +
3600 *
24 *
399 /* as long as possible, cookie max expires is 400 days */
),
});
// hacks to refresh auth state in the main window
await handleOpenUrlInHiddenWindow(mainWindowOrigin + '/auth/signIn');
} catch (e) {
logger.error('failed to open url in popup', e);
}
} }
} }
@@ -229,16 +229,15 @@ export async function showMainWindow() {
/** /**
* Open a URL in a hidden window. * Open a URL in a hidden window.
* This is useful for opening a URL in the background without user interaction for *authentication*.
*/ */
export async function handleOpenUrlInHiddenWindow(url: string) { export async function openUrlInHiddenWindow(url: string) {
const win = new BrowserWindow({ const win = new BrowserWindow({
width: 1200, width: 1200,
height: 600, height: 600,
webPreferences: { webPreferences: {
preload: join(__dirname, './preload.js'), preload: join(__dirname, './preload.js'),
}, },
show: false, show: environment.isDebug,
}); });
win.on('close', e => { win.on('close', e => {
e.preventDefault(); e.preventDefault();
@@ -250,3 +249,11 @@ export async function handleOpenUrlInHiddenWindow(url: string) {
await win.loadURL(url); await win.loadURL(url);
return win; return win;
} }
export async function openUrlInMainWindow(url: string) {
const mainWindow = await getMainWindow();
if (mainWindow) {
mainWindow.show();
await mainWindow.loadURL(url);
}
}
@@ -29,12 +29,20 @@ export function getElectronAPIs() {
}; };
} }
type Schema =
| 'affine'
| 'affine-canary'
| 'affine-beta'
| 'affine-internal'
| 'affine-dev';
// todo: remove duplicated codes // todo: remove duplicated codes
const ReleaseTypeSchema = z.enum(['stable', 'beta', 'canary', 'internal']); const ReleaseTypeSchema = z.enum(['stable', 'beta', 'canary', 'internal']);
const envBuildType = (process.env.BUILD_TYPE || 'canary').trim().toLowerCase(); const envBuildType = (process.env.BUILD_TYPE || 'canary').trim().toLowerCase();
const buildType = ReleaseTypeSchema.parse(envBuildType); const buildType = ReleaseTypeSchema.parse(envBuildType);
const isDev = process.env.NODE_ENV === 'development'; const isDev = process.env.NODE_ENV === 'development';
let schema = buildType === 'stable' ? 'affine' : `affine-${envBuildType}`; let schema =
buildType === 'stable' ? 'affine' : (`affine-${envBuildType}` as Schema);
schema = isDev ? 'affine-dev' : schema; schema = isDev ? 'affine-dev' : schema;
export const appInfo = { export const appInfo = {
@@ -45,7 +53,7 @@ export const appInfo = {
viewId: viewId:
process.argv.find(arg => arg.startsWith('--view-id='))?.split('=')[1] ?? process.argv.find(arg => arg.startsWith('--view-id='))?.split('=')[1] ??
'unknown', 'unknown',
schema: `${schema}`, schema,
}; };
function getMainAPIs() { function getMainAPIs() {
-1
View File
@@ -412,7 +412,6 @@ export const createConfiguration: (
{ context: '/api', target: 'http://localhost:3010' }, { context: '/api', target: 'http://localhost:3010' },
{ context: '/socket.io', target: 'http://localhost:3010', ws: true }, { context: '/socket.io', target: 'http://localhost:3010', ws: true },
{ context: '/graphql', target: 'http://localhost:3010' }, { context: '/graphql', target: 'http://localhost:3010' },
{ context: '/oauth', target: 'http://localhost:3010' },
], ],
} as DevServerConfiguration, } as DevServerConfiguration,
} satisfies webpack.Configuration; } satisfies webpack.Configuration;