> ## 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.

# Tự Xây dựng Cửa sổ Bật lên Liên kết

> Chế độ trực tiếp (Direct Mode) — kết nối từng mạng một từ nút của riêng bạn, với một cửa sổ bật lên mà bạn tự mở và tự theo dõi.

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} />

Chế độ trực tiếp (Direct Mode) kết nối **từng mạng xã hội một**, từ một nút trong dashboard của
riêng bạn. Bạn tạo một phiên liên kết cho mạng đó, mở URL mà nó trả về trong một cửa sổ bật lên,
và trang của bạn nghe được điều gì đã xảy ra. Người dùng của bạn không bao giờ thấy một trang liệt
kê mọi mạng, và không bao giờ rời khỏi ứng dụng của bạn lâu hơn thời gian đăng nhập của chính mạng
xã hội.

## Bạn Muốn Bề mặt Nào

Ba hình thức, và hai hình thức đầu là cùng một tích hợp. Trang này là hình thức bạn tự xây dựng.

| Bề mặt | Người dùng của bạn thấy | White-label | Chọn khi |
| - | - | - | - |
| **Widget — khung nhúng** ([mount](/docs/multiple-users/connect-widget#mount-a-slot)) | các nút của chúng tôi hiển thị ngay trong bố cục của bạn; không có cửa sổ bật lên nào cho đến cửa sổ của chính mạng xã hội | **Mạnh nhất.** Trang của bạn, phông chữ và màu sắc của bạn, và người dùng của bạn không bao giờ rời khỏi nó | bạn có một dashboard với một hàng cho mỗi mạng và muốn việc liên kết diễn ra tại chỗ. Max Pack. |
| **Widget — nút của riêng bạn** ([popup](/docs/multiple-users/connect-widget#your-own-button)) | nút của bạn, rồi một cửa sổ bật lên cho mạng xã hội | **Mạnh.** Cửa sổ bật lên là của chúng tôi, nhưng nó xuất hiện ngắn và kế thừa giao diện của bạn | bạn muốn nút và kiểu dáng của riêng mình, và không có khung nào trong bố cục của bạn. Max Pack. |
| **Trang liên kết được lưu trữ** ([hướng dẫn](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication)) | một trang do chúng tôi lưu trữ, mang logo, màu sắc và CSS tùy chỉnh của bạn | **Yếu nhất.** Đó là trang của chúng tôi, và người dùng của bạn rời khỏi trang của bạn để dùng nó | bạn muốn một liên kết duy nhất để gửi đi, hoặc bạn đang onboard qua email. Không phải xây dựng gì, không cần Max Pack. |

<Note>
  **Chế độ trực tiếp là cửa sổ bật lên của hàng thứ hai nhưng không có script của chúng tôi.** Nếu
  trang của bạn có thể tải script, [widget](/docs/multiple-users/connect-widget) làm mọi thứ trên trang
  này giúp bạn — nó tự mở và tự theo dõi cửa sổ bật lên, và nó cũng có thể nhúng các khung. Chế độ
  trực tiếp là thứ bạn cần khi trang của bạn không thể tải script bên thứ ba, hoặc bề mặt là một
  ứng dụng gốc thay vì một trình duyệt.
</Note>

<Frame caption="Nút của bạn, trang của bạn. Cửa sổ bật lên là thứ duy nhất của chúng tôi mà người dùng của bạn thấy, và chỉ trong thời gian mạng xã hội cần.">
  <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="Một dashboard khách hàng với các nút Connect của riêng nó và một cửa sổ bật lên Ayrshare hiển thị màn hình chuyển giao của Facebook" width="2880" height="1317" data-path="images/multiple-users/connect-widget-popup.webp" />
</Frame>

## Những Gì Bạn Xây dựng

Bốn bước. Bước đầu ở trên máy chủ của bạn, các bước còn lại ở trong trang của bạn.

<Steps>
  <Step title="Tạo một phiên cho một mạng">
    Từ backend của bạn, gọi
    [Tạo một Link Session](/docs/apis/profiles/create-link-session) với `mode: "connect"`, `network`,
    và `origin` mà trang của bạn chạy trên đó.

    ```javascript Your backend theme={"system"}
    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": profileKey,
      },
      body: JSON.stringify({
        mode: "connect",
        network: "reddit",
        origin: "https://app.example.com",
      }),
    });

    const { url } = await response.json();
    ```

    Bạn nhận lại một `url` trỏ đến một trang connect cho một mạng duy nhất, và không có `token` —
    token nằm bên trong URL. Hãy coi toàn bộ URL như một mật khẩu: nó đăng nhập người dùng của bạn
    vào User Profile của họ.

    <Warning>
      Hãy tạo phiên trên máy chủ của bạn, không bao giờ trong trình duyệt. Lệnh gọi cần API key
      của bạn.
    </Warning>
  </Step>

  <Step title="Mở nó trong click handler, một cách đồng bộ">
    Cửa sổ bật lên phải được mở bằng `window.open` **ngay trong chính click handler**. Trình duyệt
    chỉ cho phép một cửa sổ bật lên trong khi nó vẫn đang xử lý cú nhấp của người dùng, và quyền
    đó không sống sót qua một `await` — nên việc fetch URL trước rồi mở nó trong callback chắc
    chắn sẽ bị chặn cửa sổ bật lên.

    Hãy fetch URL khi bạn render nút, hoặc khi người dùng của bạn hover lên nó. Đến lúc nhấp, bạn
    đã có sẵn nó.

    ```javascript Your page theme={"system"}
    // `url` đã được fetch từ trước. Không có gì async giữa cú nhấp và window.open.
    button.addEventListener("click", () => {
      const popup = window.open(url, "ayrshare-connect", "width=600,height=800");
      if (!popup) {
        showError("Allow popups for this site to connect an account.");
        return;
      }
      listenForOutcome(popup, url);
    });
    ```
  </Step>

  <Step title="Lắng nghe kết quả">
    Vì bạn đã truyền `origin`, cửa sổ bật lên gửi một sự kiện đến trang của bạn cho mỗi điều xảy
    ra trong nó: `connect:success`, `connect:error`, `connect:cancelled`, và các sự kiện tiến
    trình ở giữa. Đúng một trong ba sự kiện đó đến cho mỗi lần kết nối.

    [Sự kiện Hoàn tất Liên kết](/docs/multiple-users/link-completion-events) có bảng sự kiện đầy đủ và
    một listener để sao chép-dán — `listenForOutcome` ở trên chính là đoạn mã đó. Hai phần của nó
    dễ bị bỏ sót và cả hai đều gây ra bug thật:

    * **Kiểm tra `event.origin`** so với origin của URL bạn đã mở. Bất kỳ trang nào cũng có thể
      gửi một message đến cửa sổ của bạn, và origin là phần duy nhất của một message không thể bị
      giả mạo.
    * **Poll `popup.closed`**, với một khoảng ân hạn ngắn trước khi bạn kết luận bất cứ điều gì.
      Một cửa sổ bật lên mà người dùng của bạn tự tay đóng sẽ không gửi gì cả, và không có khoảng
      ân hạn, một kết nối thành công có thể bị báo cáo là đã hủy.
  </Step>

  <Step title="Xử lý từng kết cục">
    | Kết cục | Nghĩa là gì | Việc cần làm |
    | - | - | - |
    | `connect:success` | tài khoản đã được kết nối **và đã lưu** | làm mới hàng đó. Một [`GET /user`](/docs/apis/user/profile-details) ngay sau đó đã hiển thị nó. |
    | `connect:error` | kết nối thất bại | hiển thị `message`. `code` có mặt khi thất bại có mã được lập danh mục và vắng mặt khi không có. |
    | `connect:cancelled` | người dùng của bạn rút lui, hoặc đóng cửa sổ | giữ nguyên hàng như cũ. `reason` là `popupClosed`, `scopesDenied` hoặc `userCancelled`. |
    | không có gì đến | liên kết đã hết hạn, bị thu hồi hoặc không tồn tại, nên trang không bao giờ biết được nơi gửi sự kiện | poll [Lấy một Link Session](/docs/apis/profiles/get-link-session), endpoint này báo cáo các trạng thái đó một cách xác thực. |
  </Step>
</Steps>

<Tip>
  Thêm `&autoClose=false` vào URL trong khi bạn đang xây dựng. Khi đó cửa sổ bật lên sẽ giữ nguyên
  mở sau mọi kết cục thay vì tự đóng, để bạn có thể đọc những gì nó nói.
</Tip>

## Ghi chú Theo Từng Mạng

Hầu hết các mạng chỉ là một cửa sổ bật lên và không có gì khác: người dùng của bạn nhấp, ủy quyền
tại mạng xã hội, và cửa sổ bật lên đóng lại. Đây là các ngoại lệ đáng biết trước khi bạn xây dựng.

<AccordionGroup>
  <Accordion title="X yêu cầu API key của riêng bạn">
    X trong chế độ trực tiếp dùng thông tin xác thực X Developer App của **bạn**, được cung cấp
    khi bạn tạo phiên dưới dạng các header `X-Twitter-OAuth1-Api-Key` và
    `X-Twitter-OAuth1-Api-Secret` trên [Tạo một Link Session](/docs/apis/profiles/create-link-session).

    Một phiên được tạo cho `twitter` hoặc `x` mà **không có** các header đó sẽ bị từ chối khi cửa
    sổ bật lên mở ra: người dùng của bạn được thông báo kết nối không khả dụng và không được hiển
    thị form nào, và trang của bạn nhận `connect:error` với một `message` và **không có `code`**.
    Điều đó là cố ý. Thông tin xác thực bị thiếu là của bạn, không phải của người dùng của bạn, và
    người dùng cuối không bao giờ được yêu cầu gõ API key của bạn.

    Tương phản với Bluesky, nơi app password là thông tin xác thực của chính người dùng cuối —
    thông tin đó trang connect có thu thập, trong một form bên trong cửa sổ bật lên.
  </Accordion>

  <Accordion title="Facebook hiển thị một nút trước màn hình đăng nhập của Meta">
    `network: "facebook"` hiển thị một nút duy nhất trong cửa sổ bật lên, và màn hình đăng nhập
    của chính Meta mở ra từ cú nhấp đó — Meta yêu cầu việc đăng nhập của họ phải được bắt đầu bằng
    một cú nhấp bên trong trang lưu trữ SDK của họ. Người dùng của bạn nhấp hai lần thay vì một
    lần; không có gì khác biệt.

    Instagram hành xử theo cùng cách khi nó được liên kết **thông qua một Facebook Page** — tức là
    khi phiên mang `instagramLinkMethod: "facebook"`, hoặc khi cài đặt
    [Instagram Login](/docs/multiple-users/manage-user-profiles#instagram-login) của tài khoản bạn chọn
    luồng đó. Với Instagram Login trực tiếp thì không có nút bổ sung nào.
  </Accordion>

  <Accordion title="Bluesky và Telegram hiển thị nội dung trang, không phải một chuyển hướng">
    Cả hai đều không đưa người dùng của bạn đến một màn hình đăng nhập của mạng. Thay vào đó, cửa
    sổ bật lên render nội dung: một form handle và app-password cho Bluesky, và một mã để dùng cho
    Telegram. Các sự kiện kết cục là như nhau trong cả hai trường hợp.

    X không thuộc nhóm này. Với các key của bạn trên phiên, nó hoàn tất mà không hỏi người dùng
    của bạn bất cứ điều gì, và không có chúng thì nó bị từ chối — xem ở trên.
  </Accordion>

  <Accordion title="Telegram hoàn tất ngoài luồng">
    Telegram hiển thị một mã thay vì chuyển hướng đi đâu cả, và kết nối hoàn tất khi người dùng
    của bạn dùng mã đó — sau khi cửa sổ bật lên đã biến mất. Không có sự kiện trình duyệt nào để
    chờ, vì vậy hãy poll [Lấy một Link Session](/docs/apis/profiles/get-link-session) và theo dõi
    `completedNetworks`.
  </Accordion>

  <Accordion title="Facebook Groups không thể được kết nối theo cách này">
    Facebook Groups không phải là một đích liên kết, nên `network: "fbg"` trả về `code: 508` khi
    bạn tạo phiên.

    WhatsApp **có** khả dụng trong chế độ trực tiếp. Nó mở Embedded Signup của Meta trong cửa sổ
    bật lên, và các sự kiện kết cục giống như bất kỳ mạng nào khác.
  </Accordion>
</AccordionGroup>

<h2 id="native-apps">
  Ứng dụng Gốc
</h2>

Một ứng dụng gốc mở cùng `url` đó, trong **trình duyệt hệ thống**, và tìm ra kết quả bằng cách
poll [Lấy một Link Session](/docs/apis/profiles/get-link-session). Đặt `origin` là custom scheme của
bạn (`myapp://connected`) để trang có đường quay lại ứng dụng của bạn; một custom scheme không thể
nhận sự kiện, vì không có cửa sổ trình duyệt nào để gửi chúng tới.

* **iOS** — `ASWebAuthenticationSession`, hoặc `SFSafariViewController`.
* **Android** — Chrome Custom Tabs.

<Warning>
  **Không bao giờ mở URL liên kết trong một webview nhúng** (`WKWebView`, `UIWebView`, `WebView`
  của Android). Các mạng xã hội từ chối xác thực trong đó: Google từ chối đăng nhập với
  `disallowed_useragent`, và Meta chặn hoàn toàn. Người dùng của bạn thấy trang lỗi của chính mạng
  xã hội, không phải của chúng tôi, và không có gì bạn có thể thay đổi ở phía mình khắc phục được.
  Các thành phần trình duyệt hệ thống ở trên tồn tại chính vì lý do này và giữ người dùng ở bên
  trong ứng dụng của bạn.
</Warning>

## Chế độ Trực tiếp Yêu cầu Gì

<ul className="custom-bullets">
  <li>
    **[Max Pack](/docs/additional/maxpack)**. Tạo một phiên chế độ connect mà không có nó trả về
    `code: 504`, bất kể phần còn lại của yêu cầu nói gì.
  </li>

  <li>
    Một **`origin`** trên mỗi phiên. Không có allowlist và không có bước đăng ký — bạn gửi nó theo
    từng lệnh gọi. Bỏ qua nó trả về `code: 505`; một giá trị không phải là một origin `https`, một
    custom scheme, hoặc `http://localhost` trả về `code: 506`.
  </li>

  <li>
    Một **`network`** mà tài khoản của bạn đã bật. Một tên không được nhận diện trả về `code: 508`;
    một tên được nhận diện nhưng tài khoản của bạn chưa bật trả về `code: 509`, điều bạn có thể
    khắc phục trên trang [Social Networks](/docs/multiple-users/manage-user-profiles#set-social-networks-access)
    của mình.
  </li>

  <li>
    **Không** dùng `allowedSocial`. Nó không thể kết hợp với `network` (`code: 507`) — một phiên
    cho một mạng duy nhất tự nó đã là allowlist của chính nó.
  </li>
</ul>

Mọi mục trong số này đều có trong tài liệu tham khảo
[Lỗi Link Session](/docs/errors/errors-ayrshare#link-session-errors), kèm thông báo mà API trả về.

## Các Bước Tiếp theo

<Card title="Sự kiện Hoàn tất Liên kết" icon="tower-broadcast" href="/docs/multiple-users/link-completion-events" horizontal>
  Mọi sự kiện mà cửa sổ bật lên gửi, và listener để nhận chúng.
</Card>

## Liên quan

<Card title="Tạo một Link Session" icon="link" href="/docs/apis/profiles/create-link-session#connect-mode" horizontal>
  Các tham số `mode`, `origin` và `network`, và các hình dạng phản hồi.
</Card>

<Card title="Lấy một Link Session" icon="magnifying-glass" href="/docs/apis/profiles/get-link-session" horizontal>
  Poll để biết việc hoàn tất khi bạn không thể dùng cửa sổ bật lên.
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.