Skip to content

Commit

Permalink
support deeplink
Browse files Browse the repository at this point in the history
  • Loading branch information
pompurin404 committed Aug 4, 2024
1 parent 60fb73b commit c2e1d67
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 13 deletions.
7 changes: 7 additions & 0 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ asarUnpack:
extraResources:
- from: './resources/'
to: ''
protocols:
name: 'Mihomo Party URI Scheme'
schemes:
- 'clash'
- 'mihomo'
win:
target:
- nsis
Expand Down Expand Up @@ -41,6 +46,8 @@ mac:
dmg:
artifactName: ${name}-macos-${version}-${arch}-installer.${ext}
linux:
desktop:
MimeType: 'x-scheme-handler/clash;x-scheme-handler/mihomo'
target:
- deb
- rpm
Expand Down
10 changes: 10 additions & 0 deletions src/main/config/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { window } from '..'
import axios from 'axios'
import yaml from 'yaml'
import fs from 'fs'
import { dialog } from 'electron'

let profileConfig: IProfileConfig // profile.yaml
let currentProfile: Partial<IMihomoConfig> // profiles/xxx.yaml
Expand Down Expand Up @@ -95,6 +96,10 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
switch (newItem.type) {
case 'remote': {
if (!item.url) {
dialog.showErrorBox(
'URL is required for remote profile',
'URL is required for remote profile'
)
throw new Error('URL is required for remote profile')
}
try {
Expand Down Expand Up @@ -126,12 +131,17 @@ export async function createProfile(item: Partial<IProfileItem>): Promise<IProfi
}
fs.writeFileSync(profilePath(id), data, 'utf-8')
} catch (e) {
dialog.showErrorBox('Failed to fetch remote profile', `${e}\nurl: ${item.url}`)
throw new Error(`Failed to fetch remote profile ${e}`)
}
break
}
case 'local': {
if (!item.file) {
dialog.showErrorBox(
'File is required for local profile',
'File is required for local profile'
)
throw new Error('File is required for local profile')
}
const data = item.file
Expand Down
9 changes: 7 additions & 2 deletions src/main/core/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '../utils/dirs'
import { generateProfile } from '../resolve/factory'
import { getAppConfig, setAppConfig } from '../config'
import { safeStorage } from 'electron'
import { dialog, safeStorage } from 'electron'
import fs from 'fs'

let child: ChildProcess
Expand Down Expand Up @@ -51,7 +51,12 @@ export function restartCore(): void {

export function checkProfile(): void {
const corePath = mihomoCorePath(getAppConfig().core ?? 'mihomo')
execFileSync(corePath, ['-t', '-f', mihomoWorkConfigPath(), '-d', mihomoTestDir()])
try {
execFileSync(corePath, ['-t', '-f', mihomoWorkConfigPath(), '-d', mihomoTestDir()])
} catch (e) {
dialog.showErrorBox('Profile check failed', `${e}`)
throw new Error('Profile check failed')
}
}

export function grantCorePermition(corePath: string): void {
Expand Down
35 changes: 32 additions & 3 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { triggerSysProxy } from './resolve/sysproxy'
import icon from '../../resources/icon.png?asset'
import { createTray } from './core/tray'
import { init } from './resolve/init'
import { getAppConfig } from './config'
import { addProfileItem, getAppConfig } from './config'
import { join } from 'path'
import {
startMihomoMemory,
Expand All @@ -23,11 +23,19 @@ if (!gotTheLock) {
app.quit()
} else {
init()
app.on('second-instance', () => {
app.on('second-instance', (_event, commandline) => {
window?.show()
window?.focusOnWebView()
const url = commandline.pop()
if (url) {
handleDeepLink(url)
}
})
app.on('open-url', (_event, url) => {
window?.show()
window?.focusOnWebView()
handleDeepLink(url)
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
Expand Down Expand Up @@ -67,6 +75,27 @@ if (!gotTheLock) {
})
}

function handleDeepLink(url: string): void {
if (url.startsWith('clash://install-config')) {
url = url.replace('clash://install-config/?url=', '').replace('clash://install-config?url=', '')
addProfileItem({
type: 'remote',
name: 'Remote File',
url
})
}
if (url.startsWith('mihomo://install-config')) {
url = url
.replace('mihomo://install-config/?url=', '')
.replace('mihomo://install-config?url=', '')
addProfileItem({
type: 'remote',
name: 'Remote File',
url
})
}
}

function createWindow(): void {
// Create the browser window.
window = new BrowserWindow({
Expand Down
12 changes: 12 additions & 0 deletions src/main/resolve/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import path from 'path'
import { startPacServer } from './server'
import { triggerSysProxy } from './sysproxy'
import { getAppConfig } from '../config'
import { app } from 'electron'

function initDirs(): void {
if (!fs.existsSync(dataDir)) {
Expand Down Expand Up @@ -71,10 +72,21 @@ function initFiles(): void {
}
}

function initDeeplink(): void {
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('clash', process.execPath, [path.resolve(process.argv[1])])
}
} else {
app.setAsDefaultProtocolClient('clash')
}
}

export function init(): void {
initDirs()
initConfig()
initFiles()
initDeeplink()
startPacServer().then(() => {
triggerSysProxy(getAppConfig().sysProxy.enable)
})
Expand Down
1 change: 1 addition & 0 deletions src/main/utils/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('triggerSysProxy', (_e, enable) => triggerSysProxy(enable))
ipcMain.handle('isEncryptionAvailable', isEncryptionAvailable)
ipcMain.handle('encryptString', (_e, str) => safeStorage.encryptString(str))
ipcMain.handle('platform', () => process.platform)
ipcMain.handle('quitApp', () => app.quit())
}
24 changes: 16 additions & 8 deletions src/renderer/src/components/sider/tun-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-c
import BorderSwitch from '@renderer/components/base/border-swtich'
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
import { useLocation, useNavigate } from 'react-router-dom'
import { encryptString, patchMihomoConfig, isEncryptionAvailable } from '@renderer/utils/ipc'
import {
platform,
encryptString,
patchMihomoConfig,
isEncryptionAvailable
} from '@renderer/utils/ipc'
import React, { useState } from 'react'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import BasePasswordModal from '../base/base-password-modal'
Expand All @@ -19,14 +24,17 @@ const TunSwitcher: React.FC = () => {
const { enable } = tun || {}

const onChange = async (enable: boolean): Promise<void> => {
const encryptionAvailable = await isEncryptionAvailable()
if (!appConfig?.encryptedPassword && encryptionAvailable) {
setOpenPasswordModal(true)
return
}
if (!encryptionAvailable) {
alert('加密不可用,请手动给内核授权')
if (enable && (await platform()) !== 'win32') {
const encryptionAvailable = await isEncryptionAvailable()
if (!appConfig?.encryptedPassword && encryptionAvailable) {
setOpenPasswordModal(true)
return
}
if (!encryptionAvailable) {
alert('加密不可用,请手动给内核授权')
}
}

await patchControledMihomoConfig({ tun: { enable } })
await patchMihomoConfig({ tun: { enable } })
}
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/src/utils/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ export async function isEncryptionAvailable(): Promise<boolean> {
export async function encryptString(str: string): Promise<Buffer> {
return await window.electron.ipcRenderer.invoke('encryptString', str)
}

export async function platform(): Promise<NodeJS.Platform> {
return await window.electron.ipcRenderer.invoke('platform')
}

export async function quitApp(): Promise<void> {
return await window.electron.ipcRenderer.invoke('quitApp')
}

0 comments on commit c2e1d67

Please sign in to comment.