Murasaki
ガイド

ネイティブ API

現時点で、Murasaki のネイティブサーフェスからアプリに公開されているもの。

Murasaki のネイティブウィンドウ、メニュー、OS 統合は @murasakijs/native — 自社製の Rust バインディング (tao/wry/muda) によって支えられているため、Rust を書くことは一切ありません。このページでは、あなたの React アプリから実際に到達可能なものを説明します。

ウィンドウとネイティブメニューバー

ウィンドウの形状(サイズ、タイトル、ルート、表示、権限、macOS の vibrancy)は murasaki.config.ts で宣言します。window はプライマリの main ウィンドウ、windows はラベル付きのセカンダリウィンドウです。全体像はウィンドウと権限を参照してください。

macOS では、標準的な App/Edit/Window メニューバー (About、Services、Hide、Quit、Edit、Window) が自動的に生成され、config.locales からローカライズされます — コードは不要です。

コンテキストメニュー

右クリックのコンテキストメニューは、マークアップではなくフックである useContextMenu で宣言し、Rust 側に投げられて macOS / Windows では本物の NSMenu / HMENU がポップアップします。詳しくは専用のコンテキストメニューガイドを参照してください。

ダイアログ、クリップボード、通知、シェル、ウィンドウ操作

レンダラーから安全に使えるネイティブ機能は murasaki/native からインポートします。すべて Promise ベースで、リクエスト ID 付きの IPC メッセージを Rust ホストが直接処理します。

'use client'

import { app, appWindow, autostart, clipboard, dialog, globalShortcut, notification, secureStorage, shell, systemPermission, tray, windows } from 'murasaki/native'

const files = await dialog.openFile({
  multiple: true,
  filters: [{ name: 'Images', extensions: ['png', 'jpg'] }],
})
await clipboard.writeText(files.join('\n'))
await notification.show({ title: '選択完了', body: `${files.length} files` })
await appWindow.setTitle('Import complete')
console.log(await appWindow.getLabel())
await windows.open('preview')
await shell.showItemInFolder(files[0])
API操作
appquit(アプリケーションの正常終了)、isElevated(読み取り専用の自己照会)
autostartパッケージ化されたアプリ向けの、ユーザー単位のログイン起動 statusenabledisable
dialogopenFile, openDirectory, saveFile, showMessage(ネイティブな OS のメッセージボックス)
clipboardreadText, writeText, readImage, writeImage, writeHtml
notificationshow(ローカルで生成した ID を返す)
shellopenExternal(安全な URL スキームのみ)、showItemInFolder, trashItem, openPath, runElevated(Windows 限定の UAC 起動)
secureStorage文字列値向けの、OS に保存される get, set, delete
systemPermissionmacOS のカメラ / マイク / 画面収録 / アクセシビリティ / 入力監視 / 位置情報 / フルディスクアクセス / 写真 / 連絡先 / カレンダー / リマインダー / 音声認識 / Bluetooth / Apple Events(自動化) / ローカルネットワーク向けの status, request
appWindowgetLabel, setTitle, setSize, minimize, toggleMaximize, show, hide, focus, close, setAlwaysOnTop、状態取得
windows宣言されたラベル向けの open, list, show, hide, focus, close
globalShortcutプロセス全体の register, unregister, unregisterAll, onTriggered
traycreate, remove, setTooltip, setIcon, setMenu, onClick, onMenuItem

app.quit() とルートの quit() ヘルパーは、いずれも app:quit が必要です。権限のないセカンダリレンダラーから、アプリケーション全体を終了することはできません。

dialog.showMessage({ title?, message, level?, buttons? }) はネイティブなメインスレッドのメッセージボックスを表示します(level のデフォルト値は 'info'buttons のデフォルト値は 'ok' です)。押されたボタンに解決されます('ok' | 'cancel' | 'yes' | 'no')。title は 256 UTF-8 バイト、message は 4096 UTF-8 バイトまでで、どちらも制御文字は拒否されます(改行は message のみ許可されます)。dialog:message が必要です。

