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와 유사하다. 단, protocolspackager 함수에 전달되는 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)
})