> ## Documentation Index
> Fetch the complete documentation index at: https://www.ayrshare.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Ayrshare Connect ウィジェット

> スクリプトタグ 1 つと、ページに埋め込まれた当社のボタンで、ユーザーが貴社自身のダッシュボード内からソーシャルアカウントを連携できるようにします。

export const PlansAvailable = ({plans = [], maxPackRequired}) => {
  let displayPlans = plans;
  if (plans && plans.length === 1) {
    const lowerCasePlan = plans[0].toLowerCase();
    if (lowerCasePlan === "business") {
      displayPlans = ["Launch", "Business", "Enterprise"];
    } else if (lowerCasePlan === "premium") {
      displayPlans = ["Premium", "Launch", "Business", "Enterprise"];
    }
  }
  return <Note>
Available on {displayPlans.length === 1 ? "the " : ""}
{displayPlans.join(", ").replace(/\b\w/g, l => l.toUpperCase())}{" "}
{displayPlans.length > 1 ? "plans" : "plan"}.

{maxPackRequired && <span onClick={() => window.open('https://www.ayrshare.com/docs/additional/maxpack', '_self')} className="flex items-center mt-2 cursor-pointer">
 <span className="px-1.5 py-0.5 rounded text-sm" style={{
    backgroundColor: '#C264B6',
    color: 'white',
    fontSize: '12px'
  }}>
   Max Pack required
 </span>
</span>}
</Note>;
};

<PlansAvailable plans={["business"]} maxPackRequired={true} />

Ayrshare Connect ウィジェットは、当社の連携ボタンを**貴社自身のダッシュボードの中**に配置します。
スクリプトを 1 つ読み込み、レイアウト内でネットワークを置きたい場所にスロットを置くと、当社がそこに
ボタンをレンダリングします。ボタンにはアカウントが接続済みかどうかがすでに表示されています。ユーザー
はそれをクリックし、貴社のページを離れることなくアカウントを連携します — 表示されるポップアップは
最大でも 1 つ、ネットワーク自身のものだけです。

ポップアップの処理も、OAuth コールバックも、セッションのリフレッシュも、ネットワークごとのロジックも
書く必要はありません。ネットワーク側で何かが変更されたときは、当社がデプロイした瞬間に修正が当社の
フレーム内に反映されます。貴社が再デプロイするものはありません。

## どのサーフェスを使うべきか

