見出し画像

AWS Lambdaと連携してリンク先をシンプルに表示するFigmaウィジェットを作った

この記事は、Figma 開発 Advent Calendar 2022 の12日目の記事です。

先日、Simple BookmarkというFigma・FigJamウィジェットを作成しました。URLを入力すると、ファビコンとタイトルを表示するシンプルなウィジェットをFigma、FigJamのキャンバスに配置することができます。

公開から2週間ほど経ちますが1,000人以上のの方に試していただきました。
実際に色んな人に使ってもらうのも嬉しいですし、何より自分が日常的に使う道具を自分自身で作るのは楽しいものです。

こんなシンプルなウィジェットでも、実際に作ってみると色々な試行錯誤がありました。
そのあたりを書いてみます。

なぜつくったのか?

Figmaでデザインする際、グラフィックだけでは表現できない細かな仕様や検討事項、関連資料のリンクを見やすくまとめるために以下のようなブックマークコンポーネントを使用しています。

このコンポーネントでは、タイトルとリンクURLを入力し、コンポーネントのバリアント機能によりアイコンを設定しています。
リンク先が視覚的にわかりやすく示すことができて便利ですが、もっと楽に入力できると良さそうです。そこでFigma・FigJamウィジェットを作成し、このコンポーネントを置き換えることにしました。

公式ドキュメントに説明があるように、ウィジェットはプラグインと異なりカンバス上にオブジェクトとして配置でき、編集権限があれば誰でも実行できます。

また、閲覧権限の場合はウィジェットとしての処理は実行できないものの、カンバス上のオブジェクトとしては表示可能です。

つまり、Figma・FigJamウィジェットは“カンバス上のオブジェクトにインタラクティブな機能を付与できる仕組み”と捉えることができ、普段使っている便利コンポーネントをもっと便利に拡張できる可能性を秘めています。

コンセプト

FigJamでのリンクプレビューや、同種のウィジェットはすでに存在していましたが、いくつかの理由により自分の目的を充分に満たすものではありませんでした。

  • og:image の有無により異なるフォーマットで表示され見た目が揃わない

  • 認証が必要なURLではタイトルが表示されない

そのため、これらを満たすようなコンセプトの元でウィジェットを作ることにしました。

  1. シンプルで統一感のある見た目

  2. どんなURLでもなんとなく良い感じにしてくれる

  3. タイトルがうまく表示されない場合は手動で修正できる

1. シンプルで統一感のある見た目

URLをすると → ファビコンとタイトルが表示される

この処理を極力シンプルなUIで実現するために、入力モードではURLの入力エリアのみが表示され、表示モードではファビコンとタイトルのみが表示されようにします。
このウィジェットには入力と表示の2つのモードを設けることにしました。

2. どんなURLでもなんとなく良い感じにしてくれる

どんなURLでもきちんとタイトルとファビコンが表示されるのが理想ですが、現実的には難しいです。

  • 認証が必要なURLではタイトル取得できない

  • htmlは正しく取得できているが、タイトル部分をうまく判定できない

  • 通信がタイムアウトになってしまう

そのため、タイトルが取得できない場合はURLのパス名を表示し、ファビコンが取得できない場合は代わりにデフォルトアイコンを表示します。自分がよく利用するURLはGithubプライベートリポジトリや認証が必要なTrelloのカードのURLなどです。どちらもタイトル取得できないため、以下のようにパス名を表示しています。

またGoogleドライブやGoogleスプレッドシートなどのGoogle Appは認証画面にリダイレクトされてしまいます。
苦肉の策ですが、URLのホスト名を判定して例外ケースとしてサービス名を返すようにします。

3. タイトルがうまく表示されない場合は手動で修正できる

それでも駄目な場合は手動でタイトル修正したいことがあります。そのためプラグインウィンドウを利用してタイトルを変更できるようにします。利用者にとっては手間ですが、最終的には意図通りに変更することができます。

ウィジェットの処理の流れ

以上をウィジェットの処理の流れとしてフロー図にまとめると、こんな感じになります。

開発

