feat: support google cloud login in client (#1822)

Co-authored-by: Himself65 <himself65@outlook.com>
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
Horus
2023-04-12 02:42:36 +08:00
committed by GitHub
parent 024c469a2c
commit c0669359ed
17 changed files with 252 additions and 42 deletions

View File

@@ -40,6 +40,9 @@ env:
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}
NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}
AFFINE_GOOGLE_CLIENT_ID: ${{ secrets.AFFINE_GOOGLE_CLIENT_ID }}
AFFINE_GOOGLE_CLIENT_SECRET: ${{ secrets.AFFINE_GOOGLE_CLIENT_SECRET }}
NODE_API_SERVER: 'https://app.affine.pro'
jobs: jobs:
make-macos: make-macos:

View File

@@ -11,3 +11,4 @@ resources/web-static
!.yarn/releases !.yarn/releases
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
dev.json

View File

@@ -0,0 +1,32 @@
import type { RequestInit } from 'undici';
import { fetch, ProxyAgent } from 'undici';
const redirectUri = 'https://affine.pro/client/auth-callback';
export const oauthEndpoint = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.AFFINE_GOOGLE_CLIENT_ID}&redirect_uri=${redirectUri}&response_type=code&scope=openid https://www.googleapis.com/auth/userinfo.email profile&access_type=offline`;
const tokenEndpoint = 'https://oauth2.googleapis.com/token';
export const exchangeToken = async (code: string) => {
const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy;
const proxyAgent = httpProxy ? new ProxyAgent(httpProxy) : undefined;
const postData = {
code,
client_id: process.env.AFFINE_GOOGLE_CLIENT_ID || '',
client_secret: process.env.AFFINE_GOOGLE_CLIENT_SECRET || '',
redirect_uri: redirectUri,
grant_type: 'authorization_code',
};
const requestOptions: RequestInit = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams(postData).toString(),
dispatcher: proxyAgent,
};
return fetch(tokenEndpoint, requestOptions).then(response => {
return response.json();
});
};

View File

@@ -2,13 +2,19 @@ import * as os from 'node:os';
import path from 'node:path'; import path from 'node:path';
import { Storage } from '@affine/octobase-node'; import { Storage } from '@affine/octobase-node';
import { app, shell } from 'electron';
import { BrowserWindow, ipcMain, nativeTheme } from 'electron'; import { BrowserWindow, ipcMain, nativeTheme } from 'electron';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { parse } from 'url';
import { exchangeToken, oauthEndpoint } from './google-auth';
const AFFINE_ROOT = path.join(os.homedir(), '.affine'); const AFFINE_ROOT = path.join(os.homedir(), '.affine');
fs.ensureDirSync(AFFINE_ROOT); fs.ensureDirSync(AFFINE_ROOT);
const logger = console;
// todo: rethink this // todo: rethink this
export const appState = { export const appState = {
storage: new Storage(path.join(AFFINE_ROOT, 'test.db')), storage: new Storage(path.join(AFFINE_ROOT, 'test.db')),
@@ -21,6 +27,7 @@ export const registerHandlers = () => {
ipcMain.handle('ui:theme-change', async (_, theme) => { ipcMain.handle('ui:theme-change', async (_, theme) => {
nativeTheme.themeSource = theme; nativeTheme.themeSource = theme;
logger.info('theme change', theme);
}); });
ipcMain.handle('ui:sidebar-visibility-change', async (_, visible) => { ipcMain.handle('ui:sidebar-visibility-change', async (_, visible) => {
@@ -30,5 +37,38 @@ export const registerHandlers = () => {
// hide window buttons when sidebar is not visible // hide window buttons when sidebar is not visible
w.setWindowButtonVisibility(visible); w.setWindowButtonVisibility(visible);
}); });
logger.info('sidebar visibility change', visible);
});
ipcMain.handle('ui:google-sign-in', async () => {
logger.info('starting google sign in ...');
shell.openExternal(oauthEndpoint);
return new Promise<string>((resolve, reject) => {
const handleOpenUrl = async (_: any, url: string) => {
const mainWindow = BrowserWindow.getAllWindows().find(
w => !w.isDestroyed()
);
const urlObj = parse(url.replace('??', '?'), true);
if (!mainWindow || !url.startsWith('affine://')) return;
const token = (await exchangeToken(urlObj.query['code'] as string)) as {
id_token: string;
};
app.removeListener('open-url', handleOpenUrl);
resolve(token.id_token);
logger.info('google sign in', token);
};
app.on('open-url', handleOpenUrl);
setTimeout(() => {
reject(new Error('Timed out'));
app.removeListener('open-url', handleOpenUrl);
}, 60000);
});
});
ipcMain.handle('main:env-update', async (_, env, value) => {
process.env[env] = value;
}); });
}; };