| サーフェス                                                                                                  | ユーザーに見えるもの                                                  | ホワイトラベル度                                    | 選ぶべき場面                                                          |
| ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | ------------------------------------------- | --------------------------------------------------------------- |
| **ウィジェット — 埋め込みフレーム**([`mount()`](#mount-a-slot))                                                      | 貴社レイアウト内にインラインで表示される当社のボタン。ネットワーク自身のポップアップまで、ポップアップは表示されません | **最も強力。** 貴社のページ、貴社のフォントと配色で、ユーザーはページを離れません | ネットワークごとの行を持つダッシュボードがあり、その場で連携を完結させたい場合。Max Pack が必要です。         |
| **ウィジェット — 独自のボタン**([`popup()`](#your-own-button))                                                     | 貴社のボタン、その後ネットワーク用のポップアップが 1 つ                               | **強力。** ポップアップは当社のものですが、短時間で、貴社の外観を継承します    | 独自のボタンとスタイルを使い、レイアウトにフレームを置きたくない場合。Max Pack が必要です。              |
| **ホスト型リンクページ**([使い方](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication)) | 当社がホストするページ。貴社のロゴ、カラー、カスタム CSS が反映されます                      | **最も弱い。** 当社のページであり、ユーザーは貴社のページを離れて使用します    | 配布するリンクを 1 つ用意したい場合や、メールでオンボーディングする場合。構築するものはなく、Max Pack も不要です。 |

<div className="my-8">
  <Frame caption="埋め込みフレーム: 貴社自身のレイアウト内にレンダリングされる当社のタイル。ネットワークごとに 1 スロット、または複数ネットワークで 1 スロット。">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-frames.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=d6c26228a418421772f753f31b0dfc59" alt="A customer dashboard with Ayrshare Connect tiles for Instagram, TikTok and LinkedIn embedded in a card" width="2400" height="1120" data-path="images/multiple-users/connect-widget-frames.webp" />
  </Frame>
</div>

<div className="my-8">
  <Frame caption="独自のボタン: ボタンは貴社がレンダリングし、当社の短時間のポップアップ 1 つがネットワークを処理します。">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-popup.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=b68021d477703d97a5994ee85d299be6" alt="A customer dashboard with its own Connect buttons and an Ayrshare popup showing the Facebook hand-off screen" width="2880" height="1317" data-path="images/multiple-users/connect-widget-popup.webp" />
  </Frame>
</div>

<div className="my-8">
  <Frame caption="ホスト型リンクページ: 当社がホストし、貴社のロゴとカラーが反映されたページ。ユーザーは貴社のアプリを離れて使用します。">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-hosted.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=4d22a992f6e70808fd9ad9f065ed3879" alt="The hosted social linking page showing every available network" width="2336" height="1906" data-path="images/multiple-users/connect-widget-hosted.webp" />
  </Frame>
</div>

<Note>
  ウィジェットの 2 つの行は 2 つの統合ではなく、**1 つの統合**です。1 回の `init` で両方が使えます:
  当社のボタンを置きたい場所にフレームをマウントし、それ以外の場所では独自のボタンから `popup()` を
  呼び出します。両者は 1 つのセッションを共有し、同じハンドラーに結果を報告します。

  [ダイレクトモード(Direct Mode)](/docs/multiple-users/connect-direct-mode)は、当社のスクリプト**なし**の
  同じポップアップです — 厳格な Content-Security-Policy を持つページ、サーバーレンダリングされる
  ページ、ネイティブアプリ向けで、この場合はポップアップを自分で開いて監視します。
</Note>

## スクリプトの追加

バージョンとそのハッシュを固定するか、ハッシュなしでチャンネルを追従するかのどちらかにしてください。
両方は決して行わないでください — 変化する URL に `integrity` 属性を付けると、次のリリースで動作
しなくなります。その URL が指すファイルが正当に変更されるためです。

```html Pinned version theme={"system"}
<script
  src="https://app.ayrshare.com/ayrshare-connect/1.2.0/widget.js"
  integrity="sha384-qQ/of8CnYj26nF9RkQ6qk3D1vvzWesu7mpBK6MFK2D0EmmgRJHfbDmRowFBfltmz"
  crossorigin="anonymous"
></script>
```

```html Tracking v1 theme={"system"}
<script src="https://app.ayrshare.com/ayrshare-connect/v1/widget.js" crossorigin="anonymous"></script>
```

| パス                                      | キャッシュ   | `integrity`       |
| --------------------------------------- | ------- | ----------------- |
| `/ayrshare-connect/<version>/widget.js` | 不変、1 年間 | **あり** — 固定してください |
| `/ayrshare-connect/v1/widget.js`        | 5 分     | なし                |
| `/ayrshare-connect/latest/widget.js`    | 5 分     | なし                |

**推奨するチャンネルは `v1` です。** 修正は取り込みますが、破壊的変更を跨ぐことはありません。
`latest` は定義上メジャーバージョンを跨ぐため、いずれは動作を確認していないバージョンを貴社の
ページに渡すことになります。

各バージョンのハッシュは
[`manifest.json`](https://app.ayrshare.com/ayrshare-connect/manifest.json) で公開されており、各
チャンネルが現在何を配信しているかもここに記載されています:

```json manifest.json theme={"system"}
{
  "versions": {
    "1.2.0": { "integrity": "sha384-qQ/of8CnYj26nF9RkQ6qk3D1vvzWesu7mpBK6MFK2D0EmmgRJHfbDmRowFBfltmz" }
  },
  "latest": "1.2.0",
  "channels": { "v1": "1.2.0", "latest": "1.2.0" }
}
```

各バンドルは自身のバージョンを記したコメントで始まります。ページが実際に何を実行しているかを当社に
伝える最速の方法です:

```js theme={"system"}
/*! ayrshare-connect 1.2.0 */
```

### Content-Security-Policy

ページが Content-Security-Policy を送信している場合、**2 つ**のエントリが必要です。どちらも
スクリプトの読み込み元ホストを指定します:

```
script-src https://app.ayrshare.com;
frame-src  https://app.ayrshare.com;
```

必要なのはこれだけです。当社向けの **`connect-src` エントリは不要**で — 当社のフレームは貴社の
ページからではなくフレーム内部から当社の API に到達します — **ポップアップ用のエントリも不要**です。
ポップアップは貴社のポリシーが管理しないトップレベルのウィンドウだからです。どちらのエントリも実測で
確認済みです: `script-src` を外すとスクリプトがブロックされ、`frame-src` を外すとフレームがブロック
されます。

## インスタンスの開始

必須のオプションは `session` だけです。これはマウントごとではなく**インスタンスごとに 1 回**
呼び出され、`mode: "connect"` を指定した
[Link Session の作成](/docs/apis/profiles/create-link-session)へのバックエンドのレスポンス —
`{ sessionId, token, expiresAt }` — を、返ってきたそのままの形で返す必要があります。

```javascript Your page theme={"system"}
const connect = AyrshareConnect.init({
  session: () => fetch("/my-api/ayrshare-session").then(r => r.json()),
  appearance: { "--ayr-connect-accent": "#0B7A6C" },
  maxHeight: 800,
});
```

```javascript Your backend theme={"system"}
app.get("/my-api/ayrshare-session", async (req, res) => {
  const response = await fetch("https://api.ayrshare.com/api/profiles/link-sessions", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.AYRSHARE_API_KEY}`,
      "Profile-Key": profileKeyFor(req.user),
    },
    body: JSON.stringify({ mode: "connect", origin: "https://app.example.com" }),
  });

  res.json(await response.json());
});
```

ウィジェットのセッションは `network` を**指定しません** — アカウントが許可するすべてのネットワーク
を認可し、どのネットワークを表示するかは貴社のページでマウントごとに決まります。一方で `origin` は
必ず含みます。これこそがフレームをレンダリングさせる唯一の要素です: フレームは自身を埋め込んでいる
ページをその値と照合し、それ以外の場所ではレンダリングを拒否します。

<Warning>
  セッションはサーバー上で作成してください。この呼び出しには API Key が必要で、返されるトークンは
  ユーザーを User Profile にサインインさせます — パスワードと同様に扱ってください。
</Warning>

トークンの期限が切れる前に当社が `session` を再度呼び出すため、終日開きっぱなしのページでも
ウィジェットは動作し続けます。reject した呼び出しや、トークンを返さなかった呼び出しは、0.5 秒後、
次に 1 秒後の計 2 回再試行され、その後に諦めて `error` を発行します。つまり 1 回のリフレッシュで
貴社のエンドポイントへの呼び出しは最大 3 回です。

| オプション        | デフォルト    | 動作                                                         |
| ------------ | -------- | ---------------------------------------------------------- |
| `session`    | —        | **必須。** connect モードのリンクセッションを返します。                         |
| `appearance` | 当社のデフォルト | デザイントークン。すべてのフレーム内に適用されます。[外観](#appearance)を参照してください。      |
| `css`        | なし       | すべてのフレーム内に適用される CSS 文字列。[カスタム CSS](#custom-css) を参照してください。 |
| `maxHeight`  | `600`    | フレームが内部スクロールに切り替わるまでに成長できる高さ。                              |

<h2 id="mount-a-slot">
  スロットのマウント
</h2>

スロットごとに 1 回の `mount` です。1 つのネットワーク、複数、またはセッションが許可するすべてを
要求できます — 粒度は貴社次第なので、スロットは既存のテーブルの 1 行にも、すべてをまとめた 1 つの
パネルにもなります。

```javascript theme={"system"}
connect.mount("#instagram-slot", { network: "instagram" });
connect.mount("#some-slot", { networks: ["facebook", "tiktok", "x"] });
connect.mount("#everything");

