@alter-ai/connect

中文文档
0.17.1发布于 2026/10/28MITTypeScriptESM + CJS零依赖近 30 天下载 1882

JavaScript SDK for Alter Connect - Secure OAuth integration UI

原始使用文档
文档目录30 节
  1. Quick Start
  2. 1. Install
  3. 2. Get a Session Token from the Application Backend
  4. 3. Open the Connect UI
  5. Framework Examples
  6. API Reference
  7. AlterConnect.create(config?)
  8. alterConnect.open(options)
  9. alterConnect.close()
  10. alterConnect.destroy()
  11. alterConnect.on(event, handler)
  12. alterConnect.off(event, handler)
  13. alterConnect.isOpen()
  14. alterConnect.getVersion()
  15. Mobile behavior
  16. Security
  17. Provider selection
  18. Session-controlled behavior
  19. Managed-secret sessions
  20. TypeScript Support
  21. Browser Support
  22. Troubleshooting
  23. Popup Blocked
  24. Inline Widget Shows An Error
  25. Inline Widget Never Loads
  26. Provider Window Did Not Open (Inline Mode)
  27. Session Token Expired
  28. CORS Errors
  29. Support
  30. License

Alter Connect SDK

A lightweight JavaScript SDK for embedding OAuth integrations, and end users' own API keys for managed-secret providers, into an application. The SDK opens Alter Connect in a desktop popup, uses full-page navigation on mobile-classified devices, or renders inline inside an element the application provides, and validates completion data before invoking typed callbacks.

Typed OAuth callbacks | Zod-validated payloads | TypeScript included

Quick Start

1. Install

npm install @alter-ai/connect

The package metadata declares Node.js 20.19 or later for npm tooling. Browser execution does not require Node.js. Or use the UMD bundle via CDN:

<script src="https://cdn.jsdelivr.net/npm/@alter-ai/connect@latest/dist/alter-connect.umd.js"></script>

2. Get a Session Token from the Application Backend

The application backend creates a short-lived session token using the Alter SDK:

// Application backend (Node.js example using @alter-ai/alter-sdk)
import { App, CallerType } from "@alter-ai/alter-sdk";

const alterApp = new App({
  apiKey: process.env.ALTER_API_KEY!,
  caller: "my-backend",
  callerType: CallerType.SERVICE,
});

const session = await alterApp.createConnectSession({
  allowedProviders: ["provider-id"],
  allowedOrigin: "https://app.example.com",
  // Optional. When the user approves access, the Connect UI also lets them set
  // their own usage limits on the connection being created — deny rules,
  // human approval, time windows, request quotas, and operation/parameter
  // rules. They can only narrow what they are granting, never widen it, and
  // they can change or remove the limits later in their wallet.
  //
  // This step is enabled by default. To hide it, pass:
  // allowUserPolicyRules: false,
});

const sessionToken = session.sessionToken;

3. Open the Connect UI

import AlterConnect from '@alter-ai/connect';

// Initialize SDK (no API key needed!)
const alterConnect = AlterConnect.create();

const connectButton = document.querySelector('#connect-button');
if (!(connectButton instanceof HTMLButtonElement)) {
  throw new Error('Connect button is missing');
}
connectButton.disabled = true;

// Prefetch before the user interacts. Browsers may block a popup opened only
// after an awaited network request has detached it from the click gesture.
let sessionToken = null;
fetch('/api/alter/session')
  .then(response => response.json())
  .then(({ session_token }) => {
    sessionToken = session_token;
    connectButton.disabled = false;
  })
  .catch(() => {
    console.error('Unable to prepare the Connect session');
  });

connectButton.addEventListener('click', () => {
  if (!sessionToken) return;
  void alterConnect.open({
    token: sessionToken,
    onSuccess: (grants, completion) => {
      console.log('Connected!', grants);
      grants.forEach(grant => console.log(grant.provider, grant.grant_id));
      completion.failedGrants.forEach(failure => {
        console.warn(failure.providerId, failure.reason, failure.message);
      });
    },
    onError: (error) => {
      console.error('Failed:', error);
    },
    onExit: () => {
      console.log('User closed the window');
    }
  });
});

