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

# Sự kiện Hoàn tất Liên kết

> Được thông báo khi người dùng của bạn kết nối một tài khoản, thay vì phải polling.

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={false} />

Khi bạn mở một trang liên kết trong một cửa sổ bật lên, trang của chính bạn có thể lắng nghe những
gì xảy ra trong đó: người dùng của bạn đã kết nối Reddit, họ rút lui, kết nối thất bại. Bạn nhận
một sự kiện cho mỗi kết cục, nên UI của bạn cập nhật ngay khoảnh khắc nó xảy ra thay vì theo một
bộ đếm thời gian.

Đặt [`origin`](/docs/apis/profiles/create-link-session) khi bạn tạo liên kết, mở `url` được trả về
trong một cửa sổ bật lên, và lắng nghe. Không cần gì khác, và không có gì thay đổi với các liên
kết được tạo mà không có `origin` — chúng hành xử đúng như trước nay.

<Note>
  **Đang dùng [widget nhúng](/docs/multiple-users/connect-widget)? Điều này đã được nối dây sẵn cho
  bạn.** Script nhận các sự kiện này và chuyển chúng cho `connect.on("success", …)` với tiền tố
  `connect:` được lược bỏ — không cần viết `message` listener, không cần kiểm tra origin, không
  cần poll `popup.closed`. Trang này là giao thức truyền tải bên dưới, và là thứ bạn xây dựng dựa
  trên khi **bạn** sở hữu cửa sổ: chế độ trực tiếp, hoặc một cửa sổ bật lên bạn tự mở.
</Note>

<Note>
  Các sự kiện chỉ được gửi đến đúng `origin` bạn đặt trên liên kết, và chỉ đến cửa sổ đã mở cửa sổ
  bật lên. Dù vậy hãy luôn kiểm tra `event.origin` trong listener của bạn: 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.
</Note>

## Các Sự kiện

Mỗi message được gửi đến trang của bạn là một object có dạng
`{ source: "ayrshare", version: 1, event, ... }`, và mang `network` bất cứ khi nào sự kiện liên
quan đến một mạng. Hai sự kiện thì không: một `connect:closed` từ trang liên kết được lưu trữ, nơi
nó có nghĩa là người dùng của bạn đã nhấn Done chứ không phải là một kết nối đã kết thúc, và
`connect:ready` của widget (xem bên dưới). Các kết cục mà mã của chính bạn tổng hợp — một cửa sổ
bật lên bị chặn, hoặc một cửa sổ người dùng của bạn đã đóng — không đến từ chúng tôi và chỉ mang
những gì bạn đưa cho chúng.

<Note>
  [Widget nhúng](/docs/multiple-users/connect-widget) khác biệt ở một sự kiện. `ready` của nó thông báo
  rằng một slot đã sẵn sàng chứ không nói gì về một mạng, nên nó không mang `network` — trong khi
  `ready` của cửa sổ bật lên nêu tên mạng mà nó được mở cho. Nếu bạn xử lý cả hai bề mặt từ một
  listener, hãy đọc `network` một cách phòng thủ trên `ready`.

  Widget cũng thêm một `reason` cho `cancelled` mà bề mặt này không bao giờ gửi: `superseded`, khi
  một lệnh gọi `popup()` thứ hai thay thế một lần thử vẫn đang diễn ra. Ở đây chỉ có một cửa sổ và
  một kết cục, nên không có gì để thay thế.
</Note>

| Sự kiện | Trường bổ sung | Được gửi khi |
| - | - | - |
| `connect:ready` | | Trang đã tải và liên kết đã được kiểm tra. |
| `connect:started` | | Người dùng của bạn đã được đưa đến mạng xã hội. |
| `connect:selection` | `step` | Một màn hình chọn hoặc một form đang trên màn hình — người dùng của bạn có việc phải làm. |
| `connect:success` | `refId`, `displayName` | Tài khoản đã được kết nối **và đã lưu**. `refId` là User Profile mà nó được kết nối vào. `displayName` là tên tài khoản, và bị lược bỏ khi chúng tôi chưa có nó. |
| `connect:error` | `message`, và `code` khi có | Kết nối thất bại. `code` khớp với [tài liệu tham khảo lỗi](/docs/errors/overview); nó vắng mặt khi thất bại không có mã được lập danh mục, và trên mọi kết cục mà đoạn mã của chính bạn tổng hợp, chẳng hạn một cửa sổ bật lên bị chặn. |
| `connect:cancelled` | `reason` | Người dùng của bạn rút lui. `reason` là `popupClosed`, `scopesDenied`, hoặc `userCancelled`. |
| `connect:closed` | | Cửa sổ bật lên sắp tự đóng. Không mang `network` khi nó đến từ trang liên kết được lưu trữ, nơi nó có nghĩa là người dùng của bạn đã nhấn Done chứ không phải là một kết nối đã kết thúc. |

Đúng một trong `connect:success`, `connect:error` hoặc `connect:cancelled` đến cho mỗi lần kết
nối. `connect:closed` không phải một trong số đó — nó đến sau, để cho bạn biết cửa sổ bật lên đã
đóng một cách có chủ đích.

`connect:success` chỉ được gửi sau khi tài khoản đã được lưu, nên một
[`GET /user`](/docs/apis/user/profile-details) ngay sau nó đã hiển thị tài khoản được kết nối.

<Note>
  Cửa sổ bật lên giữ nguyên mở sau `connect:error` để người dùng của bạn có thể đọc điều gì đã sai.
  Nó tự đóng sau `connect:success` và `connect:cancelled`. Thêm `&autoClose=false` vào URL để giữ
  nó mở trong mọi trường hợp trong khi bạn đang gỡ lỗi.