const row = connect.mount(document.querySelector("#tall"), { maxHeight: 1200 });
row.unmount();
```

`mount` は CSS セレクタまたは要素を受け取り、`{ unmount, element }` を返します。ターゲットが何にも
一致しない場合は throw します — ほとんどの場合、まだ存在しないスロットが原因なので、マークアップが
ドキュメントに入った後にマウントしてください。

各マウントは 1 つの iframe です。フレームは自身の高さを当社に報告し、当社がそれに合わせてリサイズ
するため、当社のコンテンツの変化に応じて貴社のレイアウトはリフローします。`maxHeight` を超えると、
フレームはページからはみ出す代わりに内部でスクロールします。**サポートする最小のスロット幅は
300px です。**

ネットワークキーは Ayrshare 自身のもので、エイリアス表記も使えます: `instagram` と `instagramapi`
はどちらも `instagramApi` を意味し、`x` は `twitter` を意味します。ネットワークでないキーはタイルを
レンダリングしません。

<h2 id="your-own-button">
  独自のボタン
</h2>

当社のフレームより自社のボタンを使いたいお客様は、代わりに `popup` を呼び出します。同じセッション上
で同じフローが実行され、同じハンドラーに報告されます。

<Warning>
  **クリックハンドラーの中で、何も await せずに直接呼び出してください。** ブラウザは、ユーザーの
  クリックを処理している間しかポップアップを許可せず、その許可は `await` を越えて存続しません。
  そもそも await するものはありません — セッションは `init` の時点で作成済みです。
</Warning>

```javascript theme={"system"}
linkedInButton.addEventListener("click", () => {
  connect.popup({ network: "linkedin" });
});
```

`popup` は `{ close(), network }` を返し、**常に**ハンドルを返します — ポップアップがブロックされた
後(この場合 `close()` は何もしません)を含めてです。そのため、`close()` を呼ぶ前にコードで null
チェックをする必要はありません。

ここではすべてのネットワークが動作します。フレームなら自身のパネル内で完結するものも含まれます:
Facebook はハンドオフの説明をポップアップに表示し、Bluesky と X は認証情報フォームを表示し、
LinkedIn、Pinterest、YouTube、Google Business はネットワークへ出て戻ってきます。

プログラミング上の誤りである 3 つのケース — `network` がない、破棄されたインスタンス、まだ解決して
いないセッション — に対しては同期的に throw します。ブロックされたポップアップはこれに**含まれません**:
その場合は `reason: "popupBlocked"` を伴う `error` が発行されます。ユーザーは何も間違っていないから
です。

一度に開くポップアップは最大 1 つです。2 回目の呼び出しは 1 つ目を閉じ、そちらに
`reason: "superseded"` の `cancelled` を報告します。当社の**フレーム**が開いたポップアップは別物で
あり、決して触れられません。そのため、貴社のボタンがマウント済みスロット内で進行中のフローを
キャンセルすることはありません。

<Note>
  セッションがそのネットワークを連携できるかどうかは、スクリプトではなく**サーバー**が答えます。
  Bluesky にスコープされたセッションで LinkedIn を要求すると、拒否がポップアップ内にレンダリング
  され、`error` として報告されます。スクリプトが確認するのは、ネットワークが指定されたかどうか
  だけです。
</Note>

## React

スクリプトはフレームワークに依存しないため、React に特別な対応は不要です — ただし、ライフサイクル
について最初から正しくしておく価値のあることが 4 つあります。

スクリプトはコンポーネントツリーの外で**一度だけ**読み込んでください。Next.js ならルートレイアウトの
`next/script`、Vite や Create React App なら `index.html` のタグです。コンポーネントごとに読み込む
と、マウントのたびに再実行されます。

```jsx ConnectAccounts.jsx theme={"system"}
import { useEffect, useRef, useState } from "react";

