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

@gridd/library-embed

v0.2.1

Published

Embeddable DeckAI slide library: a React iframe component driving the gridd-library postMessage protocol, plus the offline layers it runs on — a pluggable Blob cache (IndexedDB by default) and remote/local data sources behind one interface

Readme

@gridd/library-embed

React component that embeds the DeckAI slide library in an iframe and drives the gridd-library postMessage protocol, so the host app writes no raw message handlers.

It also ships the pieces that make the Library work with no network: the built UI, a pluggable Blob cache (IndexedDB by default), and remote/local data sources behind one interface — see Offline. Those layers are framework-free and exported separately, so the same code runs in a React task pane, in a sync web worker, and in the SlideCraft desktop shell.

It is the sibling of @gridd/embed but a separate package on its own version line: a different application (the DeckAI add-in, not Gridd's embeddable.html), a different origin, and a different protocol. Nothing is shared between the two at runtime.

Install

npm install @gridd/library-embed

react (^18 || ^19) is a peer dependency.

Usage

import { GriddLibrary } from "@gridd/library-embed/react";

<GriddLibrary
  baseUrl="https://black-ground-08064670f.1.azurestaticapps.net/taskpane.html"
  mode="library"
  styleSheet={deck.styleSheet}
  getIdToken={({ forceRefresh }) => auth.currentUser?.getIdToken(forceRefresh) ?? Promise.resolve(null)}
  onSlidePicked={(griddDoc, pick) => copyIntoSelectedSlide(griddDoc, pick.name)}
  onSignInRequest={() => openSignInDialog()}
  width="100%"
  height="100%"
/>;

The root export is the same module, so from "@gridd/library-embed" also works.

Props

| Prop | Type | Default | Description | | ----------------- | ---------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------- | | baseUrl | string | required | URL of the add-in's taskpane.html. Its origin is the only one trusted or posted to. | | mode | "library" | "library" | Which surface the add-in renders. Sent as ?mode= and repeated in the handshake context. | | styleSheet | GriddStyleSheetData | — | Host deck's stylesheet, so previews match. Controlled — re-pushed on identity change. | | idToken | string \| null | — | Firebase ID token of the signed-in user. Controlled — re-pushed on change. | | getIdToken | ({ forceRefresh }) => Promise<string \| null> | — | Preferred over idToken: mints a token on demand, including when the iframe reports staleness. | | width/height | string \| number | "100%" | Iframe box. | | onReady | () => void | — | Fires once the handshake completes. | | onSlidePicked | (doc: GriddSlideDocument, pick: LibraryPick) => void | — | The user chose a slide. doc is the Gridd body to copy; pick carries its metadata. | | onSignInRequest | () => void | — | The user hit a gate needing the host's sign-in flow. The panel has no login of its own. | | onError | (error: LibraryError) => void | — | The iframe (or token minting) failed in a way worth surfacing. |

library mode

The add-in is a PowerPoint task pane by default. mode=library asks it to mount just the slide-library browser — no Office chrome and no Office.js on the render path, the same bypass idea as Gridd's ?styleSheet=1 route. In that mode "insert into slide" does not write to PowerPoint; it posts gridd-library-pick to the parent instead.

Two query parameters are appended to baseUrl:

| Param | Meaning | | ------------ | ------------------------------------------------------------------------------------------------ | | mode | library — render the embeddable library. | | hostOrigin | The embedding page's origin. Advisory: it lets the add-in target its replies instead of "*". |

Protocol

Message-type constants are exported (READY_TYPE, PICK_TYPE, …) so a host never types a literal. The add-in side deliberately mirrors them in src/taskpane/embed/libraryProtocol.ts rather than importing this package, so its webpack build does not depend on this package being built or published — change one, change both, and bump PROTOCOL_VERSION in both.

iframe ──► host   gridd-library-ready            "I am mounted and listening"
host   ──► iframe gridd-library-context          mode + stylesheet + ID token
iframe ──► host   gridd-library-token-request    "my token is stale"
host   ──► iframe gridd-library-token            a fresh Firebase ID token
iframe ──► host   gridd-library-pick             the chosen slide's Gridd JSON
host   ──► iframe gridd-library-pick-result      ack, so the panel can confirm
iframe ──► host   gridd-library-signin-request   "the host should sign the user in"
iframe ──► host   gridd-library-error            surfaced to onError

Handshake

  1. On mount the iframe posts { type: "gridd-library-ready", id }.
  2. The host replies gridd-library-context with the deck's stylesheet and the current ID token. ready — not iframe onLoad — is the reliable "app is listening" signal, so context is never delivered earlier than this.
  3. Whenever styleSheet or the token changes, the host pushes a fresh gridd-library-context. Each push carries the complete context and a monotonic seq; the iframe replaces its copy wholesale (an omitted field means "not set", never "unchanged") and may ignore a seq lower than the last one it applied.

Host→iframe messages also carry protocolVersion (currently 1).

Pick payload

gridd-library-pick from the iframe:

{
  "type": "gridd-library-pick",
  "id": 7,                       // optional; when present the host acks with gridd-library-pick-result
  "document": { /* Gridd JSON */ }, // object, or a JSON string — both accepted
  "slideId": "value-chain-3",    // optional metadata below this line
  "name": "Value chain",
  "category": "Strategy",
  "premium": true,
  "thumbnail": "https://…/value-chain.png"
}

document is the add-in's existing template-apply shape, which lines up with Gridd's own document keys:

type GriddSlideDocument = {
  cols?: unknown[];
  rows?: unknown[];
  cells?: unknown; // ProseMirror docs, keyed by cell UUID
  merged?: unknown[];
  borders?: unknown[];
  cellStyles?: Record<string, unknown>; // keyed by cell UUID
  spacerStyles?: Record<string, unknown>;
};

The bridge does not police or normalise this payload — every field is optional and it is handed to onSlidePicked as received. A host that needs guaranteed defaults (or that wants to regenerate cell UUIDs to avoid collisions across embeds) should run it through its own adapter before writing it into its store.

Auth

The host owns the login. The studio signs the user in with Firebase and passes that user's ID token down; the iframe forwards it to the premium server as a bearer token. One identity across the studio, the library, and the add-in — no second sign-in in the panel.

  • Prefer getIdToken over idToken: Firebase ID tokens expire after ~1h, and the callback lets both the host (on prop change) and the iframe (via gridd-library-token-request) obtain a fresh one. With idToken alone the host must rotate the prop itself.
  • Signed out, pass null/omit. The library still browses free slides; a premium slide posts gridd-library-signin-request so the host can open its own sign-in flow.
  • The server is the trust boundary. It verifies the ID token and the user's entitlement before returning premium slide data. Lock badges in the library UI are UX, never the gate.

Security

The iframe posts with "*" — it cannot know the embedding origin ahead of time — so this component is the strict side of every exchange:

  • messages are ignored unless event.source is this component's own iframe window;
  • messages are ignored unless event.origin equals the origin of baseUrl;
  • every reply and push names that concrete origin — never "*".

This matters more than in @gridd/embed, because a Firebase ID token crosses the boundary. It is posted only to the validated add-in origin, and it is scoped to that origin deliberately.

Offline: the cache and the two data sources

The Library is a server-backed premium catalog, so offline it has two problems — the UI is remote and the catalog data is remote. Both are solved inside this package, once, so the website, the desktop app and the PowerPoint add-in share one implementation.

Shipping the UI

@gridd/library-embed/node (Node condition only) resolves the built Library UI on disk:

import { libraryAppPath, libraryAppEntry, hasLibraryApp } from "@gridd/library-embed/node";

It is Electron only. In the add-in the Library is the taskpane and serves itself over https; nothing there needs a path. The bundle is produced by npm run build:app, which copies the add-in's webpack output into app/ and refuses the copy if taskpane.html has grown a root-relative or self-hosted absolute URL — either would make the desktop shell fetch its own UI over the network.

@gridd/library-embed/cache

One interface, several backends, picked at runtime:

import { openLibraryCache, formatCacheSize } from "@gridd/library-embed/cache";

const cache = await openLibraryCache();      // never throws
cache.store.backend;                          // "indexeddb" | "bridge" | "memory"
await cache.store.put("thumbs/12.png", blob);
await cache.estimate();                       // { bytes, entries, persisted, ... }

| | | | --- | --- | | Values are Blobs | localStorage caps at ~5 MB and stores strings; thumbnails alone exceed that. It is used only for the tiny "catalog last synced at" marker. | | IndexedDB is the default | It is the only backend available in the PowerPoint add-in, so it is the one that has to work first and the one every environment shares. | | A bridge is optional | An Electron preload may set globalThis.griddLibraryCacheBridge to add a disk backend — needed only for the loopback-origin fallback, where the origin changes each launch and takes the IndexedDB with it. It is a backend, never a second code path. | | A blocked store degrades | Safari/WKWebView can refuse third-party storage outright. openLibraryCache then returns a memory-only store for the session instead of throwing. durable is false, and the UI says so. | | Nothing here is storage | Every entry is re-derivable from the server. navigator.storage.persist() is requested and usually refused in a partitioned third-party context; nothing the user owns may ever live only here. | | It is capped | enforceCacheLimit sweeps LRU to a ceiling, pinning catalog*.json and meta/. A cache that grows without bound is a support ticket about disk space — and in a partitioned bucket, silent eviction of the entries you wanted most. |

Partitioning

Chromium 115+ partitions storage by (top-level site, frame origin). The Library's IndexedDB inside PowerPoint on the web is a different bucket from the same origin inside Excel on the web, and different again from inside SlideCraft. Two consequences to build for, not discover:

  • each host warms its own cache — never tell the user "you already downloaded this";
  • a cache warmed in the add-in does nothing for SlideCraft, and the reverse.

resolveOfficePartitionKey() reads Office.context.partitionKey, which Office exposes exactly for this. It is a hash of the top-level domain and the add-in domain, and it is undefined in environments without partitioning, such as the Office-on-Windows webview — both paths are supported. Microsoft's guidance prefixes localStorage keys with it; the equivalent here is the IndexedDB database name, so namespaceDatabaseName puts it there.

@gridd/library-embed/data

Two sources behind one interface — remote (the authenticated premium API) and local (a static snapshot: catalog.json, slides/<id>.json, files/<id>.pptx, thumbs/<id>.<ext>) — and a coordinator that layers them:

import { LibraryData, RemoteLibrarySource } from "@gridd/library-embed/data";

const library = new LibraryData({
  cache,
  remote: new RemoteLibrarySource({ baseUrl, getIdToken }),
});

await library.getCatalog();                   // remote-first up, cache-first down
await library.getSlide(id, catalogEntry);     // the record: Gridd body + metadata
await library.getSlideFile(id, catalogEntry); // the PPTX bytes, ETag-revalidated
await library.sync();                         // "Update library"
await library.getStatus();                    // freshness + offline entitlement window

Cache-first when the network is down, remote-first when it is up. navigator.onLine only picks which to try first; a failed remote read always falls through to the cache, and an "offline" read that misses still tries the network before giving up.

A slide is two payloads. The record comes from /frameworks/<id> with include=none, so the PPTX never rides inside the JSON — base64 in a body costs a third again in bandwidth and in cache, and cannot be revalidated on its own. The bytes come from /frameworks/<id>/file, are cached raw, and are re-read with If-None-Match, so inserting the same template twice costs a 304 rather than the file. (A cross-origin deployment must send Access-Control-Expose-Headers: ETag, or every read is an unconditional download.)

sync() never pre-downloads the whole premium catalog — that is size the user did not ask for and an entitlement leak. It fetches the entitlement first, then caches thumbnails for the whole catalog (a locked slide still shows its picture) but slide bodies and files only for the entitled set, and prunes both when they fall out of entitlement on the next run.

Entitlements offline

The server is the trust boundary, and offline there is no server, so the cached entitlement decision is trusted for the same window as the offline session — DEFAULT_OFFLINE_ENTITLEMENT_MS, 48 hours — and expires with it. After that the Library shows the catalog but cannot open premium slides.

Say this in the UI, because a feature that quietly stops working mid-flight is worse than one with a stated limit:

catalog from 12 March · premium available offline until at most 14 March

"at most", because an evictable cache can end it early. And re-sync silently whenever a network appears, so the window keeps moving and the user rarely meets it.

Verifying it

Run this in all three runtimes — SlideCraft web, SlideCraft desktop, and the PowerPoint add-in on both Windows (WebView2) and Mac (WKWebView). Safari is where third-party storage assumptions break, so the Mac pass is not optional.

In each:

  1. Load online once and let the sync finish.
  2. Go offline and reload.
  3. The catalog renders from cache; a cached slide inserts; thumbnails show; nothing spins forever waiting on the API.
  4. Then clear site data and reload offline: it must say "catalog unavailable offline" rather than hanging.

Also check, per host: settings shows a cache size and clears it; the status line names the catalog date and the offline premium ceiling; and where storage is blocked the panel still works and says the library is not saved on this device.

Build

npm install
npm run build        # tsup → dist/{react,cache,data}/index.{js,cjs,d.ts}
npm run build:app    # add-in webpack output → app/   (needs a built add-in)
npm run typecheck

build:app reads the repo's dist/; override with LIBRARY_APP_DIST=/path/to/build.

Versioning

Versioned and published independently of @gridd/embed. Bump the minor for additive protocol messages or props; bump the major (and PROTOCOL_VERSION) for a change the add-in side must land in lockstep.