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

@narrative.io/app-bridge

v0.1.1

Published

Bridge between the Narrative platform and embedded third-party app UIs — origin-pinned MessageChannel protocol, zero runtime dependencies

Readme

@narrative.io/app-bridge

The bridge between the Narrative platform and an embedded third-party app UI.

An app registers the origin it is served from, the platform embeds it in a sandboxed iframe, and this package carries authorization, navigation, and context between the two over an origin-pinned MessageChannel.

  • Zero runtime dependencies, and that is a hard rule rather than an aspiration. You bundle this package into your app, so every dependency it had would become supply-chain surface in your application.
  • Runtime-validated on both sides. Types disappear when the code runs; every inbound message is checked structurally before it is acted on.
  • No network client. The bridge is postMessage and nothing else. You call the Narrative API with plain fetch and the bearer token it hands you.
  • Nothing fails silently. Unknown methods return errors, every request resolves or rejects, and all failures are BridgeErrors with stable codes.
bun add @narrative.io/app-bridge   # or npm / pnpm / yarn

Stability. Wire protocol version 1 is settled — the message shapes in docs/protocol.md are what the platform speaks. The package stays on 0.x while the first external integrations land, so a minor version may still adjust the JavaScript surface. Pin an exact version if that matters to you, and watch releases.

Quick start

import { connect } from '@narrative.io/app-bridge/guest'

// Pin the origins that may embed you. This is the security decision the
// whole package is built around — never use '*'.
const bridge = await connect({ platformOrigin: 'https://app.narrative.io' })

// Context arrives with the handshake, so there is no round-trip before your
// first render.
console.log(bridge.context.user, bridge.context.company, bridge.context.tier)

// Calling the Narrative API is plain fetch plus a bearer token. getToken()
// caches and renews automatically.
const { token } = await bridge.getToken()
const response = await fetch(`${bridge.context.apiBaseUrl}/datasets`, {
	headers: { Authorization: `Bearer ${token}` },
})

// Navigation: your app is a controlled component. Tell the platform where the
// user went, and switch views when the platform answers.
bridge.pathChanged('/reports')
bridge.onNavigate((path) => showView(path))

// The platform tells you about live changes; you never poll.
bridge.onContextChange((context) => showCompany(context.company))
bridge.onSessionEnd((reason) => showSignedOutBanner(reason))

No bundler? There is a <script> build that attaches a single global:

<script src="https://unpkg.com/@narrative.io/app-bridge/dist/app-bridge.global.js"></script>
<script>
	NarrativeAppBridge.connect({ platformOrigin: 'https://app.narrative.io' }).then((bridge) => {
		document.title = bridge.context.company.name
	})
</script>

Four rules that will save you an afternoon

  1. Call connect() as early as possible — before your framework boots — so the handshake overlaps your app's own startup.
  2. Your app must be frameable. An X-Frame-Options: DENY header, or a CSP frame-ancestors directive that excludes the platform, means your app cannot be embedded at all, no matter what the platform allows.
  3. Look and feel is yours. The bridge deliberately carries no styling or theming information, and never will. It moves identity, authorization, and navigation — facts, not presentation.
  4. Client secrets never go in the browser. The token getToken() hands you is installation-scoped and short-lived. Your own backend uses OAuth client credentials instead.

Documentation

| | | |---|---| | Getting started | Connecting, calling the API, navigation, and the full guest API reference | | Protocol | The wire format, message by message, and the compatibility rules | | Security model | What the bridge guarantees, what it does not, and what each side must do | | Hosting apps | The host surface, for a platform embedding apps | | Troubleshooting | Every BridgeError code and what actually causes it | | Architecture | Why the protocol is shaped this way, including the paths not taken |

The surface, at a glance

One handshake, two methods, four events. Window-level traffic is exactly two messages — hello from the app, handshake back with a transferred MessagePort — and everything after that travels on the port, unreachable from any other window.

| | name | direction | |---|---|---| | method | getToken | app → platform | | method | getContext | app → platform | | event | pathChanged | app → platform | | event | navigate | platform → app | | event | contextChanged | platform → app | | event | sessionEnded | platform → app |

Three entry points, and no root export — importing the package bare resolves nothing. That is deliberate: it makes it impossible for an app to accidentally bundle host, which is the platform's side of the conversation.

| Subpath | For | |---|---| | @narrative.io/app-bridge/guest | An app being embedded. This is the one you want. | | @narrative.io/app-bridge/host | A platform doing the embedding. | | @narrative.io/app-bridge/protocol | Shared types and validators, if you need to name them. |

Support

Bugs and feature requests: GitHub issues. Security reports go to [email protected] — see SECURITY.md.

Contributing: CONTRIBUTING.md.

License

MIT © Narrative I/O, Inc.