この記事は、Techouse Advent Calendar 2024 14日目です。
昨日は Higashiji さんによる Ruby のファイル操作で覚えておきたいバッファリングと flush/fsync でした。
14日目は、青木真一 が担当します。
背景「なぜFigmaプラグインで画像のアップロードをしたかったか」
弊社が提供するATSサービス『クラウドハウス採用』では自由度高くウェブサイト制作が可能という特徴があります。画像も自由に利用することができるため、1ウェブサイト作るのに少なくとも50ファイル以上の画像を利用されるお客様がほとんどです。
その際、大変になるのが画像のアップロード作業。現在の仕様ですと「ひとつ選択してアップロード」をファイル数分繰り返す必要があります。これをデザイナーにクラウドハウス採用の管理画面を開く手間を掛けさせず解決できないかという想いで試してみたのが今回のFigmaプラグインでの画像アップロードとなります。
Figma ではプラグイン作成のために API が用意されており、API を用いることで、選択した画像をサードパーティや自社サービスへアップロードする事ができます。
今回、 Figma プラグインを自作し『クラウドハウス採用』の API を叩いて画像をアップロードできるようにすることで、前述した画像アップロードの煩雑な作業を解消することを目指しました。
Figma プラグインによる画像アップロードを『クラウドハウス採用』に組み込むために必要な事
Figma で選択した画像を取得し、それを『クラウドハウス採用』の API に渡すために、画像を Base64 形式に変換する処理を実装する必要があります。ただし、画像のアップロード処理が正しく動作しない場合、せっかく書いたコードが無駄になってしまいます。そのため、まずは画像アップロードが成功するかどうかを確認するために、fetch の引数をあらかじめ用意したアップロードに成功する値で設定し、試行錯誤を繰り返しました。
画像アップロード実現に向けて試行錯誤
1. Figmaから画像アップロード
まずは、『クラウドハウス採用』への画像アップロードのHTTPリクエストが Figmaプラグインからでも成功するのかを試してみました。
以下の fetch
をFigmaプラグインから呼び出してみました。
fetch(`http://recback.localhost:3005/graphql`, { headers: { authorization: `Bearer ${accessToken}`, // 実際のコードでは accessToken は適切に定義してあります accept: "application/graphql+json, application/json", "content-type": "application/json", }, body: "(長いので省略)", method: "POST", });
そうすると、以下のようなエラーとなりました。
Access to fetch at 'http://recback.localhost:3005/graphql' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
どうやら、Figma プラグインからの HTTP リクエストは Origin が null となり CORS 制約でブロックされてしまうようです。
CORS 制約では、リクエストの Origin ヘッダーが適切に設定されていることが必要ですが、Origin が null の場合、ほとんどのサーバーはセキュリティの観点からリクエストを拒否します、『クラウドハウス採用』もそのように設定してあります。このため、サーバー側で CORS の設定を変更して null を許可することは技術的には可能ですが、セキュリティリスクが高く、実用的ではないと思いました。
ただし、Figma プラグインから画像をアップロードできることが確認できれば、現在の課題がセキュリティ上の問題に絞られるため、解決への一歩となります。このため、引き続き調査を進めることにしました。
2. 自社サービス以外のAPIへのリクエストができるか確認
そもそもFigmaプラグインからの fetch がうまくいくこと確認して安心したうえで調査を進めたかったので、『Pokémon API』への fetch が成功するか試しました。
以下呼び出した fetch です。
fetch("https://pokeapi.co/api/v2/pokemon/ditto", { headers: { accept: "*/*", "accept-language": "ja,en-US;q=0.9,en;q=0.8", "cache-control": "no-cache", pragma: "no-cache", priority: "u=1, i", "sec-ch-ua": '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": '"macOS"', "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", }, referrer: "https://pokeapi.co/", method: "GET", });
そうすると、以下のように返ってきたので安心して調査続行できます。
3. CORSの設定
前出のエラー内容から、 1. のリクエストの失敗要因は、『クラウドハウス採用』サーバ側のレスポンスヘッダに Access-Control-Allow-Origin
等のCORSリクエストを捌くのに必要なヘッダが設定されていない事であると推測しました。それを検証するため、開発環境で『クラウドハウス採用』サーバからのレスポンスヘッダを以下のように設定し、 fetch を呼び出してみました。
Access-Control-Allow-Origin: * Access-Control-Request-Methods: * Access-Control-Request-Headers: *
しかし、同じエラーが返ってきます。
4. プリフライトの設定
Figma の開発ツールのネットワークを見てみると、該当の POST リクエストの前にもう一つ、HTTPリクエストが飛んでいる事に気が付きました。
Preflight
というリクエストが飛び、そのレスポンスが 404 となっているようです。Preflight リクエストが何か調査してみると、これはブラウザーが自動的に発行するものであり、サーバー側はどんなオリジン、メソッド、ヘッダを許可するかを返す必要があるようです。また、Preflight リクエストは HTTP メソッドが OPTIONS となります。https://developer.mozilla.org/ja/docs/Glossary/Preflight_request
なのでさらにサーバ側で以下のような設定をしました。以下、Ruby on Rails での設定例です。
ルーティング
match '/graphql' => 'graphql#preflight', via: :options
コントローラー
def preflight response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = '*' response.headers['Access-Control-Allow-Headers'] = '*' head :ok end
こちらの設定で fetch を呼び出してみました。
そうすると、preflight, 画像アップロードともに成功しました!
まとめ
やりたかったことはできるように設定できたのですが、どのオリジンからもリクエストできるようになってしまうのはセキュリティの観点で看過できないので実用はできない、少なくとも今回の解決案では運用できないと結論付けました。
私はフロントエンジニアなのもあり、Figmaプラグインをどうやって作っていくのかについてもっと書くつもりで試行錯誤を始めたのですが、想定外のところ、CORSの壁に阻まれ断念することにしました。
他の解決方法を考えるなら、プロキシサーバーを経由することで CORS 制約を回避する方法も考えられるので引き続き試していきたいです!
明日のTechouse Advent Calendar 2024は keima さんによる GitHub Actionsのガードを高くする です。
Techouseでは、社会課題の解決に一緒に取り組むエンジニアを募集しております。 ご応募お待ちしております。