clipboard.readImage(){ width, height, pngBase64 } を返すか、クリップボードに画像がない場合は null を返します。clipboard.writeImage({ pngBase64 }) はデコードしてクリップボードの画像を置き換えます(デコード後のピクセルデータは 64 MiB、各辺は 16,384 px まで)。clipboard.writeHtml({ html, altText? }) は HTML と任意のプレーンテキストのフォールバックを書き込みます(html は 1 MiB、altText は 64 KiB まで)。それぞれ clipboard:readImageclipboard:writeImageclipboard:writeHtml が必要です。

shell.trashItem(path) は、既存のファイルまたはディレクトリを OS のごみ箱へ移動します。shell.openPath(path) は、既存のローカルファイルまたはディレクトリを OS のデフォルトハンドラーで開きます(ダブルクリックした場合と同様です)。どちらも絶対パスで、パストラバーサルを含まない、実在するパスが必要です。shell.showItemInFolder と同様、shell:trashItem / shell:openPath のケイパビリティは許可するパスのサブツリーにスコープできます。shell.openPath は、URL としてパースできるものや UNC のネットワーク/デバイスパスも拒否します。URL を開く場合は shell.openExternal を使ってください。スコープ付きの許可では、シンボリックリンクを解決したうえで、要求されたパスと実際のターゲットの両方が許可範囲内に残る場合のみ開きます。showItemInFoldertrashItem も、すべての親ディレクトリを実体解決して同じスコープ検査を再度行います。末尾自体がシンボリックリンクの場合は解決せず、リンク自体を選択・削除対象とします。

0.50 マイグレーション: 以前の quit() は権限チェックなしで生の IPC を送信していました。既存の、アップデーター機能を使わないアプリで quit() を呼んでいるウィンドウには app:quit を追加してください。組み込みのアップデーターを有効にした場合のみ、検証済みインストール/再起動ハンドシェイクの後方互換性を保つため、プライマリウィンドウへ自動的に付与されます。

これらは src/main.ts から直接呼び出す API ではなく、信頼済みのレンダラー向けです。ブリッジはアプリと完全に一致するオリジンだけを受け付け、固定された許可リストのコマンドだけを公開します。各レンダラーが実際に使うコマンドだけを window.capabilities / windows[label].capabilities で許可してください。構造化された許可では、シェルの URL / パス、権限昇格対象の正確な実行ファイルと引数の組、対象ウィンドウのラベル、OS 権限名、セキュアストレージのキー、WebView の cookie URL を allow / deny でスコープできます。それ以外の引数はコマンド単位のままなので、権限を持つウィンドウに信頼できないリモートコンテンツを読み込ませないでください。

権限昇格 (Windows)

import { app, shell } from 'murasaki/native'

if (!(await app.isElevated())) {
  try {
    await shell.runElevated({
      executable: 'C:/Program Files/Example/updater.exe',
      args: ['--apply'],
    })
  } catch (error) {
    if (error instanceof Error && error.message.includes('cancelled by the user')) {
      // ユーザーがUACプロンプトを拒否した場合はここでgracefulに処理する。
    } else {
      throw error
    }
  }
}
murasaki.config.ts
export default defineConfig({
  window: {
    capabilities: [{
      permission: 'shell:runElevated',
      allow: { executions: [{
        executable: 'C:/Program Files/Example/updater.exe',
        args: ['--apply'],
      }] },
    }],
  },
})

app.isElevated() は、ネイティブホストプロセスがすでに権限昇格された状態で実行されているかどうかを問い合わせる、読み取り専用でケイパビリティにより制御された(app:isElevated)クエリです。Windows ではプロセストークンの権限昇格状態を読み取り、macOS / Linux では「権限昇格」は実効的に root として実行されていることを意味します — GUI アプリでは稀かつ非推奨ですが、もっとも近い代替として対応しています。すべてのプラットフォームで動作し、ケイパビリティが必要で、失敗することはありません。基盤となるクエリがエラーになった場合も、単に false に解決されます。権限昇格の有無を判定できなかったクエリは、すでに非特権のレンダラーにとって新しい情報を何も伝えていないためです。