ウィジェットの開発は3種類のテンプレートから始めることができます。今回はNetwork Requestを利用したいため「With iFrame & browser APIs」を選びました。このサンプルがとてもよくできていて、2種類のテンプレートを触るだけでウィジェットとプラグインの仕組みを大まかに理解することができました。

ウィジェットとプラグインはTypeScriptで記述しJavaScriptとして実行されますが、Network Requestを直接行うことはできません。iframeとして表示されるプラグインウィンドウを介して行います。

こんな感じで入力したURLからhtmlを取得する処理を書きます。

しかし、実行した後にこれではうまく行かないことに気づきました。
ブラウザで実行されるJavascriptでは、呼び出し元と呼び出し先のドメインが異なる場合、httpヘッダーのAccess-Control-Allow-Originで許可した呼び出し元からしか読むことができません。そのためConsoleに以下のようなエラーが出力されます。

Access to XMLHttpRequest at 'https://google.com/' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

サーバーサイドではこのような制限はないため、タイトル取得用のAPIを別途作成してこのエラーを回避することにします。

AWS Lambdaでのサーバーサイド開発

サーバーサイドの環境としてAWS Lambdaを使用しました。サーバーサイド開発のほぼ未経験ですが、AWS Lambdaは数年前にAlexaスキルを触った際に利用したことがありました。

Lambda 関数の作成についてはこちらが参考になりました。

まずはNode.jsでhtmlを読み込む処理を書きます。

const https = require('https')
https.get(url, (resp) => { 
    let data = ''
    resp.on('data', (chunk) => { 
        data += chunk
    })

    resp.on('end', () => { 
        console.log('読み込み完了')
    })

}).on("error", (err) => { 
    console.log("Error: " + err.message)
})

参考:

取得したhtmlから正規表現でタイトルを取得します。

const re_title = RegExp("(?<=<title.*>).*(?=</title>)")
let title = ''
try {
    title = re_title.exec(html)[0]
} catch(error) {

}

参考:

Faviconの取得にはIcon Horseを利用しました。有料プランもありますが、一定のリクエスト数まで無料で利用することができます。

Lambdaのテスト機能を使用して、複数のURLで関数が問題なく動作することを確認します。

Lambda関数のAPI化

Lambdaで作成した関数をAPIとして利用する方法は2つあります。

  1. API Gatewayを設定

  2. Lambdaの関数URLを設定

今回は設定項目が少なく簡易的な後者を選択しました。
Lambda関数の「設定」から「関数 URL」を選択すると、細かな設定項目が表示されます。
CORSの制限なくAPIを実行できるように以下のように設定します。

  • 認証タイプをNONEに

  • オリジン間リソース共有 (CORS) を設定 を選択

  • 許可オリジン:*

  • 許可ヘッダー:content-type

  • 許可メソッド:POST

参考:

完成・ブラッシュアップ

完成したAPIをウィジェットのプラグインウィンドウ(ui.html)から呼び出し、結果をウィジェット本体(code.tsx)に渡します。

let request = new XMLHttpRequest();
let json = JSON.stringify({
  url:params.url
})
request.open('POST', apiUrl)
request.setRequestHeader('Content-Type','application/json');
request.responseType = 'text'
request.onload = async() => {
  let res = JSON.parse(request.response)
  parent.postMessage({ 
    pluginMessage: { 
      type: "responseReceived", 
      success: true,
      title: res.title, 
      favicon: res.favicon
    }
  }, "*");
}
request.onerror = (event) => {
  parent.postMessage({ 
    pluginMessage: { 
      type: "responseReceived", 
      success: false,
      statusCode: request.status,
      message: "Failed to retrieve title."
    }
  }, "*");
}
request.send(json);

こんな感じで初めてのFigma・FigJamウィジェットが完成しました。
実際に作ってみると、他にも様々な課題への対応の必要に気づくもので、
リリース後も何度か修正をしています。

  • Titleタグだけでなく、og:titleやtwitter:titleからもタイトルを取得

  • タイトル内の改行やエスケープ文字の対応

  • 長過ぎるタイトルを省略

  • html容量が大きすぎる場合の処理

不便な点やリクエストなどありましたらお気軽にコメントいただけると嬉しいです!


この記事が気に入ったらサポートをしてみませんか?