> ## 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 小工具

> 只要一個 script 標籤，把我們的按鈕內嵌到你的頁面中，讓你的使用者在你自己的儀表板內連結社群帳號。

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 小工具將我們的連結按鈕放進**你自己的儀表板**。你載入一個指令碼，在版面中每個
網路所屬的位置放一個插槽，我們就會在那裡渲染一顆按鈕，且按鈕已經顯示該帳號是否已連結。你的
使用者點擊它，不必離開你的頁面就能連結帳號——最多只會出現一個彈出視窗，也就是社群網路自己的視窗。

你不需要撰寫任何彈出視窗處理、OAuth 回呼、工作階段更新或針對個別網路的邏輯。當某個網路在他們
那一端有所變動時，修正會在我們部署的那一刻直接送進我們的框架中；你什麼都不用重新部署。

## 你需要哪一種介面

| 介面                                                                                                | 你的使用者看到                                  | 白牌化                                  | 適用情境                                          |
| ------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------ | --------------------------------------------- |
| **小工具——內嵌框架**（[`mount()`](#mount-a-slot)）                                                         | 我們的按鈕內嵌在你自己的版面中；在社群網路自身的彈出視窗之前不會出現任何彈出視窗 | \*\*最強。\*\*你的頁面、你的字型與顏色，你的使用者從不離開    | 你的儀表板為每個網路各有一列，且希望連結就地完成。需要 Max Pack。         |
| **小工具——你自己的按鈕**（[`popup()`](#your-own-button)）                                                    | 你的按鈕，接著是該網路的一個彈出視窗                       | \*\*強。\*\*彈出視窗是我們的，但很短暫，且會繼承你的外觀設定   | 你想使用自己的按鈕與樣式，且不想在版面中放入框架。需要 Max Pack。         |
| **代管連結頁面**（[做法](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication)） | 一個由我們代管、帶有你的 logo、顏色與自訂 CSS 的頁面          | \*\*最弱。\*\*它是我們的頁面，你的使用者需要離開你的頁面來使用它 | 你想要一個可以直接發出的連結，或是透過電子郵件導入。無需建置，也不需要 Max Pack。 |

<div className="my-8">
  <Frame caption="內嵌框架：我們的方塊渲染在你自己的版面中，每個網路一個插槽，或一個插槽容納多個網路。">
    <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="你自己的按鈕：按鈕由你渲染，我們短暫的彈出視窗負責處理社群網路。">
    <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="代管連結頁面：由我們代管、帶有你的 logo 與顏色的頁面，你的使用者離開你的應用程式來使用它。">
    <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>
  兩個小工具列是**同一個整合**，不是兩個。單一 `init` 便同時提供兩者：在你想放我們按鈕的位置掛載框架，
  並在其他任何地方從你自己的按鈕呼叫 `popup()`。它們共用同一個工作階段，並在相同的處理常式上回報。

  [直接模式（Direct Mode）](/docs/multiple-users/connect-direct-mode)是**不使用**我們指令碼的同一個彈出視窗——
  適用於帶有嚴格 Content-Security-Policy 的頁面、伺服器端渲染的頁面，或原生應用程式。在那裡，
  你自行開啟並監看彈出視窗。
</Note>

## 加入指令碼

釘選一個版本及其雜湊值，或追蹤一個不帶雜湊值的頻道。切勿兩者並用——在會變動的 URL 上加
`integrity` 屬性，會在我們下一次發布時失效，因為它所指的檔案已經合法地變更了。

```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` | 不可變，一年 | **是**——請釘選  |
| `/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" }
}
```

每個 bundle 的開頭也有一行註解標明自身的版本，這是告訴我們某個頁面實際執行哪個版本的最快方式：

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

### Content-Security-Policy

如果你的頁面會送出 Content-Security-Policy，需要**兩個**項目，兩者都指向你載入指令碼的主機：

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

清單就這麼多。你**不需要為我們加入 `connect-src` 項目**——我們的框架從它們自己內部呼叫我們的
API，而不是從你的頁面——也**不需要為彈出視窗加入任何項目**，因為彈出視窗是頂層視窗，不受你的
政策管轄。這兩個項目都經過實測：移除 `script-src` 會封鎖指令碼，移除 `frame-src` 會封鎖框架。

## 啟動一個實例

`session` 是唯一必要的選項。它會被呼叫**每個實例一次**，而不是每次掛載一次，且必須回傳你的
後端對 [建立 Link Session](/docs/apis/profiles/create-link-session)（帶 `mode: "connect"`）的回應——
`{ 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，而它回傳的 token 會讓你的使用者登入
  他們的 User Profile——請像對待密碼一樣對待它。
</Warning>

我們會在 token 過期之前再次呼叫 `session`，因此在整天開著的頁面上，小工具仍能持續運作。若某次
呼叫被拒絕或未回傳 token，會再重試兩次——分別在 0.5 秒與 1 秒後——之後我們才放棄並發出 `error`。
所以一次更新最多只需要三次對你端點的呼叫。

| 選項           | 預設值    | 作用                                         |
| ------------ | ------ | ------------------------------------------ |
| `session`    | —      | \*\*必填。\*\*回傳一個 connect 模式的 link session。  |
| `appearance` | 我們的預設值 | 設計 token，套用在每個框架內。請參閱[外觀](#appearance)。    |
| `css`        | 無      | 套用在每個框架內的 CSS 字串。請參閱[自訂 CSS](#custom-css)。 |
| `maxHeight`  | `600`  | 框架可以長到多高，超過後改為在內部捲動。                       |

## 掛載一個插槽

每個插槽一次 `mount`。可以要求一個網路、多個網路，或工作階段允許的所有網路——顆粒度由你決定，
所以插槽可以是現有表格中的一列，也可以是容納全部內容的一個面板。

```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 }`。若目標不符合任何元素，
它會拋出例外——這幾乎總是因為插槽還不存在，所以請在你的標記已進入文件後再掛載。

每次掛載就是一個 iframe。它會向我們回報自己的高度，我們再調整 iframe 的大小以配合，因此你的
版面會隨我們的內容變化而重排；超過 `maxHeight` 後，框架會改為在內部捲動，而不會溢出你的頁面。
**我們支援的最窄插槽為 300px。**

網路鍵名採用 Ayrshare 自己的名稱，別名拼寫也可以使用：`instagram` 與 `instagramapi` 都代表
`instagramApi`，`x` 代表 `twitter`。不是網路的鍵名不會渲染任何方塊。

## 你自己的按鈕

寧可使用自己的按鈕、不想使用我們框架的客戶，可以改為呼叫 `popup`。它執行相同的流程、
使用相同的工作階段，並在相同的處理常式上回報。

<Warning>
  \*\*請在點擊處理常式中直接呼叫它，且之前不能有任何 await。\*\*瀏覽器只在仍在處理使用者點擊的
  期間允許開啟彈出視窗，而這個許可撐不過一個 `await`。反正也沒有什麼需要 await——工作階段在
  `init` 時就已建立。
</Warning>

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

`popup` 回傳 `{ close(), network }`，且**一定**會回傳一個 handle——包括彈出視窗被封鎖時
（此時 `close()` 不做任何事）——因此你的程式碼在呼叫 `close()` 之前永遠不需要做 null 檢查。

每個網路在這裡都可以使用，包括那些在框架自己面板內完成的網路：Facebook 會在彈出視窗中顯示它的
轉交說明畫面，Bluesky 與 X 會顯示它們的憑證表單，而 LinkedIn、Pinterest、YouTube 與
Google Business 會前往該網路再返回。

對三種屬於程式錯誤的情況，它會同步拋出例外——沒有 `network`、已銷毀的實例，或尚未解析完成的
工作階段。被封鎖的彈出視窗**不**在其中：那會發出帶 `reason: "popupBlocked"` 的 `error`，
因為你的使用者並沒有做錯任何事。

同一時間最多只有一個彈出視窗開啟。第二次呼叫會關閉第一個，並在其上回報帶
`reason: "superseded"` 的 `cancelled`。由我們的**框架**開啟的彈出視窗則是另一回事，永遠不會被
碰觸，因此你的按鈕不可能取消正在已掛載插槽內執行的流程。

<Note>
  工作階段是否可以連結某個網路，由**伺服器**決定，而不是指令碼。一個範圍限定在 Bluesky 的
  工作階段被要求連結 LinkedIn 時，會在彈出視窗中看到拒絕畫面，並以 `error` 回報。指令碼只檢查
  是否有指定網路。
</Note>

## React

這個指令碼不依賴任何框架，因此 React 不需要我們提供任何特別支援——但關於它的生命週期，
有四件事值得第一次就做對。

指令碼只載入**一次**，且在你的元件樹之外。在 Next.js 中是根 layout 裡的 `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(() => {
    // Inside the effect, so the ref is attached: mount() throws if its target
    // does not exist yet, which is exactly what happens if you call it during 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` is one of ten events the widget reports. See Events below for
    // the full list, including `state`, which replaces polling.

    // destroy() takes the frames, the message listener and the timers with it.
    // Without this, a route change leaves all three behind.
    return () => {
      stop();
      connect.destroy();
    };
  }, []);

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

<Warning>
  \*\*切勿在 `destroy()` 之後重複使用實例。\*\*已銷毀的實例會一直保持銷毀狀態——在其上呼叫 `popup()`
  會拋出例外，`mount()` 也無法讓它復活。請在下一次 effect 執行時建立新的實例，上面的程式碼正是
  這樣做的。
</Warning>

這個模式有兩個後果，兩者都不是 bug：

* \*\*在開發環境中，你會看到 session 回呼觸發兩次。\*\*React 的 Strict Mode 會以「掛載 → 卸載 → 掛載」
  的方式執行 effect，因此實例會被建立、銷毀、再建立一次。上面的清理程式讓這件事是安全的；
  它在開發環境多花一次對你後端的呼叫，在正式環境則一次也不多。
* \*\*保持 effect 的相依項穩定。\*\*從父層渲染直接傳進 `mount` 的陣列字面值每次都是新值，因此依賴它的
  effect 會在每次渲染時拆掉小工具再重建。請將它 memoize，或像上面那樣保持常數。

## 事件

用 `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
```

十個事件。除了 `ready`，以及那種與網路完全無關的 `error`（見下方
[`error` 有兩種來源](#error-has-two-sources)），每個事件都帶有 `network`。

| 事件          | 觸發時機                    | 額外帶有                              |
| ----------- | ----------------------- | --------------------------------- |
| `ready`     | 框架已掛載並就緒                | —                                 |
| `click`     | 你的使用者點擊了某個網路，**兩個方向都算** | `action`：`"connect"` 或 `"unlink"` |
| `started`   | 連結嘗試正在進行中               | —                                 |
| `selection` | 你的使用者看到選擇器或表單           | `step`                            |
| `success`   | 帳號已連結**且已儲存**           | `displayName`（未知時省略）、`refId`      |
| `unlinked`  | 已連結的帳號被移除，且移除已儲存        | —                                 |
| `error`     | 嘗試失敗                    | `code`、`message`、`reason`         |
| `cancelled` | 你的使用者中途退出               | `reason`                          |
| `closed`    | 這次嘗試使用的彈出視窗已關閉          | —                                 |
| `state`     | 某個網路的帳號狀態，在掛載時與每次變化時    | `state`、`since`                   |

**其中四個是結局**——`success`、`unlinked`、`error` 與 `cancelled`——每次嘗試恰好送達一個。
`closed` 是跟在結局之後的生命週期通知，本身不是結局。

`click` 在任何連結工作開始**之前**就會觸發，因此它也會回報之後被封鎖的彈出視窗或失效的工作階段
所拒絕的點擊。它是你自己的分析所該使用的事件；`started` 才代表一次嘗試真正在執行。

### Reasons

| 事件          | `reason`        | 意義                                   |
| ----------- | --------------- | ------------------------------------ |
| `error`     | `popupBlocked`  | 瀏覽器拒絕開啟彈出視窗                          |
| `cancelled` | `popupClosed`   | 你的使用者親手關閉了視窗                         |
| `cancelled` | `scopesDenied`  | 你的使用者拒絕了網路要求的權限                      |
| `cancelled` | `userCancelled` | 你的使用者中途退出，或你的程式碼呼叫了 `handle.close()` |
| `cancelled` | `superseded`    | 第二次 `popup()` 呼叫取代了這次嘗試              |

### `error` 有兩種來源

其中只有一種是連結失敗，且兩者帶有不同的欄位。

* **連結錯誤**帶有 `network`、`code` 以及它來自的掛載。
* **工作階段錯誤**——我們無法建立或更新你的工作階段——只帶有 `message`，因為當時沒有任何東西
  正在被連結。

請防禦性地解構：在第二種情況下，`code` 與 `network` 都是 `undefined`。

### `state` 讓你不必輪詢

`state` 是資料通道，而不是對某次嘗試的回報。每個框架在掛載時會對每個網路各發出一次，帶有該網路
目前的狀態以及它持有該狀態起始的 `since` 時間戳記；狀態一有變化就再發出一次——**包括源自我們
這一側的變化**，例如 token 失效變成需要重新連結。因此你可以完全由小工具驅動你的 UI，
不需要輪詢任何東西。

其值與
[`GET /profiles` 帶 `include=state`](/docs/apis/profiles/get-profiles) 回傳的是同一組列舉值：
`linked`、`unlinked`、`identityVerificationRequired`、`restricted`、`rateLimited`、`suspended`。

## 取消連結

我們的方塊既能連結也能取消連結。你的使用者點擊已連結的網路、確認後，帳號即被移除：

1. `click` 觸發，帶 `action: "unlink"`。
2. 移除儲存完成後，`unlinked` 觸發。

**失敗**的取消連結會回報 `error`；使用者在確認步驟退出的則回報 `cancelled`。沒有獨立的
「取消連結失敗」事件。

## 外觀

客戶的樣式表無法觸及跨來源框架的內部，因此樣式是以資料的形式傳遞，由我們在框架內套用。將
`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>
  \*\*顏色要成對設定。\*\*只設定背景 token 而不設定旁邊的前景 token，是唯一會讓結果變得難以閱讀的
  方式：單獨把 `--ayr-connect-surface-bg` 設成深色，而我們預設的 `--ayr-connect-surface-fg`
  仍是深海軍藍。沒有任何機制能替你推斷另一半。
</Warning>

完全不帶 `appearance` 時，每個 token 都保持預設值，框架看起來像這樣：

<div className="my-8">
  <Frame caption="預設 token：白色表面、深海軍藍文字、靛藍強調色、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>

傳入幾個 token，同一個框架就會換上你的色盤。這個範例把表面改為淡藍色、將文字與強調色加深以搭配，
並把圓角再修得圓一點：

```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="套用上述 token 之後的同樣三個方塊。">
    <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>

\*\*只有一個色盤，沒有亮／暗預設組。\*\*沒有任何東西依 `prefers-color-scheme` 切換，這是刻意的——
你頁面的主題未必與使用者的作業系統一致，而 media query 會悄悄推翻你選擇的顏色。深色儀表板
要透過提供深色的值來設定主題。

這份 token 清單是我們跨版本維持的受支援契約。

### Token 清單

| Token                              | 預設值                                                       |
| ---------------------------------- | --------------------------------------------------------- |
| `--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`                                                    |

對其 token 而言不是有效 CSS 的值會被**忽略，並在你的主控台留下警告**，而不會被套用。這比聽起來
更重要：`--ayr-connect-spacing` 的無單位 `8` 是一個看似無害的字串，卻會讓所有讀取它的計算失效
並使版面崩壞，而且任何地方都不會出現錯誤。請為長度加上單位。

### 在轉交畫面放上你自己的名稱

在把你的使用者交給社群網路之前，我們會顯示一個短暫的畫面，說明他們是透過誰連結的。有兩個掛勾
可以讓你把它變成自己的，且兩者都跨版本穩定。

`--ayr-connect-partner-name` 設定標籤。它是唯一以**文字**為值的 token，因此必須以 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') }",
});
```

Logo 不是 token。我們把該標誌以定位好、設好尺寸的元素出貨，你透過 `css` 以 `background-image`
填入內容，如上所示——一個能從我們文件內部抓取圖片的 token 不是我們會接受的東西，所以這個請求
來自你撰寫的規則，而不是你傳給我們的值。

<Note>
  `[data-ayr-connect-partner-mark]` 與 `[data-ayr-connect-partner-name]` 是下方自訂 CSS
  注意事項的**例外**：這兩個選擇器屬於契約的一部分，我們會跨版本維持它們。
</Note>

### 自訂 CSS

`css` 接受一個套用在每個框架內的字串，用於 token 無法涵蓋的情況。

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

<Warning>
  \*\*自訂 CSS 不跨版本受支援。\*\*它的選擇器指向我們的內部標記，而內部標記會在版本之間改變——今天
  有效的規則可能在任何更新之後悄悄不再匹配。上方的 token 契約才是我們維持的部分。如果你依賴
  自訂 CSS，請釘選一個版本。
</Warning>

彈出視窗會與框架完全一樣地繼承實例的 `appearance` 與 `css`，因此你的使用者不會在流程中途
突然看到我們中性的預設樣式。

## 上線前值得知道的事

<AccordionGroup>
  <Accordion title="工作階段已過期的彈出視窗回報 cancelled，而不是 error">
    你以 `popup()` 開啟的彈出視窗無法被告知 token 為何被拒絕——要說明原因，它必須信任一個尚未
    驗證的來源，而這是我們的安全模型所不允許的。因此帶著失效 token 的彈出視窗會關閉，並以帶
    `reason: "popupClosed"` 的 `cancelled` 呈現，而不是 `error`。

    實務上這很少見：在靜默更新之前開啟的彈出視窗會繼續運作，因為它在開啟時就已驗證了自己的
    token。如果你看到無法解釋的 `popupClosed` 結果，請檢查你的 `session` 端點是否回傳了
    新鮮的工作階段。
  </Accordion>

  <Accordion title="每個實例一個工作階段，而不是每次掛載一個">
    十四個掛載共用一個 token，只花費你後端一次呼叫，而不是十四次。如果你想要不同範圍的插槽——
    其中一些帶不同的 `allowedSocial`——請以自己的工作階段執行第二個 `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` 來源、自訂 scheme 或 `http://localhost` 的值會回傳 `code: 506`。
  </li>

  <li>
    工作階段上**不要有 `network`**。那個參數會讓工作階段變成
    [直接模式](/docs/multiple-users/connect-direct-mode)，而直接模式工作階段的 URL 不是指令碼
    所預期的。
  </li>
</ul>

上面提到的每個錯誤碼都收錄在
[Link Session 錯誤](/docs/errors/errors-ayrshare#link-session-errors)參考中，包含 API 回傳的確切訊息
以及處理方式。