shell.runElevated({ executable, args? }) は、Windows の「管理者として実行」(UAC)の同意プロンプトを通じて、executable を新しいプロセスとして起動します。Windows 限定です — macOS の SMJobBless / AuthorizationExecuteWithPrivileges は非推奨で、Linux には単一の同等機構が存在しないため、それ以外のすべてのプラットフォームは unsupported エラーを返します。executable は絶対パスかつパストラバーサルを含まない、実在するファイルへのパスである必要があります。構造化された shell:runElevated の許可は、実行ファイルと順序を含む完全な引数リストを、1 つの正確な executions エントリとして照合します。任意の引数を伴うパスのサブツリーを許可するものではありません。シンボリックリンクを解決した実際の実行ファイルと引数の組も許可されている必要があり、スコープ内のリンクによって権限昇格先がすり替えられることを防ぎます。args(最大 64 件、各要素は最大 4096 UTF-8 バイト、制御文字は不可)はシェルを経由せず直接、権限昇格されたプロセスへ渡され、CommandLineToArgvW と同じルールでクオートされます。この呼び出しは fire-and-forget です — 権限昇格されたプロセスが起動した時点で解決し、終了を待ちません。

ユーザーが同意プロンプトを拒否した場合、shell.runElevated は、メッセージが正確に "elevation was cancelled by the user" であるエラーで拒否されます。これにより、プロンプトが拒否された場合を他の失敗と区別して処理できます。

shell:runElevated は強力なケイパビリティです — 付与すると、レンダラーが許可された実行ファイルを権限昇格して実行する UAC プロンプトをトリガーできるようになります。構造化された、正確な実行ファイル/引数の許可を推奨します。互換性のため、文字列形式は無制限です。権限昇格されたヘルパーが本当に必要なウィンドウにのみ付与してください。

ログイン時の自動起動

パッケージ化されたアプリでは、プラットフォーム固有のヘルパーを用意しなくても、ユーザーがログイン時の自動起動を選べるようにできます。

import { autostart } from 'murasaki/native'

if ((await autostart.status()) === 'disabled') {
  await autostart.enable()
}

// 同じ設定画面から無効化できるようにする
await autostart.disable()

現在の登録状態を確認するには autostart:read を、有効化・無効化には autostart:write を許可します。murasaki dev ではこの 3 つの操作をすべて拒否するため、一時的な開発用の Node 実行ファイルが永続的なログイン項目として登録されることはありません。登録先はユーザー単位で、macOS では LaunchAgent、Windows では現在のユーザーの Run キー、Linux では XDG Autostart のデスクトップエントリです。status() は、保存された登録が現在のパッケージ化された実行ファイルと完全に一致する場合だけ 'enabled' を返し、それ以外は 'disabled' を返します。

murasaki.config.ts
export default defineConfig({
  window: {
    capabilities: ['autostart:read', 'autostart:write'],
  },
})

enable() は、明示的なユーザー向け設定画面からのみ呼び出してください。ユーザー、端末管理、OS ポリシーはいずれも登録を削除・無効化できます。現在の macOS 実装は LaunchAgent を使うため、App Sandbox ビルドとは互換性がありません。サンドボックス化された配布には別途署名したログイン項目ヘルパーが必要で、この API ではまだ対応していません。

セキュアストレージ

信頼済みのレンダラーがリフレッシュトークンやライセンス値などの短い文字列を永続化する必要があり、プレーンテキストファイルへ保存したくない場合は secureStorage を使います。macOS では Keychain、Windows では Credential Manager、Linux では freedesktop.org の Secret Service D-Bus API(gnome-keyring、KWallet、KeePassXC など)を使用します。Linux では稼働中の Secret Service プロバイダーが必要で、到達できない場合はすべての呼び出しが構造化されたエラーで失敗します。その他の未対応プラットフォームは明示的な unsupported エラーを返します。どのプラットフォームでも、Murasaki がプレーンテキストファイルへフォールバックすることはありません。

import { secureStorage } from 'murasaki/native'

await secureStorage.set('refresh-token', token)
const saved = await secureStorage.get('refresh-token') // string | null
await secureStorage.delete('refresh-token')            // 未登録でも成功

必要なウィンドウだけへ操作ごとに独立して許可し、完全一致のキーまたは末尾プレフィックスによるスコープを推奨します。

murasaki.config.ts
export default defineConfig({
  appId: 'com.example.notes',
  productName: 'Notes',
  window: {
    capabilities: [
      { permission: 'secureStorage:get', allow: { keys: ['account:*'] } },
      { permission: 'secureStorage:set', allow: { keys: ['account:*'] } },
      { permission: 'secureStorage:delete', allow: { keys: ['account:*'] } },
    ],
  },
})

