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

@edge-markets/connect-link

v1.3.0

Published

Browser SDK for EDGE Connect popup authentication

Readme

@edge-markets/connect-link

Browser SDK for EDGE Connect popup authentication.

Features

  • 🔒 Secure by default - PKCE OAuth flow, no client secret in browser
  • 🚀 Simple API - Just new EdgeLink() and link.open()
  • 📱 Works everywhere - Handles popup blockers gracefully
  • 📊 Event tracking - onSuccess, onExit, onEvent callbacks
  • 🎨 Beautiful loading state - Professional branded experience
  • 📝 Full TypeScript - Complete type definitions

Installation

npm install @edge-markets/connect-link
# or
pnpm add @edge-markets/connect-link
# or
yarn add @edge-markets/connect-link

Quick Start

import { EdgeLink } from '@edge-markets/connect-link'

// 1. Create instance (do this once)
const link = new EdgeLink({
  clientId: 'your-client-id',
  environment: 'staging',
  onSuccess: (result) => {
    // Send to your backend for token exchange
    fetch('/api/edge/exchange', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        code: result.code,
        codeVerifier: result.codeVerifier,
      }),
    })
  },
  onExit: (metadata) => {
    if (metadata.reason === 'popup_blocked') {
      alert('Please allow popups for this site')
    }
  },
})

// 2. Open from a click handler
document.getElementById('connect-btn')!.onclick = () => link.open()

⚠️ Important: User Gesture Requirement

link.open() MUST be called directly from a user click handler!

Browsers block popups that aren't triggered by user interaction. Any async work before calling open() will break this.

// ✅ Correct - direct click handler
button.onclick = () => link.open()

// ✅ Correct - immediate call in handler
button.onclick = () => {
  trackClick() // sync ok
  link.open()
}

// ❌ Wrong - async gap breaks user gesture
button.onclick = async () => {
  await someAsyncWork() // breaks it!
  link.open() // BLOCKED
}

// ❌ Wrong - setTimeout breaks user gesture
button.onclick = () => {
  setTimeout(() => link.open(), 100) // BLOCKED
}

Configuration

interface EdgeLinkConfig {
  // Required
  clientId: string              // Your OAuth client ID
  environment: EdgeEnvironment  // 'production' | 'staging' | 'sandbox'
  onSuccess: (result) => void   // Called on successful auth

  // Optional
  onExit?: (metadata) => void   // Called when user exits
  onEvent?: (event) => void     // Called for analytics events
  scopes?: EdgeScope[]          // Scopes to request (default: all)
  linkUrl?: string              // Custom Link URL (dev only)
  redirectUri?: string          // Custom redirect URI (default: window.location.origin + '/oauth/edge/callback')
}

The redirectUri is automatically set to ${window.location.origin}/oauth/edge/callback if not provided.

Callbacks

onSuccess

Called when user successfully authenticates and grants consent:

onSuccess: (result) => {
  // result.code - Authorization code (send to backend)
  // result.codeVerifier - PKCE verifier (send to backend)
  // result.state - State parameter (for validation)

  // IMPORTANT: Exchange tokens on your backend, not here!
  // The backend has your client secret, the browser doesn't.
}

onExit

Called when user exits the flow:

onExit: (metadata) => {
  switch (metadata.reason) {
    case 'user_closed':
      // User closed the popup
      break
    case 'popup_blocked':
      // Popup was blocked - show instructions
      alert('Please allow popups')
      break
    case 'error':
      // An error occurred
      console.error(metadata.error?.message)
      break
  }
}

onEvent

Called for analytics and debugging:

onEvent: (event) => {
  // event.eventName - 'OPEN' | 'CLOSE' | 'HANDOFF' | 'SUCCESS' | 'ERROR'
  // event.timestamp - Unix timestamp
  // event.metadata - Additional context

  analytics.track('edge_link_event', event)
}

Methods

| Method | Description | |--------|-------------| | open(options?) | Opens the Link popup (must be from click handler) | | close() | Closes the popup programmatically | | destroy() | Cleans up resources (call when done) | | isOpen() | Returns true if popup is open |