export function ConnectAccounts() {
  const slot = useRef(null);
  const [linked, setLinked] = useState([]);

  useEffect(() => {
    // effect の中で行うことで ref が確実にアタッチされています: mount() はターゲットが
    // まだ存在しないと throw しますが、render 中に呼ぶとまさにそれが起こります。
    const connect = window.AyrshareConnect.init({
      session: () => fetch("/my-api/ayrshare-session").then(r => r.json()),
      appearance: { "--ayr-connect-accent": "#0B7A6C" },
    });

    connect.mount(slot.current, { networks: ["instagram", "tiktok", "x"] });

    const stop = connect.on("success", ({ network }) => {
      setLinked(current => [...current, network]);
    });
    // `success` はウィジェットが報告する 10 のイベントの 1 つです。ポーリングを置き換える
    // `state` を含む完全な一覧は、下記の「イベント」を参照してください。

    // destroy() はフレーム、message リスナー、タイマーをすべて片付けます。
    // これがないと、ルート変更で 3 つとも残ってしまいます。
    return () => {
      stop();
      connect.destroy();
    };
  }, []);

  return <div ref={slot} />;
}
```

<Warning>
  **`destroy()` の後にインスタンスを再利用してはいけません。** 破棄されたインスタンスは破棄された
  ままです — `popup()` は throw し、`mount()` で復活することもありません。上記コードのように、次の
  effect の実行で新しいインスタンスを作成してください。
</Warning>

このパターンには 2 つの帰結があり、どちらもバグではありません:

* **開発時には session コールバックが 2 回発火します。** React の Strict Mode は effect を
  mount → unmount → mount の順に実行するため、インスタンスは作成、破棄、再作成されます。上記の
  クリーンアップがそれを安全にします。コストは開発時のバックエンドへの 1 回の追加呼び出しだけで、
  本番ではゼロです。
* **effect の依存配列を安定させてください。** 親の render から `mount` に直接渡された配列リテラルは
  毎回新しい値になるため、それに依存する effect は render のたびにウィジェットを破棄して再構築
  します。メモ化するか、上記のように定数にしてください。

## イベント

`on` で購読します。`on` は購読解除関数を返します。ハンドラーはイベントペイロードと、その発生元の
マウントを受け取ります。ハンドラーを名前で指定したい場合は `off(name, handler)` が同じ役割を
果たします。

```javascript theme={"system"}
const stop = connect.on("success", ({ network, displayName }, mount) => {
  refreshRow(network, displayName, mount.element);
});
stop();