The package launches the hosted OAuth Connect UI and validates desktop popup results before callbacks receive them.

Framework Examples

React, Vue, Angular, Svelte, and vanilla applications use the same pattern:

  1. Create one reusable AlterConnect instance.
  2. Fetch a session token before enabling the Connect button.
  3. Call open() directly in the click handler so popup activation remains attached to the user gesture.
  4. Destroy the instance when the owning component unmounts.

The CDN bundle exposes the same class as window.AlterConnect. CommonJS consumers read the default export with require('@alter-ai/connect').default.

API Reference

AlterConnect.create(config?)

Creates a new SDK instance.

const alterConnect = AlterConnect.create({
  debug: true  // Enable console logging (default: false)
});
OptionTypeDescriptionDefault
debugbooleanEnable debug loggingfalse
baseURLstringReserved and unsupported. Passing any value throws.—

Note: Visual customization (colors, fonts, logo) is configured via the Developer Portal branding settings. Alter Connect applies the configured branding automatically.


alterConnect.open(options)

Attempts to open Connect. Desktop uses a centered popup window (500×700 px). Mobile-classified devices use full-page navigation, with the limitation documented under Mobile behavior. Supplying container renders Connect inline inside that element on every device instead (see Inline mode).

await alterConnect.open({
  token: 'sess_abc123...',
  onSuccess: (grants, completion) => { /* ... */ },
  onError: (error) => { /* ... */ },
  onExit: () => { /* ... */ },
  onEvent: (eventName, metadata) => { /* ... */ }
});
ParameterTypeRequiredDescription
tokenstringYesShort-lived session token created by the application backend with an Alter SDK: sess_… for OAuth Connect or ms_sess_… for a managed-secret session
containerHTMLElementNoRender Connect inline inside this attached element instead of a popup window. The widget fills the element (350×300 px minimum recommended). The host page's origin must equal the session's allowed origin.
singleProviderbooleanNoInline OAuth mode only (ignored for a managed-secret session): the session's single_provider flag (true when the session names exactly one provider). Called from the user's click, the provider window opens immediately with a plain status line in the container; a blocked window falls back to the inline tile. This is the picker decision, not the consent decision — the session's separate direct flag governs the hosted consent screens and the widget does not consume it.
onSuccess(grants, completion) => voidYesCalled with the legacy grants array and a typed completion object. completion.failedGrants identifies partial failures.
onError(error) => voidNoCalled for failures delivered to the browser SDK, including a terminal provider denial (code: "connect_denied").
onExitfunctionNoCalled when the user closes the desktop popup or presses Cancel inside the inline widget; this does not mark the backend session denied
onEvent(eventName, metadata) => voidNoCalled with connect_opened once a flow launches (metadata.mode names the flow: popup, redirect, or embed; a blocked popup reports popup_blocked through onError and no connect_opened), with provider_declined when the user refuses a provider in a session that can still continue, with provider_error when the provider itself fails in such a session, with embed_unsupported when the inline widget had to fall back to a window, and with embed_continue when an inline managed-secret flow continues in a window the hosted page opened

The promise resolves after the launch attempt, not after user authorization. Missing or invalid token or onSuccess values, including a token that starts with neither sess_ nor ms_sess_, reject the returned promise with code: "invalid_options"; a container that is not an element attached to the document rejects with code: "invalid_container", and a host page with no http(s) origin, or one that is itself inside an iframe, rejects with code: "embed_unsupported_origin". baseURL is not an OpenOptions field: TypeScript rejects it, while plain JavaScript ignores it.

Inline (embedded) mode

