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

@spreadspace/react

v0.1.1

Published

React SDK for embedding SpreadSpace surfaces via a secure, token-isolated iframe.

Readme

@spreadspace/react

React SDK for embedding SpreadSpace surfaces in your app via a secure, token-isolated <iframe>.

npm install @spreadspace/react
# or
pnpm add @spreadspace/react

react and react-dom are peer dependencies (18 or 19).

What this is

<SpreadSpaceEmbed /> renders a SpreadSpace surface (e.g. the spreading workspace or the document review panel) inside an iframe served from embed.spreadspace.app, and wires the host side of the embed postMessage protocol: auto-resize, navigation callbacks, theme sync, and — the important part — token refresh that never exposes a token to your page.

This is the Path A (iframe) integration. For a co-resident React-only widget that shares your bundle, see <SpreadSpaceReview /> shipped from the SpreadSpace app. For server-side token/handle minting, see @spreadspace/node.

The security model in one paragraph

Your page never holds a SpreadSpace token. You give the SDK a getHandle function that calls your backend, which proxies POST /api/embed/iframe-urls with your ss_live_* API key and returns a short-lived, single-use signed_url (it embeds an ih_… handle). The SDK loads it directly as the iframe src. (Returning the bare handle_id also works, but then you must set embedOrigin per environment.) Inside the iframe — same-origin to SpreadSpace — the handle is exchanged for the real ss_embed_* token, which stays in the iframe. When the token nears expiry or is rejected, the iframe asks your page (over postMessage) for a fresh handle, the SDK calls getHandle again, and the iframe re-exchanges it. A handle is worthless off SpreadSpace's origin and dies in seconds; the token never crosses the iframe boundary.

Quickstart

import { SpreadSpaceEmbed } from '@spreadspace/react';

function LoanWorkspace({ loanId }: { loanId: string }) {
  // getHandle calls YOUR backend, which proxies POST /api/embed/iframe-urls
  // with your API key and returns the `signed_url` from the response.
  const getHandle = async ({ surface, loanId }) => {
    const res = await fetch('/my-backend/spreadspace/iframe-url', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ surface, loan_id: loanId }),
    });
    const { signed_url } = await res.json();
    return signed_url; // a full, env-agnostic embed URL — never a token
  };

  return (
    <SpreadSpaceEmbed
      surface="spreading"
      loanId={loanId}
      getHandle={getHandle}
      theme="dark"
      onNav={(nav) => {
        if (nav.action === 'open-loan') router.push(`/loans/${nav.data?.loanId}`);
        if (nav.action === 'upload') router.push(`/loans/${loanId}/upload`);
      }}
    />
  );
}

Your backend endpoint is a thin proxy:

// Server-side. Uses @spreadspace/node or a raw fetch with your API key.
app.post('/my-backend/spreadspace/iframe-url', async (req, res) => {
  const r = await fetch('https://api.spreadspace.app/api/embed/iframe-urls', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SPREADSPACE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ surface: req.body.surface, loan_id: req.body.loan_id }),
  });
  const data = await r.json();
  res.json({ signed_url: data.signed_url }); // forward the signed URL (bare handle_id also works)
});

Scope note: the minted token is capped to documents:read, extractions:read, and spreads:read, locked to the one loan_id. On plans without Analytics, spreads:read is dropped server-side, so the spreading surface falls back to documents-only — inspect the returned scopes if you depend on it.

Props

| Prop | Type | Notes | | --- | --- | --- | | surface | 'spreading' \| 'documents' | Which surface to render. | | loanId | string | Loan the surface is scoped to. | | getHandle | (ctx) => Promise<string> \| string | Mints a fresh single-use handle. Called for the initial load and every refresh. | | onNav | (payload) => void | Frame navigation intents (upload, open-loan, custom). | | theme | 'light' \| 'dark' | Pushed to the frame on mount and on change. | | onReady | (payload) => void | Frame shell mounted. | | onError | (payload) => void | Frame error; fatal distinguishes severity. | | embedOrigin | string | Override the embed app origin (rarely needed). | | title / className / style / sandbox / initialHeight | — | Iframe element passthroughs. |

The component also forwards a ref exposing { reload(), iframe } — call reload() to mint a fresh handle and reload the frame (e.g. after the user re-authenticates).

Advanced: the protocol module

The typed envelope and both messengers are exported (also as @spreadspace/react/postmessage) if you want to drive a hand-rolled iframe:

import { createHostMessenger, type NavPayload } from '@spreadspace/react/postmessage';

createHostMessenger verifies the frame origin, pins inbound messages to your iframe's contentWindow, correlates token-refresh request/response by id, and replies with a handle — the response payload is type-branded so a token field is a compile error.

License

MIT.