@alter-ai/connect
v0.12.0
Published
JavaScript SDK for Alter Connect - Secure OAuth integration UI
Readme
Alter Connect SDK
A lightweight JavaScript SDK for embedding OAuth integrations into an application. The SDK opens Alter Connect in a desktop popup, uses full-page navigation on mobile-classified devices, and validates completion data before invoking typed callbacks.
Typed OAuth callbacks | Zod-validated payloads | TypeScript included
Quick Start
1. Install
npm install @alter-ai/connectThe 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:
- Create one reusable
AlterConnectinstance. - Fetch a session token before enabling the Connect button.
- Call
open()directly in the click handler so popup activation remains attached to the user gesture. - 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)
});| Option | Type | Description | Default |
|--------|------|-------------|---------|
| debug | boolean | Enable debug logging | false |
| baseURL | string | Reserved 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.
await alterConnect.open({
token: 'sess_abc123...',
onSuccess: (grants, completion) => { /* ... */ },
onError: (error) => { /* ... */ },
onExit: () => { /* ... */ },
onEvent: (eventName, metadata) => { /* ... */ }
});| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| token | string | Yes | Short-lived session token created by the application backend with an Alter SDK |
| onSuccess | (grants, completion) => void | Yes | Called with the legacy grants array and a typed completion object. completion.failedGrants identifies partial failures. |
| onError | (error) => void | No | Called for failures delivered to the browser SDK, such as a blocked popup, a malformed result, or a total usage-limit application failure. Hosted-page-only failures require backend polling. |
| onExit | function | No | Called when the user closes the desktop popup; this does not mark the backend session denied |
| onEvent | (eventName, metadata) => void | No | Called once with connect_opened after each launch attempt, including a blocked popup |
The promise resolves after the launch attempt, not after user authorization. Missing or invalid token or onSuccess values reject the returned promise with code: "invalid_options". baseURL is not an OpenOptions field: TypeScript rejects it, while plain JavaScript ignores it.
onSuccess, onError, and onExit run through the event emitter's exception
guard. If one throws, the SDK logs the exception and does not perform that
callback's final state/listener cleanup; callback code should not throw.
onEvent is invoked directly, so an exception from it rejects open() after
the launch attempt.
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 whose grants were revoked because 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 with code: "grant_policy_application_failed" instead of reporting
bare success.
alterConnect.close()
Closes an active popup 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. The success handler receives (grants, completion). Analytics does not emit an event bus event; use the per-open onEvent callback.
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 only when the supplied onExit callback returns normally; if
onExit is omitted or throws, call close() explicitly before opening again.
if (alterConnect.isOpen()) {
console.log('Connect is open');
}alterConnect.getVersion()
Gets the SDK version.
console.log(alterConnect.getVersion()); // "0.2.0" in package 0.11.0getVersion() reads a runtime constant embedded in the bundle. It does not currently match the package metadata version.
Mobile behavior
The SDK chooses the flow using user-agent, touch, viewport, and orientation checks:
| Device | Flow | How It Works | |--------|------|-------------| | Desktop | Popup | Opens a centered popup (500x700px) and reports completion through callbacks | | Mobile-classified viewport ≤480 px | Redirect | Navigates the full page to Connect | | Mobile-classified viewport 481–1024 px in portrait | Redirect | Navigates the full page to Connect | | Other devices | Popup | Uses the desktop callback flow |
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 but sends no denial
transition to the backend. A server-side poll remains pending until its timeout
or the session expires.
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.
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.
TypeScript Support
Full TypeScript definitions included:
import AlterConnect, {
type AlterConnectConfig,
type OpenOptions,
type ConnectCompletion,
type ConnectFailedGrant,
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 });
});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
- Documentation: https://docs.alterauth.com
- Issues: GitHub Issues
- Email: [email protected]
License
MIT