connect.on("error", ({ network, code, message }) => report(code, message));
connect.destroy(); // every frame, listener and timer
```

イベントは 10 個です。`ready` と、ネットワークとはまったく無関係な種類の `error` を除き、すべてが
`network` を伴います — 下記の [`error` には 2 つの発生源がある](#error-has-two-sources)を参照して
ください。

| イベント        | 発火タイミング                            | 追加で伴うもの                              |
| ----------- | ---------------------------------- | ------------------------------------ |
| `ready`     | フレームがマウントされ、準備ができたとき               | —                                    |
| `click`     | ユーザーがネットワークをクリックしたとき(**どちらの方向でも**) | `action`: `"connect"` または `"unlink"` |
| `started`   | 連携の試行が進行中になったとき                    | —                                    |
| `selection` | ユーザーがピッカーまたはフォームに到達したとき            | `step`                               |
| `success`   | アカウントが接続され、**保存された**とき             | `displayName`(不明な場合は省略)、`refId`      |
| `unlinked`  | 接続済みアカウントが削除され、その削除が保存されたとき        | —                                    |
| `error`     | 試行が失敗したとき                          | `code`、`message`、`reason`            |
| `cancelled` | ユーザーが途中でやめたとき                      | `reason`                             |
| `closed`    | この試行が使ったポップアップが閉じたとき               | —                                    |
| `state`     | ネットワークのアカウント状態(マウント時と変化のたび)        | `state`、`since`                      |

**このうち 4 つが結末です** — `success`、`unlinked`、`error`、`cancelled` で、試行ごとにちょうど
1 つが届きます。`closed` は結末ではなく、結末の後に続くライフサイクル通知です。

`click` は連携処理が始まる**前**に発火するため、ブロックされたポップアップや失効したセッションに
よって拒否されることになるクリックも報告します。貴社独自のアナリティクスにはこのイベントを使って
ください。試行が実際に進行していることを意味するのは `started` です。

### Reason

| イベント        | `reason`        | 意味                                        |
| ----------- | --------------- | ----------------------------------------- |
| `error`     | `popupBlocked`  | ブラウザがポップアップを開くことを拒否した                     |
| `cancelled` | `popupClosed`   | ユーザーがウィンドウを手動で閉じた                         |
| `cancelled` | `scopesDenied`  | ネットワークが求めた権限をユーザーが拒否した                    |
| `cancelled` | `userCancelled` | ユーザーが途中でやめた、またはコードが `handle.close()` を呼んだ |
| `cancelled` | `superseded`    | 2 回目の `popup()` 呼び出しがこの試行を置き換えた           |

<h3 id="error-has-two-sources">
  `error` には 2 つの発生源がある
</h3>

連携の失敗はそのうち 1 つだけで、それぞれ異なるフィールドを持ちます。

* **連携エラー**は `network`、`code`、そして発生元のマウントを伴います。
* **セッションエラー** — セッションを作成またはリフレッシュできなかった場合 — は `message` のみを
  伴います。その時点では何も連携されていなかったからです。

防御的に分割代入してください: 後者では `code` と `network` は `undefined` です。

### `state` でポーリングが不要になる

`state` は、試行の報告ではなくデータチャンネルです。各フレームはマウント時にネットワークごとに 1 つ
発行し、そのネットワークの現在の状態と、それを保持し始めた `since` タイムスタンプを伝えます。さらに
状態が変わるたびにもう 1 つ発行されます — トークンが失効して再連携が必要になった場合など、**当社側で
発生した変化も含みます**。つまり、何もポーリングせずに UI 全体をウィジェットから駆動できます。

値は [`GET /profiles` の `include=state`](/docs/apis/profiles/get-profiles) が返すのと同じ enum です:
`linked`、`unlinked`、`identityVerificationRequired`、`restricted`、`rateLimited`、`suspended`。

## リンク解除

当社のタイルは連携だけでなくリンク解除も行います。ユーザーが接続済みネットワークをクリックし、確認
すると、アカウントが削除されます:

1. `action: "unlink"` を伴って `click` が発火します。
2. 削除が保存されると `unlinked` が発火します。

**失敗した**リンク解除は `error` を報告し、確認ステップでユーザーがやめた場合は `cancelled` を報告
します。リンク解除失敗の専用イベントはありません。

<h2 id="appearance">
  外観
</h2>

お客様のスタイルシートはクロスオリジンフレームの内部には届かないため、スタイルはデータとして渡され、
当社がフレーム内で適用します。`appearance` を CSS カスタムプロパティとして `init` に渡してください。
受け取らなかったものはそれぞれ当社のデフォルトのままになります。

```javascript theme={"system"}
const connect = AyrshareConnect.init({
  session: getSession,
  appearance: {
    "--ayr-connect-font-family": "Inter, system-ui, sans-serif",
    "--ayr-connect-surface-bg": "#111318",
    "--ayr-connect-surface-fg": "#F2F3F7",
    "--ayr-connect-accent": "#0B7A6C",
    "--ayr-connect-accent-fg": "#FFFFFF",
    "--ayr-connect-radius": "12px",
  },
});
```

<Warning>
  **色はペアで設定してください。** 前景トークンを添えずに背景トークンだけを設定することが、読めない
  ものを生み出す唯一の方法です: `--ayr-connect-surface-bg` だけを暗い値に設定しても、当社のデフォルト
  の `--ayr-connect-surface-fg` は依然として濃いネイビーのままです。もう片方を推測してくれるものは
  ありません。
</Warning>

`appearance` をまったく渡さない場合、すべてのトークンはデフォルトを保持し、フレームは次のように
表示されます:

<div className="my-8">
  <Frame caption="デフォルトのトークン: 白のサーフェス、濃いネイビーのテキスト、インディゴのアクセント、8px の角丸。">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-appearance-default.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=62f9ce7e061c8bb58132960b9e270f4a" alt="Three Ayrshare Connect tiles with the default appearance" width="920" height="528" data-path="images/multiple-users/connect-widget-appearance-default.webp" />
  </Frame>
</div>

いくつかのトークンを渡すだけで、同じフレームが貴社のパレットになります。この例ではサーフェスを淡い
ブルーにし、テキストとアクセントをそれに合わせて深くし、角をもう少し丸くしています:

```javascript theme={"system"}
const connect = AyrshareConnect.init({
  session: getSession,
  appearance: {
    "--ayr-connect-surface-bg": "#EFF6FF",
    "--ayr-connect-border-color": "#BFDBFE",
    "--ayr-connect-surface-fg": "#0F2A5F",
    "--ayr-connect-surface-fg-muted": "#4A6A9A",
    "--ayr-connect-accent": "#1D4ED8",
    "--ayr-connect-radius": "14px",
    "--ayr-connect-spacing": "10px",
  },
});
```

<div className="my-8">
  <Frame caption="上記のトークンを適用した後の、同じ 3 つのタイル。">
    <img src="https://mintcdn.com/ayrshare-docs/-2tpx3mFy9vWIObd/images/multiple-users/connect-widget-appearance-themed.webp?fit=max&auto=format&n=-2tpx3mFy9vWIObd&q=85&s=686d1ad900628a6d4c75dc242f1ab38c" alt="Three Ayrshare Connect tiles restyled with pale blue surfaces and a deeper blue accent" width="920" height="574" data-path="images/multiple-users/connect-widget-appearance-themed.webp" />
  </Frame>
</div>

**パレットは 1 つで、ライト/ダークのプリセットはありません。** `prefers-color-scheme` に反応する
ものは意図的にありません — 貴社のページのテーマはユーザーの OS 設定と一致するとは限らず、メディア
クエリは貴社が選んだ色を黙って上書きしてしまうからです。ダークなダッシュボードには、ダークな値を
渡してテーマを適用します。

このトークン一覧は、バージョンを跨いで維持されるサポート対象の契約です。

### トークン一覧

| トークン                               | デフォルト                                                     |
| ---------------------------------- | --------------------------------------------------------- |
| `--ayr-connect-font-family`        | `Inter, "Segoe UI", system-ui, -apple-system, sans-serif` |
| `--ayr-connect-font-size`          | `16px`                                                    |
| `--ayr-connect-font-size-sm`       | `12px`                                                    |
| `--ayr-connect-label-font-weight`  | `600`                                                     |
| `--ayr-connect-status-font-size`   | `12px`                                                    |
| `--ayr-connect-status-font-weight` | `600`                                                     |
| `--ayr-connect-surface-bg`         | `#FFFFFF`                                                 |
| `--ayr-connect-surface-bg-hover`   | `#F7F7F7`                                                 |
| `--ayr-connect-surface-fg`         | `#010629`                                                 |
| `--ayr-connect-surface-fg-muted`   | `#56596F`                                                 |
| `--ayr-connect-border-color`       | `#DDDEE2`                                                 |
| `--ayr-connect-border-width`       | `1px`                                                     |
| `--ayr-connect-radius`             | `8px`                                                     |
| `--ayr-connect-spacing`            | `8px`                                                     |
| `--ayr-connect-accent`             | `#4553EE`                                                 |
| `--ayr-connect-accent-fg`          | `#FFFFFF`                                                 |
| `--ayr-connect-focus-ring-color`   | `#4553EE`                                                 |
| `--ayr-connect-focus-ring-width`   | `2px`                                                     |
| `--ayr-connect-disabled-fg`        | `#6E7185`                                                 |
| `--ayr-connect-status-radius`      | `4px`                                                     |
| `--ayr-connect-status-success-bg`  | `#EBFFF8`                                                 |
| `--ayr-connect-status-success-fg`  | `#237C5C`                                                 |
| `--ayr-connect-status-warning-bg`  | `#FFF7EF`                                                 |
| `--ayr-connect-status-warning-fg`  | `#702E00`                                                 |
| `--ayr-connect-status-critical-bg` | `#FFE5E1`                                                 |
| `--ayr-connect-status-critical-fg` | `#AD1902`                                                 |
| `--ayr-connect-status-info-bg`     | `#ECF9FF`                                                 |
| `--ayr-connect-status-info-fg`     | `#1F6686`                                                 |
| `--ayr-connect-callout-bg`         | `#FFF7EF`                                                 |
| `--ayr-connect-callout-fg`         | `#702E00`                                                 |
| `--ayr-connect-partner-name`       | `""`                                                      |
| `--ayr-connect-icon-size`          | `32px`                                                    |
| `--ayr-connect-avatar-size`        | `40px`                                                    |