エントリは appId とキーから SHA-256 で導出した識別子で名前空間分離されるため、リリースをまたいで appId を維持してください。キーは空でない 256 UTF-8 バイト以下、値は空でない 2,048 UTF-8 バイト以下です。NUL は拒否され、ネイティブ IPC のボディにも 256 KiB の上限があります。保存済みデータが破損している、非 UTF-8 である、または上限を超えている場合は、暗黙に変換せずエラーを返します。

互換性のため、文字列形式のケイパビリティはキーに制限がありません。構造化された keys リストは通常、完全一致で、末尾が 1 つの * で終わるエントリだけがプレフィックス一致になります。Keychain / Credential Manager は保存時のプレーンテキストファイルを避ける仕組みであり、許可されたスコープ内で実行される XSS からシークレットを守るものではありません。レンダラーが必要としないシークレットは、Node またはサーバー専用のコードに置いてください。

トレイアイコン

クライアントコンポーネントから、1 つのシステムトレイアイコンを作成できます。デフォルトでは config.icon を使い、明示的に指定する場合は 8-bit RGB/RGBA PNG を渡します。操作ごとに独立した権限が必要です。

import { tray } from 'murasaki/native'

await tray.create({
  tooltip: '同期中',
  template: true,
  menu: [
    { id: 'open', label: 'Murasakiを開く' },
    { separator: true },
    { id: 'quit', label: '終了' },
  ],
  menuOnLeftClick: navigator.userAgent.includes('Mac OS X'),
  menuOnRightClick: true,
})
const unsubscribe = tray.onClick(({ button, double }) => {
  console.log({ button, double })
})
const unsubscribeMenu = tray.onMenuItem(async (id) => {
  if (id === 'open') await appWindow.show()
  if (id === 'quit') await app.quit()
})

await tray.setTooltip('同期完了')
await tray.setIcon('/absolute/path/to/synced.png')
await tray.setMenu([{ id: 'open', label: 'Murasakiを開く' }])
// 後で: unsubscribe(); unsubscribeMenu(); await tray.remove()
murasaki.config.ts
export default defineConfig({
  // ...
  capabilities: [
    'tray:create',
    'tray:setTooltip',
    'tray:setIcon',
    'tray:setMenu',
    'tray:remove',
    'window:show',
    'app:quit',
  ],
})

template は macOS 専用です。もう一度 create すると既存のアイコンを置き換えます。トレイメニューはイベント駆動で、クリック可能な項目には一意な id が必要です。終了などの特権アクションは、それぞれ本来のネイティブケイパビリティを経由します。プロセス全体のアイコンを作成したレンダラーを閉じると、アイコンも削除されます。

Linux のトレイは libappindicator を使うため、AppIndicator をホストする環境が必要です(GNOME では AppIndicator/KStatusNotifierItem Shell 拡張機能が必要です)。トレイメニューのクリックと動的なアイコン/メニューの差し替えは macOS / Windows と同様に動作しますが、トレイアイコン自体のクリック/ダブルクリックイベントは Linux では発火しません — AppIndicator には「アタッチされたメニューを表示する」以外のシグナルがないためです。

グローバルショートカット

信頼済みのクライアントコンポーネントから、macOS / Windows / Linux(X11/XWayland)でプロセス全体のショートカットを登録できます。登録と解除は、それぞれ独立したデフォルト拒否のケイパビリティです。

murasaki.config.ts
export default defineConfig({
  appId: 'com.example.notes',
  productName: 'Notes',
  window: {
    capabilities: [
      'globalShortcut:register',
      'globalShortcut:unregister',
    ],
  },
})
'use client'

import { useEffect } from 'react'
import { globalShortcut } from 'murasaki/native'

export function CaptureShortcut() {
  useEffect(() => {
    let active = true
    const unsubscribe = globalShortcut.onTriggered(({ id }) => {
      if (id === 'capture-region') startCapture()
    })

    void globalShortcut.register('CmdOrCtrl+Shift+K', 'capture-region')
      .catch((error) => {
        if (active) console.error('shortcutを利用できません', error)
      })

    return () => {
      active = false
      unsubscribe()
      void globalShortcut.unregister('capture-region')
    }
  }, [])

  return null
}

