# UI 拡張機能の仕組み UI 拡張機能システムと、Stripeダッシュボードを拡張する方法についてご紹介します。 Stripe アプリの UI 拡張機能 ([TypeScript](https://www.typescriptlang.org/) と [React](https://reactjs.org/)) を使用すると、Stripe プロダクトに独自の UI をレンダリングできるようになります。これらのツールは、React での開発経験を持つ人であればすぐにご利用いただけると思います。ただし、これらは別のウェブページに埋め込まれる[サンドボックス化された安全な iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox) 内で実行されるため、標準の React ブラウザーアプリケーションとはいくつかの点で異なります。 ## 概要 - [Intro to React (React の概要) (英語)](https://reactjs.org/tutorial/tutorial.html) - [Get started with TypeScript (TypeScript の使用の開始) (英語)](https://www.typescriptlang.org/docs/) - [Stripe の UI コンポーネント](https://docs.stripe.com/stripe-apps/components.md) UI Extensions は TypeScript で記述され、React を使用し、[Stripe の UI ツールキット](https://docs.stripe.com/stripe-apps/components.md)で UI を作成します。その他の React 環境とは異なり、UI Extensions は、任意の HTML をサポートしていません。代わりに、Stripe が提供する UI コンポーネントのみを使用します。UI 拡張機能の構造には、いくつかの主要なディレクトリーとファイルが含まれています。 - `stripe-app.json`: *アプリマニフェスト* (In a Stripe App, the app manifest is a stripe-app.json file in your app's root directory. It defines your app's ID, views, permissions, and other essential properties)。アプリが Stripe と対話する方法を記述します。これには、必要な権限、UI 拡張機能が存在するかどうか、存在する場合はその拡張機能が Stripe の UI のどこに表示されるかなどが含まれます。 - `package.json`: NPM パッケージのメタデータ。UI 拡張機能は通常の [NPM パッケージ](https://docs.npmjs.com/about-packages-and-modules)です。[npm](https://docs.npmjs.com/cli) または [yarn](https://yarnpkg.com/) を使用して依存関係を管理することができます。 - `src`: UI 拡張機能の実際の TypeScript ソースコード。デフォルトでは、CLI は、汎用ビューを `src/views` に配置し、対応するエントリーを `stripe-app.json` に配置します。 UI 拡張機能の開発には、Stripe CLI アプリプラグインを使用します。CLI は、アプリを正しい構造で初期化して、アプリマニフェストを設定し、開発サーバーを実行して、Stripe に送信するためにアプリを適切にバンドルします。 ### UI 拡張機能を開発する - アプリ開発者としてビューを作成します。このビューは、特定の[ビューポート](https://docs.stripe.com/stripe-apps/reference/viewports.md)が画面に表示されるときは常に表示されるように登録された React コンポーネントです。たとえば、あるビューを、ユーザーが請求書の詳細ページを表示するたびに表示されるようにするには、ビューポートの `stripe.dashboard.invoice.detail` に登録します。 - アプリをアップロードする準備ができたら、CLI コマンドにより、コードがバンドルされて Stripe にアップロードされ、Stripe の CDN でホストされます。 - アプリの UI 拡張機能が初期化されると、Stripe は、サンドボックス化された iframe にアプリのコードをダウンロードします。 - ユーザーが特定のビューポートを持つページに移動する場合 (例: `/invoices/inv_1283`): - Stripe は、ビューポートに表示されるコンテキストを使用して、iframe 内の UI 拡張機能のビューを定義します。 - Stripe がビューをダッシュボードに渡すと、ユーザーに表示されます。 - ユーザーが UI 拡張機能を操作 (ボタンのクリックなど) すると、UI 拡張機能の iframe のイベントハンドラがイベントを受信し、ビューを更新します。 ![Stripe アプリの UI 拡張機能のシステム図](https://b.stripecdn.com/docs-statics-srv/assets/extensions-structure.d6d4d0512ef2194eda5c7ffd214e894e.jpg) ## ビューとビューポート UI をアプリのユーザーに表示するには、React ビューを作成してビューポートに登録します。 ビューは、アプリがエクスポートする React コンポーネントです。ビューポートは、ビューが表示される場所を示す識別子です。アプリがアップロードされると、アプリによってエクスポートされたすべてのビューが、関連付けられているビューポートに登録されます。 `stripe apps add view` を実行すると、ビューは自動的にビューポートに登録します。これにより、*アプリマニフェスト* (In a Stripe App, the app manifest is a stripe-app.json file in your app's root directory. It defines your app's ID, views, permissions, and other essential properties)にエントリーが追加されます。 ```json { //... other manifest properties "ui_extension": { "views": [ { "viewport": "stripe.dashboard.invoice.detail", // See all valid values at stripe.com/docs/stripe-apps/reference/viewports "component": "NameOfComponent" // This is provided by you } // ... additional views ] } } ``` ## UI 拡張機能のライフサイクル UI 拡張機能は、非表示のサンドボックス化された iframe で実行され、UI の更新は Stripe ダッシュボードに非同期で送信されます。サンドボックス化された単一の iframe で、複数のビューに同時に対応できます。 サンドボックス化された iframe と、それによって表示されるビューのライフサイクルは次のように機能します。 - ダッシュボードは、ダッシュボードが読み込まれてからユーザーがアプリを開くまでの間に発生する UI 拡張機能の iframe を読み込みます。 - ビューを表示する必要がある場合、ダッシュボードはサンドボックス化された iframe が初期化されるのを待ってから正しいビューをマウントするように指示し、[適切なコンテキスト](https://docs.stripe.com/stripe-apps/reference/extensions-sdk-api.md#props)に渡します。 - ユーザーがビューを閉じると (たとえば、アプリのドロワーを閉じる場合)、ビューのマウントが解除されます。マウントが解除されると、ビューは DOM とサンドボックス化された React ツリーから削除されます。 - サンドボックス化された iframe は、リソースの使用状況に応じて、実行状態のままになるか、シャットダウンされます。ダッシュボードは iframe を終了する前に [useEffect](https://reactjs.org/docs/hooks-effect.html) やその他のクリーンアップハンドラを実行できるように最善を尽くすことのみ保証しています。 ![Stripe Apps の UI 拡張機能のライフサイクル](https://b.stripecdn.com/docs-statics-srv/assets/extensions-lifecycle.0fef55cf88daa063de38151596ad17e5.jpg) Stripe Apps の UI 拡張機能のライフサイクル ## サンドボックス化された iframe の制限事項 UI 拡張機能のコードは一意のサンドボックス環境で実行されるため、Stripe Apps の UI 拡張機能は、フルブラウザーのコンテキストで実行される通常の React アプリが行うすべてを実行できるわけではありません。 ### Stripe Apps と通常の React アプリの主な相違点 1. Stripe Apps は、DOM に直接アクセスできません。ダッシュボードから不可視の別個の DOM を持つ iframe 内で実行されます。 1. ダッシュボードがすべてのデータをシリアライズしてアプリにプロキシー送信します。UI ツールキットのコンポーネントはシリアライズ可能なデータのみを受け付けます。 1. ダッシュボードはすべての「プロパティー」もシリアライズしてアプリにプロキシー送信するため、UI ツールキットのコンポーネントに渡される、またはこれによってトリガーされる関数は、非同期型になります。 ### React と JavaScript の制限事項 以下の制約は、アプリの開発時に React と JavaScript で何を実行できるかに影響します。React ツリーは、Stripe ダッシュボードのホスト環境がデシリアライズして評価するまで DOM にレンダリングされません。アプリの DOM が更新され、ダッシュボードの React のインスタンスがデータ入力を管理します。 #### グローバルな document オブジェクトと window オブジェクトは制限されている UI 拡張コードが実行されている DOM 環境は、[サンドボックス化された iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox)によってロックダウンされます。そのため、[localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)、[indexedDB](https://developer.mozilla.org/en-US/docs/web/api/indexeddb)、[BroadcastChannel](https://developer.mozilla.org/en-US/docs/web/api/broadcastchannel) などの最上位の API は使用できません。[同一生成元ポリシー](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)に依存する DOM API は、サンドボックス化された iframe の生成元が `null` であるため、正常には機能しません。 #### React の ref プロパティがサポートされない UI コンポーネントは、React ツリーがシリアル化され、レンダリングのために Stripe ダッシュボードに渡されるため、React `ref` プロパティをサポートしていません。コンポーネントが最終的にレンダリングされる DOM は、サンドボックス化された iframe で実行されているアプリコードからアクセスできません。 #### アプリでは React のバージョンを管理できない 各 Stripe アプリで生成されるデフォルトの `package.json` ファイルには、`react` の `dependency` エントリーがありません。Stripe アプリの `package.json` ファイルに特定のバージョンの React を追加しても、アプリが表示される React バージョンは管理されません。タイプチェックとユニットテストのみを実行します。Stripe ダッシュボードは、そのバージョンの React (現在のバージョン 17.0.2) を使用して、すべてのアプリを表示します。互換性を確保するため、Stripe から指示された場合にのみ変更してください。 #### インタラクションに非制御コンポーネントを使用する ダッシュボードはすべてのデータ入力をシリアライズしてアプリにプロキシー送信します。この結果、React の[制御コンポーネント](https://reactjs.org/docs/forms.html#controlled-components)の使用時に入力遅延が発生します。この遅延は、ユーザーに認識され、その間にユーザーが入力した文字を上書きする可能性があります。また、先頭でテキストを編集しようとすると、カーソルがテキスト入力の末尾にスキップします。 アプリの遅延を減らすために、非制御の方法でユーザーの入力を使用します。 ```jsx import {useState} from 'react'; import {TextArea} from '@stripe/ui-extension-sdk/ui'; const App = () => { const defaultValue = 'Initial TextArea value'; const [text, setText] = useState(defaultValue); return ( <>