そのトークンにとって有効な CSS でない値は、適用されるのではなく**コンソールに警告を出して無視**
されます。これは見た目以上に重要です: `--ayr-connect-spacing` に単位なしの `8` を渡すのはまったく
無害な文字列に見えますが、適用されればそれを読むすべての計算を無効にし、どこにもエラーを出さずに
レイアウトを崩壊させます。長さには単位を付けてください。

### ハンドオフ画面に貴社の名前を表示する

ユーザーをネットワークに引き渡す前に、誰を通じて接続しているのかを示す短い画面を表示します。これを
貴社のものにするためのフックが 2 つあり、どちらもバージョン間で安定しています。

`--ayr-connect-partner-name` はラベルを設定します。値が**テキスト**である唯一のトークンなので、CSS
文字列として引用符で囲む必要があります — 引用符のない値は無効で、何もレンダリングされません:

```javascript theme={"system"}
AyrshareConnect.init({
  session: getSession,
  appearance: { "--ayr-connect-partner-name": "'Acme Social'" },
  css: "[data-ayr-connect-partner-mark]::after { background-image: url('https://cdn.example.com/mark.svg') }",
});
```

ロゴはトークンではありません。当社はマークを位置とサイズが決められた要素として提供し、貴社は上記の
ように `css` の `background-image` で埋めます — 当社のドキュメント内から画像を取得できるトークンは
受け付けられないため、リクエストは貴社が渡した値からではなく、貴社が書いたルールから発生します。

