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

@uniweb/frame-bridge

v0.1.0

Published

Promise-based iframe communication library with automatic dimension reporting, URL sync, and JSON-LD injection

Downloads

312

Readme

Frame Bridge

Seamless URL synchronization and communication for iframe-embedded applications.

The Problem

When you embed an application in an iframe (a search system, documentation site, store, etc.), the parent page URL becomes static. This breaks:

  • Deep linking - Users can't link to specific pages within the iframe
  • SEO - Search engines can't index individual pages in your embedded app
  • Bookmarking - Users can't bookmark specific states
  • Browser navigation - Back/forward buttons don't work as expected

The Solution

Frame Bridge automatically synchronizes URLs between parent and child frames, passes parameters bidirectionally, and handles iframe dimensions and messaging - making your embedded application behave like a native part of the parent site.

<!-- Parent URL: https://example.com/experts?term=biology -->
<!-- Iframe navigates to: /search/results -->
<!-- Parent URL updates to: https://example.com/experts?route=/search/results&term=biology -->

Users can now bookmark, share, and search engines can index every page in your embedded app.

Features

  • URL Synchronization - Keep parent URL in sync with iframe routes
  • Parameter Passing - Pass query parameters from parent to child
  • Automatic Resizing - ResizeObserver-based height updates
  • Promise-based Messaging - Clean async/await API for parent-child communication
  • JSON-LD Injection - SEO-friendly structured data from iframes
  • Origin Validation - Secure cross-origin communication
  • Multiple Iframes - Support for multiple child frames
  • Zero Config - Works out of the box with sensible defaults

Installation

Coming soon (not yet published to npm)

npm install @uniweb/frame-bridge

Or use via CDN:

<!-- Parent -->
<script src="https://cdn.jsdelivr.net/npm/@uniweb/[email protected]/dist/auto/parent.min.js"></script>

<!-- Child -->
<script src="https://cdn.jsdelivr.net/npm/@uniweb/[email protected]/dist/auto/child.min.js"></script>

Quick Start

Zero Config (Auto-init)

Parent HTML:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.../parent.min.js"></script>
  </head>
  <body>
    <!-- Embed your application -->
    <iframe src="https://app.example.com" data-messenger-id="main"></iframe>

    <script>
      // URLs are automatically synchronized
      window.FrameBridge.on("routeChange", (id, { path, title }) => {
        console.log("Iframe navigated to:", path);
        // Parent URL automatically updated to: ?route=/new-path
      });
    </script>
  </body>
</html>

Child HTML (in iframe):

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.../child.min.js"></script>
  </head>
  <body>
    <h1>My Embedded Application</h1>

    <script>
      // Access parameters passed from parent URL
      window.FrameBridge.on("parentReady", ({ params }) => {
        console.log("Received params:", params);
        // If parent URL is: ?term=biology&filter=active
        // params = { term: 'biology', filter: 'active' }
      });

      // Navigate and parent URL updates automatically
      window.location.href = "/search/results";
    </script>
  </body>
</html>

That's it! URLs are synchronized, parameters are passed, and the iframe resizes automatically.

NPM Usage with Custom Configuration

Parent Setup

import { ParentMessenger } from "@uniweb/frame-bridge/parent";

const messenger = new ParentMessenger({
  // Security
  allowedOrigins: ["https://app.example.com"],

  // URL sync config (all default to true)
  urlSync: true,
  urlParamKey: "route", // ?route=/users/123
  preserveOtherParams: true, // Keep other query params

  // Pass config to children
  analyticsId: "GA-XXXXX",
  syncParams: ["term", "filter"], // Only sync these params to iframe

  // Features
  autoResize: true, // Automatically resize iframe to content
  jsonLD: true, // Inject JSON-LD from iframe into parent

  // Callbacks
  onIframeReady: (id, { route, dimensions }) => {
    console.log(`Iframe ready at ${route.path}`);
  },

  onRouteChange: (id, { path, title }) => {
    console.log(`Iframe navigated to ${path}`);
    // Parent URL automatically updated
  },

  onDimensionUpdate: (id, { height }) => {
    console.log(`Iframe height: ${height}px`);
    // Iframe automatically resized
  },
});