await alterConnect.open({
  token: 'sess_abc123...',
  container: document.getElementById('alter-connect'),
  onSuccess: (grants, completion) => { /* ... */ },
  onExit: () => { /* user pressed Cancel inside the widget */ },
});

The hosted Connect UI renders inside container, which the application sizes. Provider sign-in cannot run inside a frame, so the widget opens it in a new window: from the application's own click when a single-provider session's single_provider flag is passed as singleProvider (no tile), otherwise from the user's click on the tile inside the widget; the widget updates when the window finishes; open() itself need not run inside a click handler in this mode. The widget carries its own Cancel control (onExit). Mobile devices use the inline widget too (never the full-page redirect). When the session's single_provider flag is not passed as singleProvider, or the browser blocked the window it tried to open, a session created for exactly one provider shows a single "Continue with" tile naming that provider. Either way the user confirms the connection after the provider returns, unless the session was also minted with direct, which closes on delivery instead. Browsers that refuse the widget's storage inside a frame are handled automatically: the widget offers "Continue in a new window" and completes there through the same callbacks (onEvent receives embed_unsupported).

onSuccess, onError, and onExit run through the event emitter's exception guard. If one throws, the SDK logs the exception and still performs that callback's final state/listener cleanup. onEvent splits: connect_opened is invoked directly, so an exception from it rejects open() after the launch attempt, while the hosted-flow events run through that same guard. The terminal handlers are always registered and always release the flow, so a manual popup close resets isOpen() whether or not onExit was supplied and whether or not it threw.

Completion data (onSuccess):

For compatibility, the first argument remains an array of Grant objects. The second argument makes partial completion explicit:

interface ConnectCompletion {
  grants: Grant[];
  failedGrants: Array<{
    providerId: string;
    reason: string;
    message: string;
  }>;
}

failedGrants is empty for full success. When at least one provider succeeds, it identifies any providers that were connected but not kept: not_selected (the user did not tick it on the Confirm screen), activation_failed (it could not be completed when the user confirmed), or another code when the selected usage limits could not be applied. Unknown reason strings are preserved.

Error Object (onError):

{
  code: string;
  message: string;
  details?: Record<string, unknown>;
  failedGrants?: Array<{
    providerId: string;
    reason: string;
    message: string;
  }>;
}

A completion with no successful grants and one or more failed grants invokes onError instead of reporting bare success. The code is grant_policy_application_failed when any grant was rejected because the selected usage limits could not be applied, and grant_activation_failed when every selected connection instead could not be completed at Confirm (start the connection again).

A provider denial is terminal only when the session has nothing left to try. Desktop delivers code: "connect_denied" to onError and the error event; a mobile flow with returnUrl delivers the same code to the returning page's error listener. If the session still offers a provider the user has not yet declined, already authorized a grant, or still has an account the user has connected and can approve, Connect returns to the picker and leaves backend polling pending so the user can continue — and reports the refusal as a non-terminal provider_declined event (onEvent and the event bus) rather than an error, because onError closes the Connect UI and would strand a user who declined only one of several providers. A provider failure that was not a refusal reports as provider_error on that same channel, so an application must branch on the event name rather than treat every notice as a user decision. Both are popup-flow only: the mobile returnUrl flow has no opening window to notify. Closing the popup through browser chrome remains onExit; it is not a provider denial.


alterConnect.close()

Closes an active popup (or removes the inline widget) if one exists, stops the current flow handler, removes the current per-open callbacks, resets isOpen(), and emits close.

alterConnect.close();

alterConnect.destroy()

Destroys the SDK instance and cleans up resources.

alterConnect.destroy();

alterConnect.on(event, handler)

Register an event listener. Returns an unsubscribe function.

const unsubscribe = alterConnect.on('success', (grants, completion) => {
  console.log('Connected:', grants);
  console.log('Providers not kept:', completion.failedGrants);
});

// Later: unsubscribe();

