ディープリンク
概要
This guide will take you through the process of setting your Electron app as the default handler for a specific protocol.
このチュートリアルを終える頃には、特定のプロトコルで始まる 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')
}
In this next step, we will create our BrowserWindow and tell our application how to handle an event in which an external protocol is clicked.
This code will be different in Windows and Linux compared to macOS. これは、両プラットフォームが open-url イベントではなく second-instance イベントを発生させるからです。Windows が同じ Electron インスタンス内でプロトコルリンクの内容を開くためには、追加のコードが必要になります。 Read more about this here.
Windows と Linux のコード:
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// 誰かが 2 つ目のインスタンスを実行しようとしたので、ウインドウにフォーカスさせなければなりません。
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}
// commandLine は文字列の配列で、最後の要素がディープリンクの URL です。
dialog.showErrorBox('Welcome Back', `You arrived from: ${commandLine.pop()}`)
})
// mainWindow を作成する、アプリの残りも読み込む、etc...
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 以外では、すべてのウインドウを閉じたときに終了します。 // ユーザが 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 を使用している場合は、Forge の構成 で macOS サポート向けに packagerConfig を調整し、Linux サポート向けに適切な Linux メーカーの構成を調整します (次の例は、構成変更を追加するために必要な最小限のもののみを示していることに注意してください)。
{
"config": {
"forge": {
"packagerConfig": {
"protocols": [
{
"name": "Electron Fiddle",
"schemes": ["electron-fiddle"]
}
]
},
"makers": [
{
"name": "@electron-forge/maker-deb",
"config": {
"mimeType": ["x-scheme-handler/electron-fiddle"]
}
}
]
}
}
}
Electron パッケージャ
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" を入力すると、アプリが応答してエラーダイアログボックスを表示することを確認できます。
- main.js
- preload.js
- index.html
- renderer.js
// 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)
})
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('shell', {
open: () => ipcRenderer.send('shell:open')
})
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>app.setAsDefaultProtocol Demo</title>
</head>
<body>
<h1>App Default Protocol Demo</h1>
<p>The protocol API allows us to register a custom protocol and intercept existing protocol requests.</p>
<p>These methods allow you to set and unset the protocols your app should be the default app for. Similar to when a
browser asks to be your default for viewing web pages.</p>
<p>Open the <a href="https://www.electronjs.org/docs/latest/api/protocol">full protocol API documentation</a> in your
browser.</p>
-----
<h3>Demo</h3>
<p>
First: Launch current page in browser
<button id="open-in-browser" class="js-container-target demo-toggle-button">
Click to Launch Browser
</button>
</p>
<p>
Then: Launch the app from a web link!
<a href="electron-fiddle://open">Click here to launch the app</a>
</p>
----
<p>You can set your app as the default app to open for a specific protocol. For instance, in this demo we set this app
as the default for <code>electron-fiddle://</code>. The demo button above will launch a page in your default
browser with a link. Click that link and it will re-launch this app.</p>
<h3>Packaging</h3>
<p>This feature will only work on macOS when your app is packaged. It will not work when you're launching it in
development from the command-line. When you package your app you'll need to make sure the macOS <code>plist</code>
for the app is updated to include the new protocol handler. If you're using <code>@electron/packager</code> then you
can add the flag <code>--extend-info</code> with a path to the <code>plist</code> you've created. The one for this
app is below:</p>
<p>
<h5>macOS plist</h5>
<pre><code>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>electron-api-demos</string>
</array>
<key>CFBundleURLName</key>
<string>Electron API Demos Protocol</string>
</dict>
</array>
<key>ElectronTeamID</key>
<string>VEKTX9H2N7</string>
</dict>
</plist>
</code>
</pre>
<p>
<!-- You can also require other files to run in this process -->
<script src="./renderer.js"></script>
</body>
</html>
// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All APIs exposed by the context bridge are available here.
// Binds the buttons to the context bridge API.
document.getElementById('open-in-browser').addEventListener('click', () => {
window.shell.open()
})