View File

@@ -1,11 +1,21 @@
import './security-restrictions'; import './security-restrictions';
import { app } from 'electron'; import { app } from 'electron';
import path from 'path';
import { registerHandlers } from './app-state'; import { registerHandlers } from './app-state';
import { restoreOrCreateWindow } from './main-window'; import { restoreOrCreateWindow } from './main-window';
import { registerProtocol } from './protocol'; import { registerProtocol } from './protocol';
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('affine', process.execPath, [
path.resolve(process.argv[1]),
]);
}
} else {
app.setAsDefaultProtocolClient('affine');
}
/** /**
* Prevent multiple instances * Prevent multiple instances
*/ */
@@ -15,7 +25,13 @@ if (!isSingleInstance) {
process.exit(0); process.exit(0);
} }
app.on('second-instance', restoreOrCreateWindow); app.on('second-instance', (event, argv) => {
restoreOrCreateWindow();
});
app.on('open-url', async (_, url) => {
// todo: handle `affine://...` urls
});
/** /**
* Disable Hardware Acceleration for more power-save * Disable Hardware Acceleration for more power-save
@@ -45,7 +61,6 @@ app
.then(registerHandlers) .then(registerHandlers)
.then(restoreOrCreateWindow) .then(restoreOrCreateWindow)
.catch(e => console.error('Failed create window:', e)); .catch(e => console.error('Failed create window:', e));
/** /**
* Check new app version in production mode only * Check new app version in production mode only
*/ */

View File