Emitted bus events: success, error, exit, close, event. The success handler receives (grants, completion); the event handler receives (eventName, metadata) and carries the non-terminal transitions provider_declined, provider_error, embed_unsupported and embed_continue. connect_opened is raised directly by open() and reaches the per-open onEvent callback only.


alterConnect.off(event, handler)

Removes a previously registered event handler. Pass the same handler reference that was supplied to on().

const handleError = error => console.error(error);
alterConnect.on('error', handleError);
alterConnect.off('error', handleError);

alterConnect.isOpen()

Returns the SDK's open-state flag. It becomes true when a launch starts and returns to false when close() runs. A manually closed popup resets it automatically, whether or not onExit was supplied and whether or not that callback threw.

if (alterConnect.isOpen()) {
  console.log('Connect is open');
}

alterConnect.getVersion()

Gets the SDK version.

console.log(alterConnect.getVersion()); // the installed package version

Returns the published package version.

Mobile behavior

Without a container, the SDK chooses the flow using user-agent, touch, viewport, and orientation checks:

DeviceFlowHow It Works
DesktopPopupOpens a centered popup (500x700px) and reports completion through callbacks
Mobile-classified viewport ≤480 pxRedirectNavigates the full page to Connect
Mobile-classified viewport 481–1024 px in portraitRedirectNavigates the full page to Connect
Other devicesPopupUses the desktop callback flow
Any device, container suppliedInlineRenders the hosted UI inside the element; provider sign-in opens in a new window (a new tab on phones) and the widget reports completion through callbacks

Redirect completion: full-page navigation destroys the callbacks and listeners registered by the original page. Set returnUrl on the session and Connect navigates back to it with the outcome, which is delivered to the callbacks registered on the instance constructed by the returning page. Callback parameters that do not carry the per-flow value the SDK minted are ignored. Sessions created without a returnUrl, and returns arriving more than five minutes after the flow started, complete through createConnectSession() plus pollConnectSession() on the application backend.

Closing the desktop popup invokes onExit locally, shortly after the window closes (an outcome the window sent just before closing is delivered instead), but sends no denial transition to the backend. A server-side poll remains pending until its timeout or the session expires.

A provider can likewise render an authorization error on its own origin without redirecting to Alter. The browser SDK cannot observe that page or manufacture a provider error. Server SDK polling reports the no-callback outcome as ConnectTimeoutError; details.reason distinguishes a local polling deadline from a session that was first observed pending and later expired.

Security

  • API keys stay on the backend. The frontend only receives a short-lived session token minted by the application backend.
  • No API keys or provider credentials in the browser. The frontend receives only the short-lived Connect session token.
  • The inline widget is isolated from the host page. It renders on Alter's origin inside a frame the host page cannot read, accepts only the session's allowed origin as its parent, and only hands the outcome to that origin. Provider sign-in always runs in a top-level window.

Provider selection

The hosted UI renders the providers permitted by the session. When that provider is not already connected, a one-provider allowlist still renders one available provider. A verified user with an existing healthy connection can use the consent fast path; requestedGrant, when present, controls the sibling grant requested on that connection. Set switchAccount: true while creating the session to force full provider authorization.

Session-controlled behavior

Provider allowlists, per-provider requested scopes, origin binding, user identity, requested sibling grants, delegated agents, onward-delegation permission, delegation scope constraints, account switching, grant-expiry bounds, and policy authoring are session-creation concerns. They are not browser OpenOptions. Grant-expiry bounds add a duration picker to the hosted confirmation screen.

A recovery session from the server SDKs' createConnectSessionForError() is opened like any other session by passing its sessionToken to open(). This browser package exports none of the server-side exception classes, including ReAuthRequiredError, NoDelegatedGrantError, GrantNotFoundError, CredentialRevokedError, or the headless-flow ConnectFlowError family (ConnectDeniedError, ConnectConfigError, ConnectTimeoutError).