// Programmatic control
messenger.sendToChild("iframe-id", "navigate", { path: "/users/123" });
messenger.sendToAllChildren("setTheme", { theme: "dark" });

Child Setup

import { ChildMessenger } from "@uniweb/frame-bridge/child";

const messenger = new ChildMessenger({
  // Security
  allowedOrigins: ["https://parent.example.com"],

  // Features
  dimensionReporting: true,
  routeReporting: true,

  // Custom route getter (for SPAs)
  getRoute: () => ({
    path: window.location.pathname,
    title: document.title,
  }),

  // Callbacks
  onParentReady: ({ params, config }) => {
    console.log("Received params:", params);
    console.log("Analytics ID:", config.analyticsId);

    // Use params to initialize your app
    if (params.term) {
      performSearch(params.term);
    }
  },

  onNavigate: ({ path }) => {
    // Parent requested navigation
    window.history.pushState({}, "", path);
  },

  onParamUpdate: (params) => {
    // Parent URL params changed
    console.log("Updated params:", params);
  },
});

// Update route manually (e.g., after navigation)
messenger.updateRoute("/search/results", "Search Results");

// Update JSON-LD for SEO
messenger.updateJSONLD({
  "@context": "https://schema.org",
  "@type": "SearchResultsPage",
  name: "Expert Search Results",
});

// Get current params
const searchTerm = messenger.getParam("term");
const allParams = messenger.getAllParams();

React Integration

Child with React Router:

import { useEffect } from "react";
import { useLocation, useSearchParams } from "react-router-dom";
import { ChildMessenger } from "@uniweb/frame-bridge/child";

const messenger = new ChildMessenger({
  getRoute: () => ({
    path: window.location.pathname,
    title: document.title,
  }),
  onParentReady: ({ params }) => {
    // Initialize app with params from parent URL
    console.log("Parent params:", params);
  },
  onNavigate: ({ path }) => {
    window.history.pushState({}, "", path);
  },
});

// Hook to report route changes
export function useRouteReporter() {
  const location = useLocation();

  useEffect(() => {
    messenger.updateRoute(location.pathname, document.title);
  }, [location]);
}

// In your App component
function App() {
  useRouteReporter();
  return <Routes>{/* Your routes */}</Routes>;
}

Parent with React:

import { useEffect, useState } from "react";
import { ParentMessenger } from "@uniweb/frame-bridge/parent";

function EmbeddedApp() {
  const [iframeRoute, setIframeRoute] = useState("/");
  const [messenger] = useState(
    () =>
      new ParentMessenger({
        onRouteChange: (id, { path }) => {
          setIframeRoute(path);
        },
      })
  );

  useEffect(() => {
    return () => messenger.destroy();
  }, [messenger]);

  return (
    <div>
      <iframe src="https://app.example.com" />
    </div>
  );
}

Use Cases

Embedded Search System

Parent URL: example.com/experts?term=biology
→ Iframe searches and shows results
→ User clicks an expert
→ Parent URL updates to: example.com/experts?route=/profile/john-doe&term=biology
→ URL is bookmarkable and indexable

Embedded Documentation

Parent URL: example.com/docs
→ User navigates through docs
→ Parent URL updates to: example.com/docs?route=/guides/getting-started
→ Deep links work, search engines index individual pages

Embedded Store

Parent URL: example.com/shop
→ User browses categories and products
→ Parent URL updates to: example.com/shop?route=/products/widget-123
→ Products are individually linkable and indexable

Advanced Features

Accurate Dimension Reporting

The library automatically accounts for body margin, padding, and border:

// Child automatically includes all body spacing
const dimensions = {
  width: 1200,
  height: 1840, // = content (1800px) + margin (20px) + padding (20px)
};

Custom Actions

Define bidirectional custom actions:

// Parent
const messenger = new ParentMessenger({
  actionHandlers: {
    userSelected: (iframeId, { userId }) => {
      console.log(`User ${userId} selected`);
      return { success: true };
    },
  },
});

