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

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 auth.movmo.io, so partners don't hand-roll PKCE.


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"
    />
  );
}

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>
  );
}

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. | | authorizeUrl | string | 'https://auth.movmo.io/' | 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. Override for staging ('https://auth.e2e.movmo.io/') or 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