With allowUserPolicyRules enabled, the hosted UI can author narrowing deny, human-approval, time-window, quota, and operation/parameter content rules. Human approval occurs on later API calls; Connect only authors the rule. If selected rules fail to apply for some providers, completion.failedGrants reports the partial failure. If no grant remains, onError receives grant_policy_application_failed.

Managed-secret sessions

A managed-secret Connect session lets the end user enter their own API key for a managed-secret provider, or approve the use of a grant they already hold, and optionally delegates that access to an agent. The application backend creates it with an Alter server SDK and hands its sessionToken (it starts with ms_sess_) to open(), exactly as for OAuth. The widget opens the managed-secret page for an ms_sess_ token and the OAuth page for a sess_ token, and rejects any other token with invalid_options.

// Application backend
const session = await alterApp.createManagedSecretConnectSession({
  provider: "<provider-slug>", // the provider's slug, not its template id
  userToken: "<user-jwt>",
  allowedOrigin: "https://app.example.com",
  agent: "<agent-id-or-name>", // optional: omit for the application's own use
});
// Browser
await alterConnect.open({
  token: session.sessionToken,
  onSuccess: (grants, completion) => {
    if (completion.kind !== "managed_secret") return;
    const { path, credentialKind, preflight } = completion.managedSecret;
    // grants[0].grant_id is the grant to use: the agent's delegation when an
    // agent was named (also grants[0].delegation?.grant_id), else the user's grant.
    console.log(path, credentialKind, preflight?.status, grants[0].grant_id);
  },
  onExit: () => {
    // The user cancelled (popup or inline).
  },
  onError: (error) => {
    // "access_denied": the user cancelled on a redirect return. Any other code:
    // the request ended before it was answered (for example "session_expired"
    // or "key_attempts_exhausted"); create a new session.
  },
});

completion.kind is "managed_secret" for an ms_sess_ token and "oauth" for a sess_ token; the widget sets it from the token, never from the hosted page. completion.managedSecret reports what the user confirmed:

FieldValues
pathkey_entry (the user entered their own key; the application can revoke that grant, never edit it), repair (the user replaced their key in place: a session created with repairGrantId, or one whose user's own key had expired), fast_path (the user approved a grant they already held)
credentialKindend_user (the user's own key) or developer (a key an administrator set)
preflightThe provider's check of a key the user entered: { status, message } with status passed, inconclusive (stored without being verified; message says why) or skipped (the provider cannot be checked). null when no key was entered.

grants holds one grant: provider is the provider's slug and scopes is empty. With an agent, grants[0].delegation.grant_id is the agent's delegation, the grant id the agent passes. failedGrants is always empty. A managed-secret success without a valid outcome rejects with invalid_response.

Popup, redirect and inline (container) modes all work. On a mobile-classified device the page navigates back to the session's returnUrl, and the instance constructed by the returning page delivers the same completion. Inline, the hosted page opens its own window for sign-in and key entry (reported to onEvent as embed_continue) and hands the outcome back through the frame. singleProvider is ignored for a managed-secret session.

TypeScript Support

Full TypeScript definitions included:

import AlterConnect, {
  type AlterConnectConfig,
  type OpenOptions,
  type ConnectCompletion,
  type ConnectFailedGrant,
  type ManagedSecretConnectCompletion,
  type ManagedSecretOutcome,
  type Provider,
  type Grant,
  type AlterError
} from '@alter-ai/connect';

const alterConnect = AlterConnect.create({ debug: true });

await alterConnect.open({
  token: sessionToken,
  onSuccess: (grants: Grant[], completion: ConnectCompletion) => {
    for (const grant of grants) {
      console.log(grant.grant_id);  // Store this in the application database
      console.log(grant.provider);
      console.log(grant.scopes);
    }
    for (const failure of completion.failedGrants) {
      console.warn(failure.providerId, failure.message);
    }
  },
  onError: (error: AlterError) => {
    console.error(error.code, error.message);
  }
});

The package has one runtime dependency: Zod 4 validates complete cross-window result payloads before callback data is consumed.

Browser Support

The CJS, ESM, and UMD bundles target ES2020 and use modern browser APIs including popup or full-page navigation, postMessage, URL parsing, and sessionStorage. Importing the package is side-effect free, but AlterConnect.create() must run in a browser because it checks redirect state during construction. The repository's product E2E suite exercises Chromium; it does not establish a cross-browser version matrix. The package metadata declares Node.js 20.19 or later for npm tooling; CDN browser use does not require Node.js.

Troubleshooting

Popup Blocked

Problem: Browser blocks the OAuth popup

Solution: Ensure alterConnect.open() is called directly from a user interaction (click event):

// May be blocked: the network request runs after the click.
button.addEventListener('click', async () => {
  const token = await fetchToken();
  void alterConnect.open({ token, onSuccess, onError });
});

// Reliable: fetch before enabling the button.
const token = await fetchToken();
button.disabled = false;

button.addEventListener('click', () => {
  void alterConnect.open({ token, onSuccess, onError });
});

Inline Widget Shows An Error

Problem: open({ container }) mounts the widget, it renders an error, and onError fires with code: "embed_origin_mismatch"

Solution: The host page's origin must equal the session's allowed origin (allowedOrigin at session creation, or the origin of the app's Website URL when it is omitted). Pass the exact origin of the page that embeds the widget.

Inline Widget Never Loads

Problem: open({ container }) mounts the widget, it stays empty, and onError fires with code: "embed_load_timeout" after 15 seconds

Solution: The frame delivered no message: the browser refused the hosted page, it failed to load, or the Alter host was unreachable. Confirm the page is served over http(s) at the session's allowed origin and is not itself inside an iframe (a nested host page rejects open() with code: "embed_unsupported_origin" up front), then mint a fresh session and retry.

Provider Window Did Not Open (Inline Mode)

Problem: Clicking a provider inside the inline widget does nothing

Solution: The widget opens the provider sign-in in a new window from that click; if the browser blocks it, the widget shows a message asking to allow pop-ups for the site and the click can be retried.

Session Token Expired

Problem: The hosted Connect page reports that the session expired or is invalid

Solution: Session tokens have a 10-minute default lifetime. Create a new session and open its token. An expiry detected while binding or loading the hosted page is rendered there; it is not a reliable browser onError callback.

CORS Errors

Problem: Session creation was attempted from browser code

Solution: Create sessions on the application backend with an Alter server SDK, then return only the short-lived session token to the browser.

Support

License

MIT

版本与发布记录

@alter-ai/connect 共发布 22 个版本,首次发布于 2026/01/26;过去 12 个月发布了 22 个版本。

版本发布日期距今
0.17.1latest2026/10/2811 天前
0.17.02026/10/2019 天前
0.16.02026/10/1524 天前
0.15.02026/09/221 个月前
0.14.02026/09/221 个月前
0.13.02026/09/181 个月前

关于 @alter-ai/connect 的常见问题

@alter-ai/connect 是什么?

JavaScript SDK for Alter Connect - Secure OAuth integration UI

如何安装 @alter-ai/connect?

在项目目录运行 npm install @alter-ai/connect,或使用 pnpm add / yarn add / bun add。

@alter-ai/connect 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@alter-ai/connect 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@alter-ai/connect 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.19.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@alter-ai/connect 可以商用吗?

@alter-ai/connect 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@alter-ai/connect 的下载量有多少?

根据 npm 官方下载统计,@alter-ai/connect 在 2026/10/06 – 2026/11/04 这 30 天内被下载 1,882 次,日均约 63 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@alter-ai/connect 还在维护吗?

最新版本 0.17.1 发布于 2026/10/28(11 天前);至今共发布 22 个版本,其中过去 12 个月发布了 22 个;首次发布于 2026/01/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。