// Child
const messenger = new ChildMessenger({
  actionHandlers: {
    loadUser: ({ userId }) => {
      return { user: { id: userId, name: "John" } };
    },
  },
});

// Usage with promises
const result = await messenger.sendToParent("userSelected", { userId: 123 });

Multiple Iframes

const messenger = new ParentMessenger({
  onIframeReady: (id, info) => {
    console.log("Iframe registered:", id);
  },
});

// Send to specific iframe
messenger.sendToChild("iframe-1", "action", { data: "value" });

// Send to all iframes
messenger.sendToAllChildren("action", { data: "value" });

// Get iframe info
const iframe = messenger.getIframe("iframe-1");
const allIframes = messenger.getAllIframes();

JSON-LD for SEO

// Child sends structured data
messenger.updateJSONLD({
  "@context": "https://schema.org",
  "@type": "Product",
  name: "Widget",
  offers: {
    "@type": "Offer",
    price: "19.99",
  },
});

// Parent automatically injects into <head>

Security

// Parent: Only accept messages from specific origins
const messenger = new ParentMessenger({
  allowedOrigins: [
    "https://app.example.com",
    "https://*.trusted.com", // Wildcard support
  ],
});

// Child: Only accept messages from parent origin
const messenger = new ChildMessenger({
  allowedOrigins: ["https://parent.example.com"],
});

API Reference

ParentMessenger

Constructor Options

| Option | Type | Default | Description | | --------------------- | ---------------- | ---------------- | --------------------------- | | allowedOrigins | string[] | Same-origin only | Allowed child origins | | urlSync | boolean | true | Sync URL with iframe routes | | urlParamKey | string | 'path' | Query param key for routes | | preserveOtherParams | boolean | true | Keep other query params | | autoResize | boolean | true | Auto-resize iframes | | jsonLD | boolean | true | Enable JSON-LD injection | | analyticsId | string | - | GA ID to pass to children | | syncParams | string[] | All params | Params to sync to iframe | | onIframeReady | function | - | Iframe ready callback | | onRouteChange | function | - | Route change callback | | onDimensionUpdate | function | - | Dimension update callback | | actionHandlers | object | - | Custom action handlers | | timeout | number | 5000 | Message timeout (ms) | | logLevel | string\|number | 'info' | Logging level |

Methods

  • sendToChild(iframeId, action, params) - Send message to specific iframe
  • sendToAllChildren(action, params) - Send message to all iframes
  • getIframe(iframeId) - Get iframe metadata
  • getAllIframes() - Get all iframe metadata
  • setLogLevel(level) - Change log level
  • destroy() - Cleanup and destroy

ChildMessenger

Constructor Options

| Option | Type | Default | Description | | -------------------- | ---------------- | ---------------- | ------------------------------ | | allowedOrigins | string[] | Same-origin only | Allowed parent origins | | dimensionReporting | boolean | true | Enable dimension reporting | | dimensionThreshold | number | 1 | Min px change to report | | routeReporting | boolean | true | Enable route reporting | | getRoute | function | - | Custom route getter | | onParentReady | function | - | Parent ready callback | | onNavigate | function | - | Navigation request callback | | onParamUpdate | function | - | Param update callback | | actionHandlers | object | - | Custom action handlers | | metadata | object | - | Metadata to send with announce | | timeout | number | 5000 | Message timeout (ms) | | logLevel | string\|number | 'info' | Logging level |

Methods

  • sendToParent(action, params) - Send message to parent
  • updateRoute(path, title) - Manually update route
  • updateDimensions() - Manually trigger dimension update
  • updateJSONLD(jsonld) - Update JSON-LD structured data
  • getParam(key) - Get parameter value
  • getAllParams() - Get all parameters
  • getConfig(key) - Get config value
  • setLogLevel(level) - Change log level
  • destroy() - Cleanup and destroy

Browser Support

  • Modern browsers with ES6+ support
  • ResizeObserver (with fallback for older browsers)
  • postMessage API

License

MIT © Proximify

Contributing

Contributions are welcome! Please open an issue or PR.