</Note>

## Lắng nghe

Hai điều đoạn mã này làm mà dễ bị bỏ sót. Nó kiểm tra `event.origin`, và nó theo dõi một cửa sổ
bật lên mà người dùng của bạn tự tay đóng — một cửa sổ đã đóng không thể gửi gì cả, nên polling là
cách duy nhất để nhận ra.

```javascript theme={"system"}
function connectAccount(url) {
  // Suy ra origin từ URL bạn được cấp thay vì hard-code một giá trị:
  // nếu tài khoản của bạn dùng domain liên kết riêng, cửa sổ bật lên chạy trên domain đó.
  const popupOrigin = new URL(url).origin;

  const popup = window.open(url, "ayrshare-connect", "width=600,height=800");
  if (!popup) {
    handleOutcome({ event: "connect:error", message: "The popup was blocked." });
    return;
  }

  // Được khai báo trước khi bất cứ thứ gì có thể gọi `cleanup`: một message đến sớm
  // nếu không sẽ chạm vào `poll` trong temporal dead zone của nó và throw.
  let poll;

  const cleanup = () => {
    clearInterval(poll);
    window.removeEventListener("message", onMessage);
  };

  const onMessage = event => {
    // Cả hai kiểm tra, không chỉ origin: một tab hoặc frame khác trên cùng origin
    // nếu không có thể gửi một message mà handler của bạn sẽ tin.
    if (event.origin !== popupOrigin || event.source !== popup) return;
    const message = event.data;
    if (!message || message.source !== "ayrshare") return;

    if (["connect:success", "connect:error", "connect:cancelled"].includes(message.event)) {
      // `finally`, để handler của chính bạn có throw cũng không thể để poll tiếp tục chạy —
      // sau đó nó sẽ thấy cửa sổ bật lên đã đóng và báo cáo `cancelled` chồng lên
      // kết cục bạn đã có. Cleanup cũng quan trọng sau `connect:error`,
      // khi cửa sổ bật lên giữ nguyên mở để người dùng của bạn có thể đọc nó.
      try {
        handleOutcome(message);
      } finally {
        cleanup();
      }
    }
  };
  window.addEventListener("message", onMessage);

  // Một cửa sổ bật lên bị đóng bằng tay không gửi gì cả, nên hãy theo dõi nó. Chờ một chút
  // trước khi kết luận: cửa sổ bật lên tự đóng ngay sau khi gửi, và message có thể
  // vẫn đang trên đường khi bạn lần đầu thấy cửa sổ biến mất.
  let closedAt = null;
  poll = setInterval(() => {
    if (!popup.closed) return;
    if (closedAt === null) {
      closedAt = Date.now();
      return;
    }
    if (Date.now() - closedAt < 750) return;

    cleanup();
    handleOutcome({ event: "connect:cancelled", reason: "popupClosed" });
  }, 500);
}
```

## Lỗi

`connect:error` mang cùng các mã như phần còn lại của API, nên một mã bạn thấy ở đây có nghĩa đúng
như nó có nghĩa ở mọi nơi khác. Mã dành riêng cho bề mặt này:

| Mã | Ý nghĩa |
| - | - |
| `513` | Liên kết được mở như một cửa sổ connect cho một mạng duy nhất, nhưng không được tạo cho việc đó. Chỉ có thể gặp nếu bạn tự dựng URL đó — `url` mà endpoint này trả về luôn khớp với liên kết nó đã tạo. |

Bất cứ thứ gì khác là thất bại của chính mạng xã hội, được báo cáo với mã mà thất bại đó vốn có —
ví dụ `322` cho một vấn đề ủy quyền Instagram, bao gồm cả một tài khoản vẫn là Personal thay vì
Professional.

### Một liên kết đã chết đến theo cách khác

Một liên kết đã **hết hạn**, **bị thu hồi** hoặc **không tồn tại** bị từ chối trước khi trang có
thể biết nơi gửi sự kiện, nên nó không thể gửi sự kiện nào. Người dùng của bạn thấy lý do và mã
của nó trên màn hình, và trang của bạn nghe được `connect:cancelled` khi họ đóng cửa sổ.

Để phân biệt chúng, hãy poll [Lấy một Link Session](/docs/apis/profiles/get-link-session): nó báo cáo
`expired` và `revoked` một cách xác thực, và trả về `502` cho một liên kết không tồn tại.

## Nếu Bạn Không Muốn Quản lý Cửa sổ Bật lên

Hai lựa chọn, và chỉ lựa chọn thứ hai từ bỏ các sự kiện.

**Để widget sở hữu cửa sổ.** [Widget nhúng](/docs/multiple-users/connect-widget) mở và theo dõi nó giúp
bạn và giao mọi sự kiện ở trên đến các handler của bạn. Bạn vẫn nhận được `success` ngay khoảnh
khắc một tài khoản được lưu; bạn chỉ không phải viết phần đường ống. Các khung của nó cũng có
nghĩa là hầu hết các mạng không bao giờ mở một cửa sổ bật lên nào cho đến màn hình đăng nhập của
chính mạng xã hội.

**Poll thay thế.** Nếu một cửa sổ bật lên thực sự không khả dụng — một ứng dụng render phía máy
chủ, hoặc một ứng dụng mobile mở liên kết trong trình duyệt hệ thống — hãy poll
[Lấy một Link Session](/docs/apis/profiles/get-link-session). Nó báo cáo `completedAt` và
`completedNetworks` ngay khi một tài khoản được lưu, đó là cùng khoảnh khắc `connect:success` lẽ
ra sẽ được gửi. Telegram luôn hoàn tất theo cách này, vì nó hoàn tất ngoài luồng mà không có
callback trình duyệt nào cả.


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