Skip to main content

딥 링크 설정 가이드

개요​

이 가이드에서는 여러분의 Electron 앱을 특정 프로토콜의 기본 핸들러로 설정하는 과정을 안내한다.

이 튜토리얼을 마치면, 앱이 특정 프로토콜로 시작하는 URL 클릭을 가로채고 처리할 수 있게 된다. 여기서는 "electron-fiddle://" 프로토콜을 예제로 사용한다.

예제​

메인 프로세스 (main.js)​

먼저, electron에서 필요한 모듈을 가져온다. 이 모듈들은 애플리케이션의 생명주기를 제어하고 네이티브 브라우저 윈도우를 생성하는 데 도움을 준다.

const { app, BrowserWindow, shell } = require('electron')
const path = require('node:path')

다음으로, 애플리케이션이 모든 "electron-fiddle://" 프로토콜을 처리하도록 등록한다.

if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('electron-fiddle', process.execPath, [path.resolve(process.argv[1])])
}
} else {
app.setAsDefaultProtocolClient('electron-fiddle')
}

이제 브라우저 윈도우를 생성하고 애플리케이션의 index.html 파일을 로드하는 함수를 정의한다.

let mainWindow

const createWindow = () => {
// 브라우저 윈도우를 생성한다.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

mainWindow.loadFile('index.html')
}

다음 단계에서는 BrowserWindow를 생성하고, 외부 프로토콜이 클릭되었을 때 애플리케이션이 어떻게 이벤트를 처리할지 정의한다.

이 코드는 Windows와 Linux에서 MacOS와 다르게 동작한다. 이는 두 플랫폼이 open-url 이벤트 대신 second-instance 이벤트를 발생시키기 때문이며, Windows는 동일한 Electron 인스턴스 내에서 프로토콜 링크의 내용을 열기 위해 추가 코드가 필요하다. 이에 대한 자세한 내용은 여기에서 확인할 수 있다.

const gotTheLock = app.requestSingleInstanceLock()

if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 두 번째 인스턴스가 실행되면 현재 윈도우에 포커스를 맞춘다.
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}
// commandLine은 문자열 배열이며, 마지막 요소는 딥 링크 URL이다.
dialog.showErrorBox('Welcome Back', `You arrived from: ${commandLine.pop()}`)
})

// mainWindow를 생성하고 나머지 앱을 로드한다.
app.whenReady().then(() => {
createWindow()
})
}

MacOS 코드:​

// 이 메서드는 Electron이 초기화를 마치고 브라우저 윈도우를 생성할 준비가 되었을 때 호출된다.
// 일부 API는 이 이벤트가 발생한 후에만 사용할 수 있다.
app.whenReady().then(() => {
createWindow()
})

// 프로토콜을 처리한다. 이 경우, 에러 박스를 표시하기로 선택했다.
app.on('open-url', (event, url) => {
dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`)
})

마지막으로, 누군가가 애플리케이션을 닫을 때를 처리하기 위해 추가 코드를 작성한다.

// 모든 윈도우가 닫히면 애플리케이션을 종료한다. 단, macOS에서는 예외다.
// macOS에서는 애플리케이션과 메뉴 바가 사용자가 Cmd + Q로 명시적으로 종료할 때까지 활성 상태를 유지하는 것이 일반적이다.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})

주요 참고 사항​

패키징​

macOS와 Linux에서 이 기능은 앱이 패키징된 상태에서만 동작한다. 개발 환경에서 커맨드라인을 통해 앱을 실행할 때는 작동하지 않는다. 앱을 패키징할 때는 macOS의 Info.plist와 Linux의 .desktop 파일이 새로운 프로토콜 핸들러를 포함하도록 업데이트해야 한다. 일부 Electron 앱 번들링 및 배포 도구는 이 과정을 자동으로 처리해 준다.

Electron Forge​

Electron Forge를 사용한다면, macOS 지원을 위해 packagerConfig를 조정하고, Linux 지원을 위해 적절한 Linux maker 설정을 Forge configuration에 추가한다. (아래 예제는 설정 변경을 위해 필요한 최소한의 내용만 보여준다):

{
"config": {
"forge": {
"packagerConfig": {
"protocols": [
{
"name": "Electron Fiddle",
"schemes": ["electron-fiddle"]
}
]
},
"makers": [
{
"name": "@electron-forge/maker-deb",
"config": {
"mimeType": ["x-scheme-handler/electron-fiddle"]
}
}
]
}
}
}

Electron Packager​

macOS 지원을 위해:

Electron Packager의 API를 사용한다면, 프로토콜 핸들러를 추가하는 방법은 Electron Forge와 유사하다. 단, protocols는 packager 함수에 전달되는 Packager 옵션의 일부라는 점이 다르다.

const packager = require('@electron/packager')

packager({
// ...다른 옵션들...
protocols: [
{
name: 'Electron Fiddle',
schemes: ['electron-fiddle']
}
]

}).then(paths => console.log(`SUCCESS: Created ${paths.join(', ')}`))
.catch(err => console.error(`ERROR: ${err.message}`))

Electron Packager의 CLI를 사용한다면, --protocol과 --protocol-name 플래그를 사용한다. 예를 들어:

npx electron-packager . --protocol=electron-fiddle --protocol-name="Electron Fiddle"

결론​

Electron 앱을 실행한 후, 브라우저에서 커스텀 프로토콜이 포함된 URL(예: "electron-fiddle://open")을 입력하면 앱이 반응하고 오류 대화 상자를 표시하는 것을 확인할 수 있다.

// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain, shell, dialog } = require('electron/main')
const path = require('node:path')

let mainWindow

if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('electron-fiddle', process.execPath, [path.resolve(process.argv[1])])
}
} else {
app.setAsDefaultProtocolClient('electron-fiddle')
}

const gotTheLock = app.requestSingleInstanceLock()

if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}

dialog.showErrorBox('Welcome Back', `You arrived from: ${commandLine.pop().slice(0, -1)}`)
})

// Create mainWindow, load the rest of the app, etc...
app.whenReady().then(() => {
createWindow()
})

app.on('open-url', (event, url) => {
dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`)
})
}

function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

mainWindow.loadFile('index.html')
}

// 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.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})

// Handle window controls via IPC
ipcMain.on('shell:open', () => {
const pageDirectory = __dirname.replace('app.asar', 'app.asar.unpacked')
const pagePath = path.join('file://', pageDirectory, 'index.html')
shell.openExternal(pagePath)
})