mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
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:
3
.github/workflows/release-desktop-app.yml
vendored
3
.github/workflows/release-desktop-app.yml
vendored
@@ -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:
|
||||||
|
|||||||
1
apps/electron/.gitignore
vendored
1
apps/electron/.gitignore
vendored
@@ -11,3 +11,4 @@ resources/web-static
|
|||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
dev.json
|
||||||
|
|||||||
32
apps/electron/layers/main/src/app-state/google-auth.ts
Normal file
32
apps/electron/layers/main/src/app-state/google-auth.ts
Normal 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();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
2
apps/electron/layers/preload/preload.d.ts
vendored
2
apps/electron/layers/preload/preload.d.ts
vendored
@@ -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; };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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', {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,8 +19,19 @@ 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'];
|
||||||
|
|
||||||
|
/** @return {{main: import('esbuild').BuildOptions, preload: import('esbuild').BuildOptions}} */
|
||||||
|
export default () => {
|
||||||
|
const define = Object.fromEntries(
|
||||||
|
ENV_MACROS.map(key => [
|
||||||
|
'process.env.' + key,
|
||||||
|
JSON.stringify(process.env[key] ?? ''),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
main: {
|
||||||
entryPoints: ['layers/main/src/index.ts'],
|
entryPoints: ['layers/main/src/index.ts'],
|
||||||
outdir: 'dist/layers/main',
|
outdir: 'dist/layers/main',
|
||||||
bundle: true,
|
bundle: true,
|
||||||
@@ -29,13 +39,16 @@ export const mainConfig = {
|
|||||||
platform: 'node',
|
platform: 'node',
|
||||||
external: ['electron'],
|
external: ['electron'],
|
||||||
plugins: [nativeNodeModulesPlugin],
|
plugins: [nativeNodeModulesPlugin],
|
||||||
};
|
define: define,
|
||||||
|
},
|
||||||
export const preloadConfig = {
|
preload: {
|
||||||
entryPoints: ['layers/preload/src/index.ts'],
|
entryPoints: ['layers/preload/src/index.ts'],
|
||||||
outdir: 'dist/layers/preload',
|
outdir: 'dist/layers/preload',
|
||||||
bundle: true,
|
bundle: true,
|
||||||
target: `node${node}`,
|
target: `node${node}`,
|
||||||
platform: 'node',
|
platform: 'node',
|
||||||
external: ['electron'],
|
external: ['electron'],
|
||||||
|
define: define,
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
let idToken: string | undefined;
|
||||||
|
if (environment.isDesktop) {
|
||||||
|
idToken = await signInWithElectron(auth);
|
||||||
|
} else {
|
||||||
const response = await signInWithPopup(auth, provider);
|
const response = await signInWithPopup(auth, provider);
|
||||||
const idToken = await response.user.getIdToken();
|
idToken = await response.user.getIdToken();
|
||||||
logger.debug(idToken);
|
}
|
||||||
|
logger.debug('idToken', idToken);
|
||||||
return fetch(prefix + 'api/user/token', {
|
return fetch(prefix + 'api/user/token', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user