Service Workerでファイルアップロードをモックし、デモページに添付ファイル機能を追加
GitHub Pages上で動作するLexxyの「Try it」デモページに、Service Workerを使ってActive StorageのDirect Uploadエンドポイントをモックすることで、サーバーなしにファイルアップロードの完全なライフサイクルを体験できるようにした変更です。あわせて、:トリガーで起動する絵文字ピッカーも追加されました。
背景
これまで「Try it」ページではattachments="false"属性によって添付ファイル機能が無効化されており、「Attachment uploads are not supported in this demo」という注記が表示されていました。GitHub Pagesは静的ホスティング環境であるため、Active StorageのDirect Uploadが必要とするサーバーサイドのエンドポイントを実装できず、アップロード機能をそのまま有効化できなかったことが背景にあります。
この制約を解消するために採用されたのが、Service Workerによるエンドポイントのモックです。サーバーを一切変更せず、ブラウザ内でActive Storage互換のAPIレスポンスを再現することで、デモ環境の制約を克服しています。
技術的な変更
docs/attachments-sw.jsとして追加されたService Workerが、Active StorageのDirect Uploadに必要な3つのHTTPエンドポイントをインターセプトします。具体的には以下のリクエストを処理します:
-
POST /rails/active_storage/direct_uploads— Blobメタデータの登録とアップロード先URLの発行 -
PUT /rails/active_storage/disk/:signed_id— ファイル本体の保存(Mapオブジェクトにバイナリを格納) -
GET /rails/active_storage/blobs/:signed_id/:filename— 保存済みファイルの配信
createBlob関数では、demo-signed-id-${++blobCounter}形式の署名付きIDを生成し、direct_upload.urlとしてディスクアップロード先を返します。これにより、Lexxy側のアップロードクライアントは本物のActive Storageと同じフローで動作します。
async function createBlob(request) {
const { blob } = await request.json()
const signedId = `demo-signed-id-${++blobCounter}`
files.set(signedId, { metadata: blob })
return new Response(JSON.stringify({
id: blobCounter,
key: `demo-key-${blobCounter}`,
filename: blob.filename,
content_type: blob.content_type,
byte_size: blob.byte_size,
checksum: blob.checksum,
signed_id: signedId,
attachable_sgid: `demo-sgid-${blobCounter}`,
direct_upload: {
url: `/rails/active_storage/disk/${signedId}`,
headers: { "Content-Type": blob.content_type },
},
}), { status: 200, headers: { "Content-Type": "application/json" } })
}
docs/try-it.md側では、attachments="false"を削除し、data-direct-upload-urlとdata-blob-url-template属性を追加してActive Storage互換のURLを指定するよう変更されました。Service Workerの登録はnavigator.serviceWorker.register()1行で行われます。
さらに、<lexxy-prompt>コンポーネントを用いた絵文字ピッカーが追加されました。trigger=":"属性により:入力で起動し、insert-editable-text属性によって選択した絵文字がそのままエディタに挿入されます。各<lexxy-prompt-item>にはsearch属性でキーワードが定義されており、インクリメンタルサーチに対応しています。
設計判断
ブラウザのService Worker APIを使うことで、デモ用のモックサーバーやプロキシを別途用意することなく、静的ホスティング環境の制約を完全にブラウザ内で解決している点が注目されます。ファイルはページのライフタイム中のみMapオブジェクトに保持されるため、永続化は行われませんが、デモ用途としてはプログレスバーの表示やアタッチメントレンダリングを含む完全なアップロードフローを確認するには十分です。
Active StorageのAPIレスポンス形式(signed_id、attachable_sgid、direct_uploadオブジェクトなど)を忠実に再現しているため、Lexxyのクライアントコードはモック環境であることを意識せずに動作します。これはLexxyとActive Storageのインターフェース仕様をそのままデモのモック仕様として利用した設計といえます。
まとめ
Service WorkerでActive Storage互換のエンドポイントをモックするというアプローチにより、静的ホスティング環境の制約を乗り越えてデモページの機能を大幅に拡充しています。本番環境のクライアントコードを一切変更せずにデモを強化できる点は、Lexxyのコンポーネント設計がサーバー実装から適切に分離されていることの証左でもあります。