add google login flow
This commit is contained in:
+137
-29
@@ -3,7 +3,7 @@ import { join, dirname, resolve, relative, isAbsolute } from 'path'
|
||||
import { chmodSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'fs'
|
||||
import { get as httpGet } from 'http'
|
||||
import { get as httpsGet } from 'https'
|
||||
import { spawn, exec, ChildProcess } from 'child_process'
|
||||
import { spawn, exec, execFile, ChildProcess } from 'child_process'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
@@ -11,6 +11,7 @@ protocol.registerSchemesAsPrivileged([
|
||||
])
|
||||
|
||||
let splash: BrowserWindow | null = null
|
||||
let pendingGoogleOAuthPayload: { loginToken?: string; error?: string } | null = null
|
||||
|
||||
type CommandResult = {
|
||||
ok: boolean
|
||||
@@ -57,6 +58,77 @@ function getLauncherDir(): string {
|
||||
return dirname(process.execPath)
|
||||
}
|
||||
|
||||
function dispatchGoogleOAuthPayload(payload: { loginToken?: string; error?: string }): void {
|
||||
const windows = BrowserWindow.getAllWindows().filter((win) => !win.isDestroyed())
|
||||
if (windows.length === 0) {
|
||||
pendingGoogleOAuthPayload = payload
|
||||
return
|
||||
}
|
||||
|
||||
pendingGoogleOAuthPayload = null
|
||||
for (const win of windows) {
|
||||
win.webContents.send('google-oauth-callback', payload)
|
||||
}
|
||||
}
|
||||
|
||||
function handleProtocolUrl(rawUrl: string): void {
|
||||
let url: URL
|
||||
try {
|
||||
url = new URL(rawUrl)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
if (url.protocol !== 'autonomy-studio:' || url.hostname !== 'auth' || url.pathname !== '/google/callback') {
|
||||
return
|
||||
}
|
||||
|
||||
dispatchGoogleOAuthPayload({
|
||||
loginToken: url.searchParams.get('loginToken') ?? undefined,
|
||||
error: url.searchParams.get('error') ?? undefined,
|
||||
})
|
||||
}
|
||||
|
||||
function escapeDesktopExecValue(value: string): string {
|
||||
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
||||
}
|
||||
|
||||
function registerLinuxProtocolHandler(): void {
|
||||
if (process.platform !== 'linux') return
|
||||
|
||||
try {
|
||||
const desktopFileName = is.dev
|
||||
? 'autonomy-studio-launcher-dev.desktop'
|
||||
: 'autonomy-studio-launcher.desktop'
|
||||
const applicationsDir = join(app.getPath('home'), '.local', 'share', 'applications')
|
||||
const desktopFilePath = join(applicationsDir, desktopFileName)
|
||||
const iconPath = is.dev
|
||||
? join(process.cwd(), 'resources/icon.png')
|
||||
: join(dirname(process.execPath), 'resources/icon.png')
|
||||
const execArgs = process.defaultApp && process.argv[1]
|
||||
? `"${escapeDesktopExecValue(process.execPath)}" "${escapeDesktopExecValue(resolve(process.argv[1]))}" %u`
|
||||
: `"${escapeDesktopExecValue(process.execPath)}" %u`
|
||||
|
||||
mkdirSync(applicationsDir, { recursive: true })
|
||||
writeFileSync(desktopFilePath, [
|
||||
'[Desktop Entry]',
|
||||
'Type=Application',
|
||||
'Name=Autonomy Studio Launcher',
|
||||
`Exec=${execArgs}`,
|
||||
`Icon=${iconPath}`,
|
||||
'Terminal=false',
|
||||
'NoDisplay=true',
|
||||
'MimeType=x-scheme-handler/autonomy-studio;',
|
||||
''
|
||||
].join('\n'), 'utf8')
|
||||
|
||||
execFile('xdg-mime', ['default', desktopFileName, 'x-scheme-handler/autonomy-studio'], () => undefined)
|
||||
execFile('update-desktop-database', [applicationsDir], () => undefined)
|
||||
} catch (error) {
|
||||
console.warn('Failed to register autonomy-studio protocol handler', error)
|
||||
}
|
||||
}
|
||||
|
||||
function getUserAppsDir(): string {
|
||||
return join(app.getPath('home'), '.local', 'share', 'autonomy-studio', 'apps')
|
||||
}
|
||||
@@ -711,6 +783,9 @@ function createWindow(): void {
|
||||
splash?.close()
|
||||
splash = null
|
||||
mainWindow.show()
|
||||
if (pendingGoogleOAuthPayload) {
|
||||
dispatchGoogleOAuthPayload(pendingGoogleOAuthPayload)
|
||||
}
|
||||
}, 1500)
|
||||
})
|
||||
|
||||
@@ -853,6 +928,7 @@ function createWindow(): void {
|
||||
|
||||
ipcMain.handle('get-launcher-cache-status', () => getLauncherCacheStatus())
|
||||
ipcMain.handle('clear-launcher-cache', () => clearLauncherCache())
|
||||
ipcMain.handle('start-google-login', (_, url: string) => shell.openExternal(url))
|
||||
ipcMain.handle('launcher-tailscale-status', () => readTailscaleStatus())
|
||||
ipcMain.handle('launcher-ssh-key-ensure', () => ensureLauncherSshKey())
|
||||
ipcMain.handle(
|
||||
@@ -1055,32 +1131,64 @@ function createWindow(): void {
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
electronApp.setAppUserModelId('com.electron')
|
||||
|
||||
protocol.handle('appdocs', (request) => {
|
||||
const url = new URL(request.url)
|
||||
const docsBase = is.dev
|
||||
? join(process.cwd(), 'resources/docs')
|
||||
: join(__dirname, '../renderer/docs')
|
||||
const filePath = join(docsBase, url.pathname)
|
||||
return net.fetch(`file://${filePath}`)
|
||||
})
|
||||
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
createSplashWindow()
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
const gotSingleInstanceLock = app.requestSingleInstanceLock()
|
||||
if (!gotSingleInstanceLock) {
|
||||
app.exit(0)
|
||||
} else {
|
||||
if (process.defaultApp && process.argv.length >= 2) {
|
||||
app.setAsDefaultProtocolClient('autonomy-studio', process.execPath, [resolve(process.argv[1])])
|
||||
} else {
|
||||
app.setAsDefaultProtocolClient('autonomy-studio')
|
||||
}
|
||||
})
|
||||
|
||||
app.on('second-instance', (_event, argv) => {
|
||||
const protocolUrl = argv.find((arg) => arg.startsWith('autonomy-studio://'))
|
||||
if (protocolUrl) handleProtocolUrl(protocolUrl)
|
||||
|
||||
const window = BrowserWindow.getAllWindows()[0]
|
||||
if (window) {
|
||||
if (window.isMinimized()) window.restore()
|
||||
window.focus()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('open-url', (event, url) => {
|
||||
event.preventDefault()
|
||||
handleProtocolUrl(url)
|
||||
})
|
||||
|
||||
for (const arg of process.argv) {
|
||||
if (arg.startsWith('autonomy-studio://')) handleProtocolUrl(arg)
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
electronApp.setAppUserModelId('com.autonomystudio.launcher')
|
||||
registerLinuxProtocolHandler()
|
||||
|
||||
protocol.handle('appdocs', (request) => {
|
||||
const url = new URL(request.url)
|
||||
const docsBase = is.dev
|
||||
? join(process.cwd(), 'resources/docs')
|
||||
: join(__dirname, '../renderer/docs')
|
||||
const filePath = join(docsBase, url.pathname)
|
||||
return net.fetch(`file://${filePath}`)
|
||||
})
|
||||
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
createSplashWindow()
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user