こんにちは、エンジニアのオオバです。

お悩みさん
お悩みさん
  • Finder Sync拡張をMac App Storeに出したい
  • temporary-exceptionのentitlementを外したい
  • App Groupで共有したbookmarkが拡張で解決できない
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    Finder のツールバーや右クリックから、「名称未設定.txt」「名称未設定.md」を作れる Finder Sync 拡張を自作しています。

    これを Mac App Store に出そうとして、サンドボックス化でハマりました。

    設計段階では「ホストアプリで作った bookmark を App Group で拡張に渡せば済む」と考えていたんです。

    実機で試すと、その案は成り立ちませんでした。

    bookmarkの共有は諦め、拡張からURLスキームでホストアプリを裏で起動して、ファイル作成をホストアプリに任せる

    先に結論をまとめます。

    Finder Sync拡張のサンドボックス化で分かったこと

    ①ホストアプリで作ったapp-scopedのbookmarkは、App Groupで渡しても拡張では解決できない(NSCocoaErrorDomain Code=259)

    ②document-scopedのbookmarkはフォルダを対象にできない(Item URL disallowed by security policy)

    ③拡張はURLスキームで依頼を送るだけにして、ファイル作成はフォルダ許可を持つホストアプリが行う

    ここから、試した順に書いていきます。

    →11万文字で徹底解説した「DOTweenの教科書」Unityアニメーションの超効率化ツールはこちら

    やりたかったこと:absolute-pathのtemporary-exceptionを外す

    拡張には、これまで次の entitlement を付けていました。

    ルート以下すべてに読み書きできる例外です。

    開発中は便利でした。

    とはいえ、このまま審査に出せばほぼ確実に却下されます。

    App Store に出すなら、この例外を外して、ユーザーが許可したフォルダにだけ ファイルを作れる形にする必要がありました。

    最初の案:bookmarkをApp Groupで拡張に渡す

    最初に考えた流れは次のとおりです。

    ホストアプリの設定画面は、この案を前提に作りました。

    ホストアプリの設定画面(SwiftUI)。フォルダ許可と拡張の有効化を案内する

    ホストアプリの設定画面(SwiftUI)。フォルダ許可と拡張の有効化を案内する

    画面は3つの手順に分かれています。

    App Groupでの共有自体は動いた

    App Group の共有は、ad-hoc 署名でも動きました。

    拡張から bookmark の Data はちゃんと読めています。

    ここまでは順調でした。

    拡張でbookmarkを解決できない

    問題は拡張で bookmark を解決するところです。

    次のエラーで失敗しました。

    NSCocoaErrorDomain Code=259  
    「ファイルのフォーマットが正しくないため、開けませんでした。」  
    

    データは届いているのに、フォーマットが正しくないと言われる。

    app-scoped bookmark は、作ったアプリでしか解決できません。

    ホストアプリと拡張は別のプロセスで、サンドボックス上も別のアプリとして扱われます。

    そのため、ホストアプリが作った bookmark を拡張が解決することはできませんでした。

    次の案:document-scoped bookmarkも駄目だった

    app-scoped が駄目なら、document-scoped bookmark はどうか。

    App Group のコンテナ内に置いたファイルを基準にして、document-scoped bookmark を作る方法を試しました。

    まず出たのが次のエラーです。

    Required entitlement com.apple.security.files.bookmarks.collection-scope is missing or false  
    

    エラー文のとおり com.apple.security.files.bookmarks.collection-scope を付けると、今度は別のエラーに変わりました。

    Item URL disallowed by security policy  
    

    document-scoped bookmark は、フォルダを対象にできません。

    今回欲しいのは「ホームフォルダ以下に書き込む権限」なので、この案も使えませんでした。

    サンドボックスは正しく効いている

    念のため、拡張から直接ファイルを書き込めるかも確かめました。

    結果は全フォルダで失敗。

    temporary-exception を外したことで、サンドボックスは意図どおりに効いています。

    つまり、拡張の中だけでファイルを作る道はふさがっている状態でした。

    解決のヒント:OSSのRClickの作り

    行き詰まったので、同じように Finder の右クリックを拡張する OSS の RClick(GitHub で1078スター)を読みました。

    RClick では、拡張はクリックされたことを本体アプリへ伝えるだけでした。

    ファイル操作は、常駐している本体アプリが行っています。

    拡張から本体アプリへの連絡には DistributedNotificationCenter を使っていました。

    本体アプリならフォルダ許可の bookmark を自分で作って自分で解決できるので、Code=259 の問題は起きません。

    常駐はさせたくなかった

    ただ、「新規ファイルを作る」だけのツールのために、本体アプリを常駐させるのは避けたいところ。

    そこで、拡張から URL スキームでホストアプリへ依頼を送り、必要なときだけホストアプリを起動する方式にしました。

    解決:URLスキームでホストアプリを裏で起動する

    拡張からは、次のような URL をホストアプリへ送ります。

    findernewfile://create?dir=...&ext=md&token=...

    前面に出さずに起動する

    拡張からは NSWorkspace の open(_:withApplicationAt:configuration:) で、URL とホストアプリを指定して開きます。

    configuration は activates = false にしました。

    これで、ホストアプリが前面に出ずに起動します。

    URLで起動されたときは画面を出さない

    ホストアプリは LSUIElement で起動するようにしています。

    起動時の通知に入っている launchIsDefaultUserInfoKey を見て、起動のされ方を分けました。

    ユーザーから見ると、Finder のボタンを押したらファイルができるだけです。

    すぐ終了すると続けて送った依頼を取りこぼした

    最初は、1件処理したらすぐホストアプリを終了するようにしていました。

    ところが、続けてボタンを押すと2件目以降の依頼を取りこぼしてしまう。

    そこで、最後の依頼から30秒後に終了する ように変えました。

    tokenで外からの呼び出しを弾く

    URL スキームは、拡張以外からも呼べてしまいます。

    たとえば Web ページのリンクに findernewfile:// を仕込まれると、勝手にファイルを作られるかもしれません。

    その対策が token です。

    App Group で拡張とホストアプリだけが知っている合言葉を共有し、URL の token が一致しない依頼は処理しないようにしました。

    結果:主要な場所でファイルを作れた

    この方式で、次の場所に新規ファイルを作れることを確認しました。

    拡張から absolute-path の temporary-exception を外したまま、これまでどおり使えています。

    補足:設計前に実機で試すべきだった

    今回いちばんの反省は、「bookmark を App Group で共有すればいい」という案を、確かめないまま設計の前提にしたことです。

    設定画面まで作ってから、前提が崩れたことに気づきました。

    Finder Sync 拡張とホストアプリの間では、この案は成り立ちません。

    一方で、確かめるだけなら実機で数十分あれば足ります。

    サンドボックスまわりは、ドキュメントを読んだだけでは分からない制約が多いですよね。

    設計を固める前に、いちばん怪しいところだけ小さく試すのがおすすめです。

    まとめ:Finder Sync拡張は依頼を送るだけにして、ファイル操作はホストアプリに任せる

    Finder Sync拡張をサンドボックス化するときのポイント

    ①ホストアプリのapp-scoped bookmarkはApp Groupで渡しても拡張で解決できない(Code=259)

    ②document-scoped bookmarkはcollection-scopeを付けてもフォルダを対象にできない

    ③拡張からURLスキームでホストアプリを前面に出さず起動し、ファイル作成はホストアプリが行う

    ④続けて届く依頼に備えて、ホストアプリは最後の依頼から30秒後に終了する

    ⑤URLスキームにはApp Groupで共有したtokenを付けて、外からの呼び出しを弾く

    Finder Sync 拡張を App Store に出そうとしている人の参考になれば嬉しいです。

    オススメ記事
    検証環境