register(accelerator, id?){ id, accelerator } に解決されます。返される accelerator はプラットフォームごとに解決済みの正規値で、CmdOrCtrl は macOS では Command、Windows / Linux では Control になります。id を省略した場合は、この正規化された accelerator が id になります。unregisterAll() が解除するのは呼び出したレンダラー自身の登録だけで、他のウィンドウの登録には影響しません。

accelerator は上限付きの ASCII 文字列で、1〜4 個の modifier の後に既知の key を 1 個置きます。modifier の重複、key だけのショートカット、不正・未知の key、ライフサイクル/OS 予約済みの組み合わせ(Command+Q、Alt+F4 など。Linux には OS レベルの予約リストがありません — デスクトップ環境ごとに異なるためです)、id の重複、accelerator の重複、他のアプリがすでに取得済みの組み合わせは拒否されます。同時登録はプロセスあたり 64 件までです。id は 1〜128 文字で、英数字と ._:-+ を使えます。

所有権はレンダラーウィンドウに紐づき、クローズまたは破棄でその登録が自動的に解除され、アプリのシャットダウンで全登録が解除されます。トリガーイベントはライブのみで、所有者にだけ届きます。リロード後のリプレイはありません。

Linux のグローバルショートカットは X11 または XWayland が必須です(内部で使用する crate にネイティブな Wayland バックエンドがないため)。純粋な Wayland セッション(WAYLAND_DISPLAY が設定され DISPLAY が未設定)では、register は何も登録せず失敗するのではなく、構造化された unsupported エラーを返します。

パーサー、所有権、ケイパビリティ、macOS ビルド、Windows x64/arm64 ビルドは自動検証しています。OS レベルでの利用可否は、他のインストール済みアプリ、キーボードレイアウト、リモートデスクトップソフトウェア、OS 予約済みバインディングにも依存するため、リリース前に各対応 OS のパッケージ化されたアプリで、実際の組み合わせをスモークテストしてください。

システム権限

OS の同意と Murasaki のレンダラーケイパビリティは別物です。パッケージ化された macOS アプリでは、用途説明と任意の起動時リクエストを設定に宣言できます。用途説明は Info.plist へ書き込まれます。用途説明が不足している場合は拒否されるため、説明のないままプロンプトが表示されてアプリがクラッシュすることはありません。

murasaki.config.ts
export default defineConfig({
  // ...
  systemPermissions: {
    macOS: {
      camera: {
        usageDescription: 'ビデオ通話でカメラを使用します。',
        requestOnLaunch: true,
      },
      microphone: {
        usageDescription: '音声通話でマイクを使用します。',
      },
      screenRecording: { requestOnLaunch: false },
      accessibility: { requestOnLaunch: false },
      inputMonitoring: { requestOnLaunch: false },
      location: {
        usageDescription: '近くのスポットを表示します。',
        mode: 'whenInUse',
      },
      fullDiskAccess: { requestOnLaunch: false },
      photos: { usageDescription: '投稿に写真を添付します。' },
      contacts: { usageDescription: '友達を見つけます。' },
      calendar: { usageDescription: '予定を確認します。' },
      reminders: { usageDescription: 'リマインダーを確認します。' },
      speechRecognition: { usageDescription: 'ボイスメモを文字起こしします。' },
      bluetooth: { usageDescription: '近くのデバイスを見つけます。' },
      appleEvents: { usageDescription: '他のappを代わりに自動操作します。' },
      localNetwork: { usageDescription: 'このネットワーク上のデバイスを探します。' },
    },
  },
  capabilities: [
    'systemPermission:status',
    'systemPermission:request',
  ],
})

クライアントコンポーネントから文脈に応じて要求する場合:

const status = await systemPermission.status('microphone')
if (status === 'notDetermined') {
  await systemPermission.request('microphone')
}
種類形態補足
cameraリクエスト可能NSCameraUsageDescription
microphoneリクエスト可能NSMicrophoneUsageDescription
locationリクエスト可能NSLocationWhenInUseUsageDescriptionmode: 'always' では NSLocationAlwaysAndWhenInUseUsageDescription も追加)。
photosリクエスト可能NSPhotoLibraryUsageDescription。読み書き可能なライブラリアクセス。
contactsリクエスト可能NSContactsUsageDescription
calendarリクエスト可能NSCalendarsUsageDescription + NSCalendarsFullAccessUsageDescription(常に両方書き込まれます — 下記参照)。
remindersリクエスト可能NSRemindersUsageDescription + NSRemindersFullAccessUsageDescription(下記参照)。
speechRecognitionリクエスト可能NSSpeechRecognitionUsageDescription
bluetoothリクエスト可能、明示的なリクエスト呼び出しなしNSBluetoothAlwaysUsageDescription。下記参照。
screenRecordingプロンプト形式、用途説明なしgranted / notGranted のみ。
accessibilityプロンプト形式、用途説明なしgranted / notGranted のみ。
inputMonitoringプロンプト形式、用途説明なしgranted / notGranted のみ。
fullDiskAccessガイダンスのみ下記参照。
appleEventsガイダンスのみ、宣言のみNSAppleEventsUsageDescription。下記参照。
localNetwork宣言のみNSLocalNetworkUsageDescription。下記参照。

カメラ / マイク / 位置情報 / 写真 / 連絡先 / カレンダー / リマインダー / 音声認識の OS プロンプトは非同期に完了するため、request() が最初は notDetermined を返す場合があります。保護対象の機能を有効にする前に、アプリが再びフォーカスされた時点で status() を再確認してください。

requestOnLaunch は、パッケージ化された macOS アプリと、appleEvents / localNetwork(下記の宣言のみの種類)を除くすべての種類に適用されます。開発時はターミナル/Node ホストの ID で動作するため、TCC の実際の挙動はパッケージ化されたビルドで確認してください。Windows のパッケージ化されていないデスクトップアプリでは、デバイスに紐づく種類の同意はそれを利用するデバイス API 側が要求するため、汎用的な起動時プロンプトはありません。そのため Murasaki は許可されたと偽ることなく unsupported を返します。macOS の screen recording / accessibility / input monitoring は、初回と拒否を区別できない場合に notGranted を返します。15 種類すべてが macOS 専用です — Windows と Linux には、これらの TCC 管理下のプロンプトに相当する OS レベルの機能がないため、許可されたと偽ることなく、すべての呼び出しで unsupported を返します。

location.usageDescription は camera / microphone と同様に必須で、NSLocationWhenInUseUsageDescription へ書き込まれます。mode: 'always' を指定すると NSLocationAlwaysAndWhenInUseUsageDescription も追加で書き込まれ(Apple は always リクエストでも when-in-use キーの存在を要求するため)、when-in-use ではなく常時許可(always-authorization)を要求します。

calendar / reminders は macOS 14 以降では full access をリクエストし、それより古いシステムでは非推奨の 14 未満向け EventKit API にフォールバックします。この判定はビルドマシンではなく、リクエスト時点で実際に動いているシステムに対して行われるため、1 つのパッケージ化されたアプリがどちらのシステムでも正しく動作します。そのため Info.plist には、ビルドしたシステムに関係なく、常にレガシーの用途説明キーと 14 以降の full access キーの両方が書き込まれます。

bluetooth には明示的なリクエスト呼び出しがありません — CoreBluetooth は central manager が最初に作成された時点で暗黙的に同意を確定します。status()CBManager.authorization(ライブの manager を必要としないクラスレベルのプロパティ)を読むだけなので、他のどの種類の status 確認とも同じくらい低コストです。request() は、その OS 側の判定をトリガーするためだけに manager を作成します。

fullDiskAccess はガイダンスのみです。macOS には Full Disk Access 用の TCC リクエスト API が存在しないため、request() はシステム設定の Full Disk Access ペインを開いて、ユーザー自身に許可してもらうことしかできません — 許可が得られたと偽ることは決してありません。status() はベストエフォートのヒューリスティックです(Full Disk Access 自体で保護されている ~/Library/Application Support/com.apple.TCC/TCC.db が読めるかどうかを確認します)。通常の granted / notGranted に加えて、ヒューリスティックで確信が持てない場合は unknown を返すことがあります。