Scopes

Request only the permissions you need:

import { EdgeLink, EDGE_SCOPES } from '@edge-markets/connect-link'

const link = new EdgeLink({
  clientId: 'your-client-id',
  environment: 'staging',
  scopes: [
    EDGE_SCOPES.USER_READ,    // Read profile
    EDGE_SCOPES.BALANCE_READ, // Read balance
    // EDGE_SCOPES.TRANSFER_WRITE - Only if you need transfers
  ],
  onSuccess: handleSuccess,
})

React Hook

The recommended way to use EdgeLink in React:

import { useEdgeLink } from '@edge-markets/connect-link'

function ConnectButton() {
  const { open, ready, isOpen, error } = useEdgeLink({
    clientId: process.env.NEXT_PUBLIC_EDGE_CLIENT_ID!,
    environment: 'staging',
    onSuccess: async (result) => {
      await fetch('/api/edge/exchange', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(result),
      })
    },
    onExit: (metadata) => {
      if (metadata.reason === 'popup_blocked') {
        alert('Please allow popups')
      }
    },
  })

  if (error) {
    return <div>Error: {error.message}</div>
  }

  return (
    <button onClick={() => open()} disabled={!ready || isOpen}>
      {isOpen ? 'Connecting...' : 'Connect EdgeBoost'}
    </button>
  )
}

Hook Return Values

| Property | Type | Description | |----------|------|-------------| | open | (options?) => void | Opens the Link popup | | ready | boolean | True when EdgeLink is initialized | | isOpen | boolean | True when popup is open | | error | Error \| null | Initialization or runtime error |

Manual React Example

For more control, you can use EdgeLink directly:

import { useEffect, useRef, useCallback } from 'react'
import { EdgeLink } from '@edge-markets/connect-link'

function ConnectButton() {
  const linkRef = useRef<EdgeLink | null>(null)

  useEffect(() => {
    linkRef.current = new EdgeLink({
      clientId: process.env.NEXT_PUBLIC_EDGE_CLIENT_ID!,
      environment: 'staging',
      onSuccess: async (result) => {
        await fetch('/api/edge/exchange', {
          method: 'POST',
          body: JSON.stringify(result),
        })
      },
      onExit: (metadata) => {
        if (metadata.reason === 'popup_blocked') {
          alert('Please allow popups')
        }
      },
    })

    return () => linkRef.current?.destroy()
  }, [])

  const handleClick = useCallback(() => {
    linkRef.current?.open()
  }, [])

  return (
    <button onClick={handleClick}>
      Connect EdgeBoost
    </button>
  )
}

How It Works

  1. User clicks buttonlink.open() is called
  2. Popup opens immediately → Shows branded loading state
  3. PKCE generated → Secure OAuth without client secret
  4. Popup navigates to Link page → User logs in & grants consent
  5. Code returned via postMessage → Secure cross-origin communication
  6. onSuccess called → You send code to backend for token exchange
┌─────────────┐     ┌─────────────────┐     ┌──────────────┐
│   Your App  │     │  EdgeLink Popup │     │ Your Backend │
└──────┬──────┘     └────────┬────────┘     └──────┬───────┘
       │                     │                     │
       │ link.open()         │                     │
       ├────────────────────►│                     │
       │                     │                     │
       │           (user logs in, grants consent)  │
       │                     │                     │
       │   postMessage(code) │                     │
       │◄────────────────────┤                     │
       │                     │                     │
       │ onSuccess(result)   │                     │
       ├─────────────────────┼────────────────────►│
       │                     │   POST /api/exchange │
       │                     │                     │
       │                     │   tokens             │
       │◄────────────────────┼─────────────────────┤
       │                     │                     │

Security

  • PKCE - Prevents authorization code interception
  • State parameter - Prevents CSRF attacks
  • Origin validation - postMessage only accepted from expected origin
  • No client secret in browser - Token exchange happens on your backend

Related Packages

  • @edge-markets/connect - Core types and utilities
  • @edge-markets/connect-node - Server SDK for token exchange

License

MIT