npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@movmo/connect-button

v0.4.0

Published

React component that initiates Movmo's OAuth 2.0 PKCE flow, so partners don't hand-roll PKCE.

Readme

@movmo/connect-button

A branded React component that initiates Movmo's OAuth 2.0 PKCE flow against the Movmo auth UI, so partners don't hand-roll PKCE.

Zero auth-related config: every Movmo URL is derived from the single isProd flag (default false → e2e). You never set an auth URL — pass isProd when you go to production, and clientId + redirectUri are the only required props.


Why it's not a drop-in checkout

This package is distinct from @movmo/express-checkout-button.

| Package | Surface | Problem solved | | -------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------- | | @movmo/express-checkout-button | Airline booking page | Embeds a Movmo checkout wallet inside the airline's booking flow | | @movmo/connect-button | Partner's auth / account page | Initiates an OAuth 2.0 PKCE authorization so the partner's backend can act on behalf of a Movmo traveler |

Different surface, different problem — do not substitute one for the other.


What this does NOT do

  • Token exchange runs on your backend. This package hands you a code and codeVerifier; your server uses both alongside client_secret to call POST /v1/oauth/token. The client_secret credential cannot reach a browser.
  • See Authentication for the full server-side token exchange flow.
  • This package never stores tokens, never makes API calls, and has no server-side component.

Install

npm install @movmo/connect-button
# peer deps
npm install react react-dom

React 18+ is required.


Quickstart

1. Render the button on your connect page

import { ConnectMovmoButton } from '@movmo/connect-button';

export function ConnectPage() {
  return (
    <ConnectMovmoButton
      clientId="your-client-id"
      redirectUri="https://yourapp.com/oauth/callback"
      // isProd — add when you go live; without it the button targets Movmo's e2e environment
    />
  );
}

2. Handle the callback

On your /oauth/callback page, call completeMovmoOAuth to validate the redirect and retrieve the codeVerifier for the server-side exchange:

import { useEffect } from 'react';
import { completeMovmoOAuth } from '@movmo/connect-button';

export function CallbackPage() {
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    const state = params.get('state');

    if (!code || !state) return;

    completeMovmoOAuth(code, state)
      .then(({ code, codeVerifier }) => {
        // POST both to your backend — never expose client_secret in the browser
        return fetch('/api/movmo/token', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ code, codeVerifier }),
        });
      })
      .catch((err) => console.error('OAuth completion failed:', err));
  }, []);

  return <p>Completing authorization…</p>;
}

Headless usage

For full control over the button's appearance, use the useMovmoOAuth hook directly:

import { useMovmoOAuth } from '@movmo/connect-button';

export function CustomConnectButton() {
  const { startOAuth, isStarting } = useMovmoOAuth({
    clientId: 'your-client-id',
    redirectUri: 'https://yourapp.com/oauth/callback',
    onError: (err) => console.error('OAuth error:', err),
  });

  return (
    <button onClick={startOAuth} disabled={isStarting}>
      {isStarting ? 'Redirecting…' : 'Connect your Movmo account'}
    </button>
  );
}

MovmoRecognition

MovmoRecognition renders a fixed-position, embeddable recognition surface — an iframe pointed at flights-ui's /recognize route — that tells the airline page whether the current visitor is a recognized Movmo traveler, without ever exposing PII or the traveler's durable identifier to the host page.

It carries no identity data itself:

  • onState fires with 'recognized' | 'anonymous' | 'none' so you know what to render, never who the traveler is.
  • onToken fires with a short-lived (120s), single-use, opaque token. Forward it to your own backend to redeem it — the browser can't read identity out of it.
  • A RECOGNITION_CONNECT gesture from inside the iframe (e.g. "Connect your account") starts the same PKCE redirect as ConnectMovmoButton, using your clientId/redirectUri/scope.

Works standalone on any page, or alongside MovmoCheckoutButton (@movmo/express-checkout-button) — the two are independent surfaces and don't share state.

import { MovmoRecognition } from '@movmo/connect-button';

export function AirlineLayout() {
  return (
    <MovmoRecognition
      clientId="your-client-id"
      redirectUri="https://yourapp.com/oauth/callback"
      airlineName="Aero"
      chipPosition="bottom-right"
      onState={(state) => console.log('recognition state:', state)}
      onToken={(token) => {
        // Forward to your backend to redeem — never decode this in the browser.
        fetch('/api/movmo/recognize', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ token }),
        });
      }}
    />
  );
}

The wrapper starts genuinely empty (0×0, hidden) and only becomes visible once the iframe posts its first size — there's no layout shift or placeholder chrome to account for before that. Repositioning via chipPosition is CSS-only on the wrapper; the iframe itself is never moved or reloaded once mounted.

MovmoRecognition props