appleEventslocalNetwork は宣言のみです — どちらにも requestOnLaunch フィールドはなく、Murasaki の役割は用途説明を書き込むことだけです。オートメーションの同意は対象アプリごとに付与され、実際に Apple Event を送信して初めて判定できるため、appleEventsstatus() は常に unknown を返し、request() は(上記の fullDiskAccess と同様)確認できない許可を主張する代わりに、ガイダンスとしてシステム設定の Automation ペインを開くだけです。localNetwork には照会/リクエストの API が一切存在しません — アプリが実際にローカルネットワークへアクセスした最初のタイミングで macOS が自動的にプロンプトを表示するため、status()request() はどちらも静的な unknown を返すだけの no-op です。

Entitlements と App Sandbox

murasaki bundle --sign は、sign.entitlements / sign.helperEntitlements を指定しない限り、メインアプリと同梱の Node ヘルパーに対して、それぞれ別々の entitlements plist を生成します。

  • Murasaki のデフォルトである hardened-runtime-only(App Sandbox なし)の姿勢では、署名済みアプリに Info.plist の用途説明と、対応するホストリソースの entitlement の両方が必要です。camera(com.apple.security.device.camera)、microphone(com.apple.security.device.audio-input)、location、photos、contacts、calendar/reminders、Apple Events の entitlement を systemPermissions.macOS から生成します。Bluetooth のデバイス entitlement は App Sandbox 専用で、speech recognition には Hardened Runtime 用のリソース entitlement がないため、この 2 種類には用途説明だけを使用します。
  • Node にのみ JIT、署名されていない実行可能メモリ、ライブラリ検証の無効化を付与します。.node アドオンは executable entitlement なしで署名します。アプリが所有する実行可能な bundle.resources は、executable: true を指定すると、外側のアプリより先に署名されます。
  • sign.appSandbox: true は、現在はフェイルクローズで拒否されます。Apple のサンドボックス継承ヘルパーは app-sandbox + inherit のみの entitlement を要求しますが、これは現在の同梱 Node / JIT プロセスとは両立しません。署名・公証を含む一連の経路が確立するまで、App Sandbox に対応しているとは表記しません。
  • カスタムの main / helper plist はそのまま使用します。指定したパスが存在しない、ファイルでない、または plist として無効な場合は、生成された権限へ黙ってフォールバックせず、リリースビルドを停止します。

組み込みのメニューアクション

項目の action は、関数の代わりに組み込みの <Action.*/> 要素の 1 つにできます — これらは(OS 自身が処理する)ネイティブロールか、または小さなクライアントサイドの挙動を実行します:

Action挙動
<Action.Copy />, <Action.Paste />, <Action.Cut />, <Action.SelectAll />, <Action.Undo />, <Action.Redo />ネイティブ OS の編集ロール
<Action.Quit />ネイティブの終了ロール
<Action.Reload />ウィンドウをリロードします (location.reload())
<Action.Navigate to="/path" />ルーター経由のクライアントサイドナビゲーション
<Action.Run action={fn} />通常の関数を実行します(関数を直接渡すのと同じです)

自動更新

useUpdate() は、更新の確認・ダウンロード・インストールを行います — その確認・ダウンロードのロジックは Node 上で動作し、コンテキストメニューやアプリメニューが使う IPC ブリッジではなく、アプリの他の部分を配信しているのと同じローカル HTTP サーバー (Server Actions や API Routes と同じ仕組み) 経由でアクセスされます。<UpdateButton /> (こちらも murasaki から、@murasakijs/ui でスタイリング) は、それをラップしたすぐに使えるボタンです:

import { useUpdate } from 'murasaki'

const { status, latest, check, download, install } = useUpdate()

statusidle → checking → available → downloading → ready(または not-available / error)と遷移します。セットアップはコマンド 2 つだけです — マニフェストの形式、セキュリティモデル、GitHub Actions でのリリースワークフローについては、専用の自動更新ガイドを参照してください。

構造化されたケイパビリティのスコープは、シェルの URL / パス、正確な権限昇格の実行ファイルと引数の組、対象ウィンドウのラベル、OS 権限名、セキュアストレージのキー、WebView の cookie URL を扱います。グローバルショートカットの accelerator、ダイアログのデフォルト値など、それ以外の引数はコマンド単位です。設計に着手する前に、プラットフォーム機能状況を確認してください。

次へ

GitHub でこのページを改善

On this page