ディープリンクとファイル関連付け
URL スキームとドキュメントタイプを登録し、すべてのオープンリクエストを Node Main で処理します。
Murasaki は、パッケージ化された macOS / Windows / Linux アプリに対して、カスタム URL スキームとドキュメントの拡張子を登録できます。コールドスタートと、起動済みのアプリへのリクエストは、どちらも同じ Node Main の openRequested() フックに届きます。
ハンドラーを設定する
アプリが扱うスキームと拡張子を murasaki.config.ts に宣言します。
import { defineConfig } from 'murasaki'
export default defineConfig({
appId: 'com.example.violet',
productName: 'Violet',
protocols: [
{ scheme: 'violet', name: 'Violet Link' },
],
fileAssociations: [
{
extensions: ['vnote'],
name: 'Violet Note',
description: 'Violet で作成したノート',
role: 'editor',
mimeType: 'application/x-violet-note',
},
],
})拡張子は先頭のドットなしで指定します。スキームと拡張子は小文字に正規化されます。重複した値、予約済みのブラウザ / OS スキーム(blob、file、http、https、javascript、mailto、ms-settings、tel、murasaki など)、不正なスキーム / 拡張子 / MIME タイプは、ビルド時に拒否されます。すべてのフィールドとデフォルト値は設定リファレンスを参照してください。
オープンリクエストを処理する
Node Main のエントリを作成し、openRequested(context, event) を実装します。プラットフォームやトランスポートではなく、各ターゲットの kind で分岐してください。
import { defineMain } from 'murasaki/main'
export default defineMain({
async ready({ paths }) {
// 最初に DB や service を初期化する。
console.log('data directory:', paths.data)
},
async openRequested(_context, event) {
for (const target of event.targets) {
if (target.kind === 'url') {
const url = new URL(target.url)
if (url.hostname !== 'open') continue
// 実行前に route を検証し、認可する。
console.log('open link:', url.pathname)
} else {
// file を読む前に所有範囲、size、内容を検証する。
console.log('open document:', target.path)
}
}
},
})Murasaki は ready() の完了後に openRequested() を呼びます。DB などの長寿命リソースを一度初期化しておけば、コールドスタート時と実行中のアプリへのリクエストを、同じハンドラーで処理できます。
イベントリファレンス
type OpenTarget =
| { kind: 'url'; url: string; scheme: string }
| { kind: 'file'; path: string }
interface OpenRequestEvent {
activation: 'cold-start' | 'second-instance' | 'os-event'
transport: 'argv' | 'open-url' | 'open-file'
targets: OpenTarget[]
cwd?: string
}| フィールド | 意味 |
|---|---|
activation | 最初の起動に伴うリクエスト、2つ目のプロセスから転送されたリクエスト、ネイティブの OS イベントのいずれか。 |
transport | ネイティブの配信メカニズム。診断用であり、通常のアプリケーションロジックは target.kind を使います。 |
targets | 設定済みのスキーム / 拡張子と一致した URL / ファイルのみ。1 つのイベントに複数のファイルが含まれる場合があります。 |
cwd | argv ベースの起動における作業ディレクトリ。ネイティブの OS イベントの場合は含まれません。 |
2 つ目の実行ファイルの起動では、secondInstance() に生の argv / cwd が届き、openRequested() には認識済みの URL / ファイルターゲットが届きます。同じオープン処理を両方のフックで実行しないでください。一般的なアクティベーションの挙動は secondInstance() に、リンク / ファイルの意味的な処理は openRequested() に置いてください。
パッケージングの動作
| アーティファクト | 登録と配信 |
|---|---|
macOS .app | murasaki bundle が URL / ドキュメントのメタデータをアプリの Info.plist に書き込みます。Launch Services は、起動済みのアプリを含めて URL / ファイルのオープンを配送します。.dmg はこの .app を収録しています。最終テスト前に Applications へ移動してください。 |
Windows NSIS .exe | インストーラーが installer.windows.installMode に従い、ユーザー単位 / マシン単位のプロトコル / ファイル関連付けを登録します。起動された URL / ファイルは argv から正規化されます。 |
| Windows MSI | MSI がマシン単位のプロトコル / ファイル関連付けを登録します。起動された URL / ファイルは argv から正規化されます。 |
| Windows ポータブルアーカイブ | レジストリエントリを作成しません。protocols / fileAssociations を設定しても、OS のハンドラーにはなりません。 |
Linux AppDir / .AppImage / .deb | murasaki bundle が x-scheme-handler/<scheme> と application/x-<extension> の MimeType= エントリを .desktop ファイルに書き込みます。.deb は usr/share/applications/ にインストールし、インストール / 削除時にデスクトップデータベースを更新します(update-desktop-database)。手動展開した AppDir/.AppImage には同等の OS レベルの登録手順はありませんが、直接起動した場合でも、コールドスタート時の argv(.desktop の Exec= 行が展開する %U/%F)と、2 回目起動時のアクティベーションはどちらも機能します。 |
murasaki dev | システムのハンドラーメタデータをインストールしません。OS との統合は、インストール済み / パッケージ化済みのアーティファクトでテストしてください。 |
Windows の登録は、アプリを利用可能なハンドラーに追加しますが、ユーザーの現在のデフォルトアプリを勝手に変更することはありません。リリース間で appId を維持し、インストーラーの所有権と関連付け識別子を安定させてください。
パッケージ済みハンドラーをテストする
インストーラーを実行するか、パッケージ化されたアプリを Applications へ移動したあと、コールドスタートと起動済みのアプリの両方をテストします。
open 'violet://open/note-42'
open -a Violet ./example.vnoteStart-Process 'violet://open/note-42'
Start-Process '.\example.vnote'クリーンな環境で、NSIS / MSI の実際のアンインストール / アップグレードの経路もテストしてください。実行ファイルへ引数を直接渡す方法は、ハンドラーのコードを確認するには使えますが、OS への登録が正しくインストールされたことの確認にはなりません。
セキュリティ
URL、ファイルパス、起動引数は、すべて信頼できない入力です。任意のローカルプロセスが、偽の URL / パスで実行ファイルを起動できます。カスタムスキームは、送信元を証明するものではありません。
- 期待する URL のホスト、パス、アクション、パラメーターの形を許可リストで検証する
- ベアラートークンや長期的な認証情報をディープリンクの URL に含めない。サインインのコールバックには、ワンタイムコード、
state、PKCE を使う - クエリ文字列、フラグメント、機密性の高いパスは、ログ / クラッシュレポートから除外する
- ファイルをパースする前に、サイズ、パーミッション、フォーマット、内容を確認する。期待した拡張子だけでは安全性を証明できない
- シンボリックリンクや、検証から使用までの間にファイルが変化する可能性を考慮する
- 処理は Node Main に置き、検証済みの最小限のデータだけをレンダラーへ送る