@@ -10,6 +10,14 @@ app.on('web-contents-created', (_, contents) => {
* @see https://www.electronjs.org/docs/latest/tutorial/security#13-disable-or-limit-navigation * @see https://www.electronjs.org/docs/latest/tutorial/security#13-disable-or-limit-navigation
*/ */
contents.on('will-navigate', (event, url) => { contents.on('will-navigate', (event, url) => {
if (
(process.env.DEV_SERVER_URL &&
url.startsWith(process.env.DEV_SERVER_URL)) ||
url.startsWith('affine://') ||
url.startsWith('file://.')
) {
return;
}
// Prevent navigation // Prevent navigation
event.preventDefault(); event.preventDefault();
shell.openExternal(url).catch(console.error); shell.openExternal(url).catch(console.error);

View File

@@ -7,6 +7,6 @@ interface Window {
* *
* @see https://github.com/cawa-93/dts-for-context-bridge * @see https://github.com/cawa-93/dts-for-context-bridge
*/ */
readonly apis: { workspaceSync: (id: string) => Promise<any>; onThemeChange: (theme: string) => Promise<any>; onSidebarVisibilityChange: (visible: boolean) => Promise<any>; }; readonly apis: { workspaceSync: (id: string) => Promise<any>; onThemeChange: (theme: string) => Promise<any>; onSidebarVisibilityChange: (visible: boolean) => Promise<any>; googleSignIn: () => Promise<string>; updateEnv: (env: string, value: string) => void; };
readonly appInfo: { electron: boolean; isMacOS: boolean; }; readonly appInfo: { electron: boolean; isMacOS: boolean; };
} }

View File

@@ -21,7 +21,6 @@ import { isMacOS } from '../../utils';
* *
* @see https://github.com/cawa-93/dts-for-context-bridge * @see https://github.com/cawa-93/dts-for-context-bridge
*/ */
contextBridge.exposeInMainWorld('apis', { contextBridge.exposeInMainWorld('apis', {
workspaceSync: (id: string) => ipcRenderer.invoke('octo:workspace-sync', id), workspaceSync: (id: string) => ipcRenderer.invoke('octo:workspace-sync', id),
// ui // ui
@@ -30,6 +29,18 @@ contextBridge.exposeInMainWorld('apis', {
onSidebarVisibilityChange: (visible: boolean) => onSidebarVisibilityChange: (visible: boolean) =>
ipcRenderer.invoke('ui:sidebar-visibility-change', visible), ipcRenderer.invoke('ui:sidebar-visibility-change', visible),
/**
* Try sign in using Google and return a Google IDToken
*/
googleSignIn: (): Promise<string> => ipcRenderer.invoke('ui:google-sign-in'),
/**
* Secret backdoor to update environment variables in main process
*/
updateEnv: (env: string, value: string) => {
ipcRenderer.invoke('main:env-update', env, value);
},
}); });
contextBridge.exposeInMainWorld('appInfo', { contextBridge.exposeInMainWorld('appInfo', {

View File

@@ -36,6 +36,7 @@
"@electron-forge/maker-zip": "^6.1.1", "@electron-forge/maker-zip": "^6.1.1",
"@electron-forge/shared-types": "^6.1.1", "@electron-forge/shared-types": "^6.1.1",
"@electron/rebuild": "^3.2.10", "@electron/rebuild": "^3.2.10",
"@electron/remote": "2.0.9",
"dts-for-context-bridge": "^0.7.1", "dts-for-context-bridge": "^0.7.1",
"electron": "24.0.0", "electron": "24.0.0",
"esbuild": "^0.17.16", "esbuild": "^0.17.16",
@@ -44,7 +45,19 @@
"dependencies": { "dependencies": {
"cross-env": "7.0.3", "cross-env": "7.0.3",
"electron-window-state": "^5.0.3", "electron-window-state": "^5.0.3",
"fs-extra": "^11.1.1" "firebase": "^9.18.0",
"fs-extra": "^11.1.1",
"undici": "^5.21.2"
},
"build": {
"protocols": [
{
"name": "affine",
"schemes": [
"affine"
]
}
]
}, },
"packageManager": "yarn@3.5.0" "packageManager": "yarn@3.5.0"
} }

View File

@@ -2,7 +2,6 @@ import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import * as url from 'node:url'; import * as url from 'node:url';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
// const __dirname = new URL('.', import.meta.url).pathname;
const { node } = JSON.parse( const { node } = JSON.parse(
fs.readFileSync( fs.readFileSync(
path.join(__dirname, '../electron-vendors.autogen.json'), path.join(__dirname, '../electron-vendors.autogen.json'),
@@ -20,22 +19,36 @@ const nativeNodeModulesPlugin = {
}, },
}; };
/** @type {import('esbuild').BuildOptions} */ // List of env that will be replaced by esbuild
export const mainConfig = { const ENV_MACROS = ['AFFINE_GOOGLE_CLIENT_ID', 'AFFINE_GOOGLE_CLIENT_SECRET'];
entryPoints: ['layers/main/src/index.ts'],
outdir: 'dist/layers/main',
bundle: true,
target: `node${node}`,
platform: 'node',
external: ['electron'],
plugins: [nativeNodeModulesPlugin],
};
export const preloadConfig = { /** @return {{main: import('esbuild').BuildOptions, preload: import('esbuild').BuildOptions}} */
entryPoints: ['layers/preload/src/index.ts'], export default () => {
outdir: 'dist/layers/preload', const define = Object.fromEntries(
bundle: true, ENV_MACROS.map(key => [
target: `node${node}`, 'process.env.' + key,
platform: 'node', JSON.stringify(process.env[key] ?? ''),
external: ['electron'], ])
);
return {
main: {
entryPoints: ['layers/main/src/index.ts'],
outdir: 'dist/layers/main',
bundle: true,
target: `node${node}`,
platform: 'node',
external: ['electron'],
plugins: [nativeNodeModulesPlugin],
define: define,
},
preload: {
entryPoints: ['layers/preload/src/index.ts'],
outdir: 'dist/layers/preload',
bundle: true,
target: `node${node}`,
platform: 'node',
external: ['electron'],
define: define,
},
};
}; };

View File

@@ -1,10 +1,16 @@
import { spawn } from 'node:child_process'; import { spawn } from 'node:child_process';
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { generateAsync } from 'dts-for-context-bridge'; import { generateAsync } from 'dts-for-context-bridge';
import electronPath from 'electron'; import electronPath from 'electron';
import * as esbuild from 'esbuild'; import * as esbuild from 'esbuild';
import { mainConfig, preloadConfig } from './common.mjs'; import commonFn from './common.mjs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/** @type 'production' | 'development'' */ /** @type 'production' | 'development'' */
const mode = (process.env.NODE_ENV = process.env.NODE_ENV || 'development'); const mode = (process.env.NODE_ENV = process.env.NODE_ENV || 'development');
@@ -17,6 +23,17 @@ const stderrFilterPatterns = [
/ExtensionLoadWarning/, /ExtensionLoadWarning/,
]; ];
// these are set before calling commonFn so we have a chance to override them
try {
const devJson = readFileSync(path.resolve(__dirname, '../dev.json'), 'utf-8');
const devEnv = JSON.parse(devJson);
Object.assign(process.env, devEnv);
} catch (err) {
console.warn(
`Could not read dev.json. Some functions may not work as expected.`
);
}
// hard-coded for now: // hard-coded for now:
// fixme(xp): report error if app is not running on port 8080 // fixme(xp): report error if app is not running on port 8080
process.env.DEV_SERVER_URL = `http://localhost:8080`; process.env.DEV_SERVER_URL = `http://localhost:8080`;
@@ -35,26 +52,28 @@ function spawnOrReloadElectron() {
spawnProcess.stdout.on( spawnProcess.stdout.on(
'data', 'data',
d => d.toString().trim() && console.warn(d.toString(), { timestamp: true }) d => d.toString().trim() && console.warn(d.toString())
); );
spawnProcess.stderr.on('data', d => { spawnProcess.stderr.on('data', d => {
const data = d.toString().trim(); const data = d.toString().trim();
if (!data) return; if (!data) return;
const mayIgnore = stderrFilterPatterns.some(r => r.test(data)); const mayIgnore = stderrFilterPatterns.some(r => r.test(data));
if (mayIgnore) return; if (mayIgnore) return;
console.error(data, { timestamp: true }); console.error(data);
}); });
// Stops the watch script when the application has been quit // Stops the watch script when the application has been quit
spawnProcess.on('exit', process.exit); spawnProcess.on('exit', process.exit);
} }
const common = commonFn();
async function main() { async function main() {
async function watchPreload(onInitialBuild) { async function watchPreload(onInitialBuild) {
const preloadBuild = await esbuild.context({ const preloadBuild = await esbuild.context({
...preloadConfig, ...common.preload,
plugins: [ plugins: [
...(preloadConfig.plugins ?? []), ...(common.preload.plugins ?? []),
{ {
name: 'affine-dev:reload-app-on-preload-change', name: 'affine-dev:reload-app-on-preload-change',
setup(build) { setup(build) {
@@ -81,13 +100,14 @@ async function main() {
async function watchMain() { async function watchMain() {
const mainBuild = await esbuild.context({ const mainBuild = await esbuild.context({
...mainConfig, ...common.main,
define: { define: {
...common.main.define,
'process.env.NODE_ENV': `"${mode}"`, 'process.env.NODE_ENV': `"${mode}"`,
'process.env.DEV_SERVER_URL': `"${process.env.DEV_SERVER_URL}"`, 'process.env.DEV_SERVER_URL': `"${process.env.DEV_SERVER_URL}"`,
}, },
plugins: [ plugins: [
...(mainConfig.plugins ?? []), ...(common.main.plugins ?? []),
{ {
name: 'affine-dev:reload-app-on-main-change', name: 'affine-dev:reload-app-on-main-change',
setup(build) { setup(build) {

View File

@@ -5,7 +5,7 @@ import path from 'node:path';
import * as esbuild from 'esbuild'; import * as esbuild from 'esbuild';
import { mainConfig, preloadConfig } from './common.mjs'; import commonFn from './common.mjs';
const repoRootDir = path.join(__dirname, '..', '..', '..'); const repoRootDir = path.join(__dirname, '..', '..', '..');
const electronRootDir = path.join(__dirname, '..'); const electronRootDir = path.join(__dirname, '..');
@@ -62,13 +62,13 @@ async function cleanup() {
} }
async function buildLayers() { async function buildLayers() {
await esbuild.build({ const common = commonFn();
...preloadConfig, await esbuild.build(common.preload);
});
await esbuild.build({ await esbuild.build({
...mainConfig, ...common.main,
define: { define: {
...common.main.define,
'process.env.NODE_ENV': `"production"`, 'process.env.NODE_ENV': `"production"`,
}, },
}); });

View File

@@ -123,12 +123,15 @@ __metadata:
"@electron-forge/maker-zip": ^6.1.1 "@electron-forge/maker-zip": ^6.1.1
"@electron-forge/shared-types": ^6.1.1 "@electron-forge/shared-types": ^6.1.1
"@electron/rebuild": ^3.2.10 "@electron/rebuild": ^3.2.10
"@electron/remote": 2.0.9
cross-env: 7.0.3 cross-env: 7.0.3
dts-for-context-bridge: ^0.7.1 dts-for-context-bridge: ^0.7.1
electron: 24.0.0 electron: 24.0.0
electron-window-state: ^5.0.3 electron-window-state: ^5.0.3
esbuild: ^0.17.16 esbuild: ^0.17.16
firebase: ^9.18.0
fs-extra: ^11.1.1 fs-extra: ^11.1.1
undici: ^5.21.2
zx: ^7.2.1 zx: ^7.2.1
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@@ -2234,6 +2237,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@electron/remote@npm:2.0.9":
version: 2.0.9
resolution: "@electron/remote@npm:2.0.9"
peerDependencies:
electron: ">= 13.0.0"
checksum: 7949c528df0ecc9661c0b43e7f2586befb9eddfb53739adf204dc2097ba4331fb08b01f6904636e4c566d28b051a80a55718fb643ee2d2df0793c0c05278dbd4
languageName: node
linkType: hard
"@electron/universal@npm:^1.3.2": "@electron/universal@npm:^1.3.2":
version: 1.3.4 version: 1.3.4
resolution: "@electron/universal@npm:1.3.4" resolution: "@electron/universal@npm:1.3.4"
@@ -7268,6 +7280,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"busboy@npm:^1.6.0":
version: 1.6.0
resolution: "busboy@npm:1.6.0"
dependencies:
streamsearch: ^1.1.0
checksum: 32801e2c0164e12106bf236291a00795c3c4e4b709ae02132883fe8478ba2ae23743b11c5735a0aae8afe65ac4b6ca4568b91f0d9fed1fdbc32ede824a73746e
languageName: node
linkType: hard
"bytes@npm:3.0.0": "bytes@npm:3.0.0":
version: 3.0.0 version: 3.0.0
resolution: "bytes@npm:3.0.0" resolution: "bytes@npm:3.0.0"
@@ -9636,7 +9657,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"firebase@npm:^9.19.1": "firebase@npm:^9.18.0, firebase@npm:^9.19.1":
version: 9.19.1 version: 9.19.1
resolution: "firebase@npm:9.19.1" resolution: "firebase@npm:9.19.1"
dependencies: dependencies:
@@ -15960,6 +15981,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"streamsearch@npm:^1.1.0":
version: 1.1.0
resolution: "streamsearch@npm:1.1.0"
checksum: 1cce16cea8405d7a233d32ca5e00a00169cc0e19fbc02aa839959985f267335d435c07f96e5e0edd0eadc6d39c98d5435fb5bbbdefc62c41834eadc5622ad942
languageName: node
linkType: hard
"string-argv@npm:~0.3.1": "string-argv@npm:~0.3.1":
version: 0.3.1 version: 0.3.1
resolution: "string-argv@npm:0.3.1" resolution: "string-argv@npm:0.3.1"
@@ -16668,6 +16696,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"undici@npm:^5.21.2":
version: 5.21.2
resolution: "undici@npm:5.21.2"
dependencies:
busboy: ^1.6.0
checksum: baceaa9e610966631e86ad2869b657556dd465438eed55e8079cec2a306ecbeecfde2d6e37e43baf96a4c59588ebef50476131e96e018dcc0a7f5db7e6a06c85
languageName: node
linkType: hard
"unfetch@npm:^4.2.0": "unfetch@npm:^4.2.0":
version: 4.2.0 version: 4.2.0
resolution: "unfetch@npm:4.2.0" resolution: "unfetch@npm:4.2.0"

View File

@@ -31,7 +31,6 @@ export const StyledHeader = styled('div')<{ hasWarning: boolean }>(
padding: '0 20px', padding: '0 20px',
...displayFlex('space-between', 'center'), ...displayFlex('space-between', 'center'),
background: theme.colors.pageBackground, background: theme.colors.pageBackground,
transition: 'background-color 0.5s',
zIndex: 99, zIndex: 99,
position: 'relative', position: 'relative',
}; };

View File

@@ -12,8 +12,8 @@ import { jotaiStore } from '@affine/workspace/atom';
import { isValidIPAddress } from '../utils'; import { isValidIPAddress } from '../utils';
let prefixUrl = '/'; let prefixUrl = '/';
if (typeof window === 'undefined') { if (typeof window === 'undefined' || environment.isDesktop) {
// SSR // SSR or Desktop
const serverAPI = config.serverAPI; const serverAPI = config.serverAPI;
if (isValidIPAddress(serverAPI.split(':')[0])) { if (isValidIPAddress(serverAPI.split(':')[0])) {
// This is for Server side rendering support // This is for Server side rendering support
@@ -21,6 +21,7 @@ if (typeof window === 'undefined') {
} else { } else {
prefixUrl = serverAPI; prefixUrl = serverAPI;
} }
prefixUrl = prefixUrl.endsWith('/') ? prefixUrl : prefixUrl + '/';
} else { } else {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
params.get('prefixUrl') && (prefixUrl = params.get('prefixUrl') as string); params.get('prefixUrl') && (prefixUrl = params.get('prefixUrl') as string);

View File

@@ -1,4 +1,5 @@
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import { getEnvironment } from '@affine/env';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import { Slot } from '@blocksuite/store'; import { Slot } from '@blocksuite/store';
import { initializeApp } from 'firebase/app'; import { initializeApp } from 'firebase/app';
@@ -9,6 +10,7 @@ import {
getAuth as getFirebaseAuth, getAuth as getFirebaseAuth,
GithubAuthProvider, GithubAuthProvider,
GoogleAuthProvider, GoogleAuthProvider,
signInWithCredential,
signInWithPopup, signInWithPopup,
} from 'firebase/auth'; } from 'firebase/auth';
import { decode } from 'js-base64'; import { decode } from 'js-base64';
@@ -64,6 +66,13 @@ export const setLoginStorage = (login: LoginResponse) => {
); );
}; };
const signInWithElectron = async (firebaseAuth: FirebaseAuth) => {
const code = await window.apis?.googleSignIn();
const credential = GoogleAuthProvider.credential(code);
const user = await signInWithCredential(firebaseAuth, credential);
return await user.user.getIdToken();
};
export const clearLoginStorage = () => { export const clearLoginStorage = () => {
localStorage.removeItem(STORAGE_KEY); localStorage.removeItem(STORAGE_KEY);
}; };
@@ -152,6 +161,7 @@ export function createAffineAuth(prefix = '/') {
method: SignMethod method: SignMethod
): Promise<LoginResponse | null> => { ): Promise<LoginResponse | null> => {
const auth = getAuth(); const auth = getAuth();
const environment = getEnvironment();
if (!auth) { if (!auth) {
throw new Error('Failed to initialize firebase'); throw new Error('Failed to initialize firebase');
} }
@@ -167,9 +177,14 @@ export function createAffineAuth(prefix = '/') {
throw new Error('Unsupported sign method'); throw new Error('Unsupported sign method');
} }
try { try {
const response = await signInWithPopup(auth, provider); let idToken: string | undefined;
const idToken = await response.user.getIdToken(); if (environment.isDesktop) {
logger.debug(idToken); idToken = await signInWithElectron(auth);
} else {
const response = await signInWithPopup(auth, provider);
idToken = await response.user.getIdToken();
}
logger.debug('idToken', idToken);
return fetch(prefix + 'api/user/token', { return fetch(prefix + 'api/user/token', {
method: 'POST', method: 'POST',
headers: { headers: {

View File

@@ -1,3 +1,5 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path='../../../apps/electron/layers/preload/preload.d.ts' />
import type { Workspace as RemoteWorkspace } from '@affine/workspace/affine/api'; import type { Workspace as RemoteWorkspace } from '@affine/workspace/affine/api';
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store'; import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import type { FC, PropsWithChildren } from 'react'; import type { FC, PropsWithChildren } from 'react';