はじめに
こんにちは、WEAR Webフロントエンドチームでテックリードをしている冨川(@ssssota)です。業務でUniversal Linksのテストを効率化するために、独自のパッケージを開発し、GitHubおよびnpmで公開しました。本記事ではそのモチベーションと利用方法などを紹介します。
目次
背景・課題
まず、Universal Linksについて紹介します。Universal Linksは、ブラウザなどからiOSアプリを開くためのディープリンク技術の一種です。apple-app-site-association
というファイルをWebサーバに配置することで、WebページとiOSアプリの関連付けを行います。
apple-app-site-association
ファイル
apple-app-site-association
ファイルは、Webサーバに配置するJSONファイルです。このファイルには、Universal Linksの挙動を制御するための情報を記述します。以下に例を示します。
{ "applinks": { "details": [ { "appID": "HOGE1.com.example.app", "components": [ { "#": "nondeeplinking", "exclude": true }, { "/": "/search/", "?": { "q": "*" } } ] }, { "appIDs": ["HOGE2.com.example.app", "HOGE2.com.example.app"], "components": [{ "/": "/*/posts/*" }] } ] } }
このように、 apple-app-site-association
ファイルには、アプリIDと対象URLのマッピングが記述されています。このファイルをWebサーバに配置することで、対象URLに対してiOSアプリが開かれるか否かを制御できます。一見するとglobのような記述と、パス、クエリ、フラグメントを設定でき柔軟に見えます。しかし、挙動が予想しにくい面もあります。
例えば、 *
はワイルドカードで /
という文字にもマッチします。上記の例では /*/posts/*
というパス設定を記述していますが、 /foo/posts/
や /foo/bar/posts/baz/qux
なども該当します。これはglobに慣れているエンジニアにとっては違和感を感じるかもしれません。
挙動確認の課題
Webサーバにファイルを配置する関係上、アプリエンジニアではなくWebエンジニアがファイルを管理することがあります。これはWebアプリのパスを管理しているエンジニアが管理するという意味では非常に合理的ではあるものの、挙動確認はやや困難です。設定のミスによって、意図せずアプリが起動するようになるなどのリスクもあります。
apple-app-site-association
ファイルの挙動確認は、 swcutil
というmacOSにプリインストールされたコマンドを用いることで行うことができます。しかし、このコマンドはドキュメントが少ない他、Universal Linksの挙動確認においては使い勝手が悪いです。以下にいくつかの使用例を示します。
$ swcutil verify -j ./apple-app-site-association -u '/' swcutil must be run as root $ sudo swcutil verify -j ./apple-app-site-association -u '/' { s = applinks, a = HOGE.com.example.app, d = www.example.com }: Pattern "/" matched. $ sudo swcutil verify -j '{"applinks":{"details":[{"appIDs":["HOGE1.com.example.app","HOGE2.com.example.app"],"components":[{"#":"nondeeplinking","exclude":true},{"/":"/search/"}]}]}}' -u '/search/' { s = applinks, a = HOGE1.com.example.app, d = www.example.com }: Pattern "/search/" matched. { s = applinks, a = HOGE2.com.example.app, d = www.example.com }: Pattern "/search/" matched.
まず、 sudo
コマンドでroot権限を使用していることに気づきます。非root権限で実行すると swcutil must be run as root
というメッセージが標準エラー出力に出力されます。また、規模の大きなWebアプリケーションでは多数のURLパスのテストが必要となるケースも考えられます。このような場合シェルスクリプトなどを用い反復することになります。しかしながら出力はプログラムから扱いやすい形式とはいえません。
解決の取り組み
先のような課題を解決するため、 universal-links-test
というNPMパッケージを開発しました。このパッケージは、 swcutil
コマンドをラップした関数を提供し、 apple-app-site-association
ファイルの挙動確認をサポートします。
NPMパッケージとして提供することで、Web開発者が容易に導入し、テストと共に apple-app-site-association
ファイルを管理できるようになります。
パッケージの機能
現在、universal-links-test
は主に verify
関数を提供します1。この関数は apple-app-site-association
ファイルのファイルパス(もしくはJSON)とURLを受けとり、指定されたURLによってどのアプリが開かれるかをMapで返します。以下に例を示します。
import { verify, type AppleAppSiteAssociation } from "universal-links-test"; const aasa: AppleAppSiteAssociation = { applinks: { details: [{ appIDs: ["HOGE.com.example.app"], components: [ { "#": "nondeeplinking", exclude: true }, { "/": "/search/" } ], }], }, }; const result: Map<string, "match" | "block"> = await verify(aasa, "/search/"); console.log(result.get("HOGE.com.example.app")); // => "match"
root権限とmacOS依存
verify
関数は swcutil
コマンドのラッパーであるため、macOSかつroot権限が必要です。 universal-links-test
はこの課題に対し完全な解決策は提供できないものの、緩和策を提供します。
それが universal-links-test/sim
というモジュールです。これは universal-links-test
同様、 verify
関数を提供しています。 swcutil
の挙動をシミュレートしているため、macOSやroot権限でなくとも利用できます。開発時には universal-links-test/sim
を、CI環境では universal-links-test
を利用することで、Universal Linksの挙動確認を確実に行えます。
GitHub Actionsでの利用例を以下に示します。 universal-links-test
はmacOSかつroot権限を必要とするため、CIはmacOSランナーを利用する他、 sudo
コマンドを用いてroot権限を取得する必要があります。すべてのテストをroot権限で実行するのはセキュリティ上望ましくないため、VitestやJestなどのテストランナーを使っている場合でも、対象ファイルのみ sudo
で実行するなど配慮が必要です。以下の例では node --test
コマンドを使うことで隔離しています。
// universal-links.test.js import { verify } from "universal-links-test"; import { test } from "node:test"; import * as assert from "node:assert"; const appleAppSiteAssociationPath = "path/to/apple-app-site-association"; test("Universal Links test", async () => { const cases = [ ["HOGE.com.example.app", "/search/", "match"], ["HOGE.com.example.app", "/search/#nondeeplinking", "block"], ["HOGE.com.example.app", "/", undefined], ]; for (const [appID, path, expected] of cases) { const result = await verify(appleAppSiteAssociationPath, path); assert.strictEqual(result.get(appID), expected); } });
# .github/workflows/test.yml # ... jobs: test: runs-on: macos-latest # macOSランナーを利用 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 22 - run: npm ci - run: sudo node --test universal-links.test.js # root権限が必要
デモ
デモページをGitHub Pagesで公開しています。このページは universal-links-test/sim
を利用しているため、ブラウザから環境問わずUniversal Linksの挙動確認できます。
おわりに
universal-links-test
は、Universal Linksの挙動確認をサポートするためのNPMパッケージです。Web開発者が容易に導入し、 apple-app-site-association
ファイルの管理と挙動確認ができます。また、 universal-links-test/sim
を利用することでmacOSやroot権限がなくともUniversal Linksの挙動確認ができます。
universal-links-test
では、今後も機能追加やバグ修正していく予定です。ぜひご利用いただき、フィードバックをお寄せいただければ幸いです。
私たちZOZOでは一緒にサービスを作り上げてくれる方を募集しています。ご興味のある方は、以下のリンクからぜひご応募ください。
- v0.1.0リリース時点での機能です。今後のバージョンで機能変更される可能性があります。↩