Embedded web components

Add a catalog and connection manager to any web application, without React.

Complete the backend ticket setup first. @arc0/connect is prepared locally and not yet published. Import it from the workspace or a local package artifact. It has no React dependency.

<arc0-connect></arc0-connect>
<arc0-manage></arc0-manage>
import { Arc0Client } from '@arc0/connect';
import '@arc0/connect/elements';
const client = new Arc0Client({
  apiUrl: 'https://api.arc0.ai',
  appUrl: 'https://app.arc0.ai',
  getTicket: async ({ browserChallenge, signal }) => {
    const response = await fetch('/api/arc0-ticket', {
      method: 'POST', signal,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ browserChallenge }),
      // Add your existing application CSRF protection.
    });
    if (!response.ok) throw new Error('Sign in again');
    return response.json();
  },
});
document.querySelector('arc0-connect').client = client;
document.querySelector('arc0-manage').client = client;

Share one client between both components. Dispose it on sign-out or identity change, remove the old components and create a fresh client for the new identity. Alternatively, assign .options to an individual element so it owns its client and disposes it when removed. <arc0-connections> is an alias for <arc0-manage>.

The catalog supports search, category filtering and each available authentication method. The manager displays only the session user's personal connections, with reconnect and an explicit confirmation before disconnect. Revocation first removes runtime access and may remain revoking while provider cleanup finishes. Pagination is available through “Load more connections”.

Script tag

Copy dist/arc0-connect.umd.js from your local package build into your own static assets. The script registers the elements and exposes the same client constructor:

<script src="/vendor/arc0-connect.umd.js"></script>
<script>
  const client = new Arc0Connect.Arc0Client({
    apiUrl: 'https://api.arc0.ai',
    appUrl: 'https://app.arc0.ai',
    getTicket: window.getArc0Ticket,
  });
  document.querySelector('arc0-connect').client = client;
  document.querySelector('arc0-manage').client = client;
</script>

window.getArc0Ticket implements the same authenticated callback shown above. No public CDN is assumed before publication. ESM, CommonJS and AMD are also supported.

Branding and browser policy

Both elements render in a shadow root. The authenticated branding API supplies the name, logo, support link, light/dark palettes and radius. The server determines whether “Powered by Arc0” is displayed under your plan. There is no browser override for that entitlement. A missing branding endpoint during a staggered rollout falls back to Arc0 defaults with attribution visible; a connection-only manager uses the same defaults if branding requires catalog scope. Other failures display an error.

Permit the API origin in CSP connect-src, your package source in script-src, and your branding logo origin in img-src. On browsers supporting constructable CSSStyleSheet.replaceSync() and shadow-root adoptedStyleSheets, the elements use an adopted stylesheet: no inline stylesheet permission is required, including with style-src 'self' or style-src 'none'. Theme tokens use CSSOM property assignments rather than style-attribute text.

Older browsers fall back to an inline <style> in the shadow root. That fallback must be authorized by style-src-elem (or style-src when style-src-elem is absent): either a CSP hash matching the exact stylesheet text in that package release, or an inline-style allowance such as 'unsafe-inline'. A nonce-only policy does not authorize the fallback: the package does not accept or propagate a nonce. Hosts requiring strict style CSP should use a browser supporting adopted stylesheets or build their own UI with the headless client. Never broaden script permissions for this fallback.

Embedded popup URLs include the non-secret ?embedded=1 policy marker. The hosted worker serves these starts, and their API completion pages, with Cross-Origin-Opener-Policy: same-origin. This severs the merchant opener before provider navigation, preventing provider pages from navigating the merchant tab. The client polls the authenticated result for the exact flow and authorization attempt. A merchant may use same-origin-allow-popups, but that policy alone does not protect against a provider navigating its opener. Result delivery does not require a retained opener. The separate hosted catalog keeps its own popup messaging policy; this marker grants no authority or subject selection.

Events and headless use

arc0:result bubbles a public {status, connectionId?} result. arc0:error contains a fixed {code}. Neither includes tickets or tokens. A blocked popup reports popup_blocked; invoke client.connect() directly from the click handler. Cancel connection stops waiting and cleans up the message listener. If a provider severs the opener, exact-flow polling continues until completion or the bounded flow expiry. An older API without flow receipts, an invalid receipt, or expiry yields {status: "unknown"} and “Result unknown. Refresh connections.” in the elements.

The headless client exposes listApps(), listConnections({cursor,limit}), connect({app,authMethod,reconnectConnectionId}), revoke(id), getSession(), getBranding(), subscribe() and dispose(). Follow nextCursor when listing connections. Explicitly confirm revocation in your own UI. The client never infers flow success from a connection-list change. Refresh connections explicitly after an unknown result; a connection made in another tab must not be attributed to this flow.

Use cancelConnect() (or “Cancel connection” in the component) to stop waiting when you close a popup. A provider can make its popup appear closed through COOP; the client keeps polling until completion or expiry rather than treating that as failure.

Browser requirements: native Web Crypto (in a secure context), custom elements, Shadow DOM, AbortController, AbortSignal.any/timeout, URLSearchParams and CSS color-mix. The client reports unsupported_browser when required crypto or abort APIs are missing. This slice verifies Chromium; it does not claim Safari/Firefox browser acceptance. Server expiry remains authoritative; elapsed-time deadlines avoid relying on the device wall clock. Popup polling may obtain a fresh ticket and retry a failed read once; writes are never automatically replayed.

On this page