<Note>
  `[data-ayr-connect-partner-mark]` と `[data-ayr-connect-partner-name]` は、下記のカスタム CSS の
  注意書きの**例外**です: この 2 つのセレクタは契約の一部であり、バージョンを跨いで維持されます。
</Note>

<h3 id="custom-css">
  カスタム CSS
</h3>

`css` は、トークンでカバーできないケースのために、すべてのフレーム内に適用される文字列を受け取り
ます。

```javascript theme={"system"}
AyrshareConnect.init({ session: getSession, css: "button { letter-spacing: 0.01em }" });
```

<Warning>
  **カスタム CSS はバージョンを跨いでサポートされません。** そのセレクタは当社の内部マークアップを
  対象としており、これはリリース間で変わります — 今日動くルールが、更新後に静かにマッチしなくなる
  ことがあります。維持されるのは上記のトークン契約です。カスタム CSS に依存する場合はバージョンを
  固定してください。
</Warning>

ポップアップはフレームと同様にインスタンスの `appearance` と `css` を継承するため、フローの途中で
当社の無個性なデフォルトが現れるのをユーザーが目にすることはありません。

## リリース前に知っておくべきこと

<AccordionGroup>
  <Accordion title="セッションが期限切れのポップアップは error ではなく cancelled を報告する">
    `popup()` で開いたポップアップには、トークンが拒否された理由を伝えられません — 伝えるには、まだ
    検証していないオリジンを信頼する必要があり、当社のセキュリティモデルはそれを許可しないためです。
    そのため、失効したトークンを持つポップアップは閉じられ、`error` ではなく
    `reason: "popupClosed"` の `cancelled` として現れます。

    実際にはこれは稀です: サイレントリフレッシュの前に開かれたポップアップは、開いた時点でトークン
    を検証しているため動作し続けます。説明のつかない `popupClosed` が発生する場合は、`session`
    エンドポイントが新しいセッションを返していることを確認してください。
  </Accordion>

  <Accordion title="セッションはマウントごとではなくインスタンスごとに 1 つ">
    14 個のマウントは 1 つのトークンを共有し、バックエンドへの呼び出しは 14 回ではなく 1 回です。
    スコープの異なるスロット — 一部だけ別の `allowedSocial` — が必要な場合は、マウントで絞り込める
    ことを期待するのではなく、独自のセッションを持つ 2 つ目の `init` を実行してください。
  </Accordion>

  <Accordion title="フレームは宣言したオリジン上でのみレンダリングされる">
    すべてのセッションは、貴社のページが動作する `origin` を保持しており、フレームは何かをレンダリング
    する前に、自身を埋め込んでいるページをその値と照合します。別の場所に埋め込まれたフレームは空白の
    ままで、イベントも送信しません。登録するホワイトリストも設定項目もありません — セッション作成時に
    正しい `origin` を送ってください。
  </Accordion>

  <Accordion title="高さは自動的に処理され、イベントではない">
    フレームは高さをスクリプトに報告し、スクリプトがリサイズします。貴社のレイアウトはただリフロー
    します。購読すべき resize イベントはなく、貴社側で測定するものもありません。
  </Accordion>
</AccordionGroup>

## 要件

<ul className="custom-bullets">
  <li>
    **[Max Pack](/docs/additional/maxpack)**。ウィジェットのセッションは connect モードのセッションであり、
    Max Pack なしで作成すると `code: 504` が返されます。Max Pack のないアカウントで connect モードを
    有効にする必要がある場合は、サポートにお問い合わせください。
  </li>

  <li>
    すべてのセッションに **`origin`** — 貴社のページが動作する正確なオリジン。省略すると
    `code: 505` が返されます。`https` オリジン、カスタムスキーム、`http://localhost` のいずれでも
    ない値は `code: 506` を返します。
  </li>

  <li>
    セッションに **`network` を指定しないこと**。このパラメータはセッションを
    [ダイレクトモード](/docs/multiple-users/connect-direct-mode)にするものであり、ダイレクトモードの
    セッションの URL はスクリプトが期待するものではありません。
  </li>
</ul>

上記のすべてのコードは、API が返す正確なメッセージと対処方法とともに
[Link Session エラー](/docs/errors/errors-ayrshare#link-session-errors)リファレンスに記載されています。