| Prop | Type | Default | Description | | -------------- | -------------------------------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | clientId | string | — | Required. OAuth client_id issued by Movmo. | | redirectUri | string | — | Required. Redirect URI used if the traveler triggers a connect from the iframe. | | scope | string[] | ['profile.read', 'preferences.read'] | Scopes requested if the traveler connects. See the scope note. | | isProd | boolean | false | Selects the Movmo environment: the flights-ui origin (flights.movmo.io vs. flights.e2e.movmo.io) and the auth origin (auth.movmo.io vs. auth.e2e.movmo.io). | | chipPosition | 'bottom-left' \| 'bottom-right' \| 'left' \| 'right' | 'bottom-left' | Corner/edge the wrapper is anchored to. CSS-only — never reloads the iframe. | | transparency | 'full' \| 'minimal' | 'full' | Visibility tier for the recognized-state indicator: 'full' keeps a persistent collapsed chip after the first-visit banner; 'minimal' shows the first-visit banner only, then nothing. The anonymous/connect chip always renders regardless (Safari/Firefox need the in-iframe click for Storage Access). The iframe implements the behavior — this just forwards the airline's choice. | | airlineName | string | — | Airline display name, forwarded to the iframe for personalized copy. | | onToken | (token: string) => void | — | Called with the opaque recognition token. Forward it to your backend. | | onState | (state: 'recognized' \| 'anonymous' \| 'none') => void | — | Called whenever the recognition state changes. Carries no identity data. | | extraOrigins | string[] | — | Extra origins to trust in non-prod (e.g. a local flights-ui dev server). Ignored when isProd is true. | | authorizeUrl | string | derived from isProd | Base URL for the Movmo auth UI, used only if a connect gesture fires. Defaults per isProd — the same rule as the flights-ui origin — so it never needs to be set outside local testing. Same semantics as UseMovmoOAuthOptions.authorizeUrl. | | storage | StorageAdapter | sessionStorageAdapter | Storage adapter for the OAuth code_verifier/state, used only if a connect gesture fires. |


Prop reference

ConnectMovmoButton

All UseMovmoOAuthOptions props are also accepted (see below).

| Prop | Type | Default | Description | | ------------- | ------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------- | | clientId | string | — | Required. OAuth client_id issued by Movmo. | | redirectUri | string | — | Required. Redirect URI registered for your client. | | label | string | "Connect" | Gradient-side text. Renders to the left of the white "Movmo" badge — visually reads "<label> Movmo". | | variant | 'primary' \| 'secondary' \| 'ghost' | 'primary' | Visual style. | | className | string | '' | Extra CSS classes merged onto the <button> element. | | disabled | boolean | false | Disable independently of loading state. | | ariaLabel | string | "Connect with Movmo" | Accessible label override. |

UseMovmoOAuthOptions

| Prop | Type | Default | Description | | --------------- | --------------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | clientId | string | — | Required. | | redirectUri | string | — | Required. | | scope | string[] | — | See Forward-compat note. | | isProd | boolean | false | Selects the environment defaults: auth.movmo.io (prod) vs. auth.e2e.movmo.io. Same semantics as MovmoRecognition's isProd. | | authorizeUrl | string | derived from isProd | Base URL for the Movmo auth UI. The auth-ui parses OAuth params from the root URL — there is no /authorize sub-path in the deployed implementation. An explicit value beats the isProd default — only needed for local testing. | | storage | StorageAdapter | sessionStorageAdapter | Where to persist code_verifier and state between redirect legs. | | generateState | () => string | base64url of 16 random bytes | Custom state generator (useful for testing). | | onError | (err: OAuthError) => void | — | Called with a typed error when the flow fails. The error is also re-thrown for caller try/catch. |


Storage adapter

The default sessionStorageAdapter uses window.sessionStorage. For SSR (Next.js) or environments where sessionStorage is unavailable, pass a custom adapter:

import type { StorageAdapter } from '@movmo/connect-button';

// Example: cookie-backed adapter for SSR / cross-tab persistence
const cookieAdapter: StorageAdapter = {
  getItem: (key) => {
    const match = document.cookie.match(new RegExp(`(?:^|; )${key}=([^;]*)`));
    return match ? decodeURIComponent(match[1]) : null;
  },
  setItem: (key, value) => {
    document.cookie = `${key}=${encodeURIComponent(value)}; path=/; SameSite=Strict`;
  },
  removeItem: (key) => {
    document.cookie = `${key}=; path=/; max-age=0`;
  },
};

// Pass it to the button or hook
<ConnectMovmoButton
  clientId="your-client-id"
  redirectUri="https://yourapp.com/oauth/callback"
  storage={cookieAdapter}
/>

The StorageAdapter interface is async-compatible — all methods may return Promise values.


Forward-compat note on scope

The scope[] prop is forwarded as a space-separated scope query param if provided, but Movmo does not enforce per-scope authorization today. Until fine-grained scopes ship, the resulting token carries the user's full RBAC permissions regardless of what you request.

Tracked under MOVMO-346. Accept that until it ships, include scope in your integration now if you want forward-compat, knowing it has no enforcement effect yet.


Versioning

0.x means the public API may shift between minor versions while we land Aviare and the next two partner integrations. Pin to ~0.1.0 if you want patch-only updates:

"dependencies": {
  "@movmo/connect-button": "~0.1.0"
}

License

MIT © Movmo