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

@orbital.vision/prophecy

v1.2.0

Published

Embeddable AI room visualisation SDK from Orbital Vision

Readme

Prophecy

Prophecy is Orbital Vision's embeddable AI room-visualisation SDK. It owns the browser experience, account-history lifecycle and integration hooks; OV25 remains the authenticated API and generation control plane.

The default modal always includes the branded attribution Powered by OV25 from Orbital Vision and the Orbital Vision mark in its footer.

The same release supports:

  • a single declarative <script> tag;
  • programmatic browser installation;
  • npm/ES module consumers;
  • anonymous or retailer-authenticated sessions;
  • account-owned room photos and generated-result history.

Product image sources

Prophecy supports two mutually exclusive product sources:

  1. OV25 configurator: when configurator is omitted, Prophecy finds the first HTTPS iframe whose host is exactly configurator.orbital.vision, reads its public organisation/product identity and requests 1–4 rendered product views over its existing postMessage contract.
  2. External catalogue: pass productReference plus productImages. Images may be JPEG, PNG or WebP blobs, or CORS-readable HTTP(S) URLs. Supply 1–4 views of the exact same product; each image may be at most 12 MB.

External products require a same-origin sessionEndpoint or sessionProvider. The retailer backend exchanges its private Orbital API key for the short-lived browser token; OV25 derives organizationId from that key and returns it in the public session. A private key and organisation ID must never be passed to Prophecy for partner authentication.

Script-tag installation

Publish Prophecy to npm and load its browser bundle through jsDelivr. npm is the package registry; jsDelivr is the globally cached CDN layer designed to serve the script to site visitors.

The documented URL carries the major version only. Fixes and new features reach every integration without anyone editing a script tag, and a breaking change is a deliberate move to the next major. A retailer who needs to freeze a specific build can pin the exact version instead, and takes on updating it themselves.

<iframe
  id="product-configurator"
  src="https://configurator.orbital.vision/89-public-key/7218"
></iframe>

<script src="https://cdn.jsdelivr.net/npm/@orbital.vision/prophecy@1/dist/prophecy.js"></script>

That is the entire OV25 integration. The tag needs no attributes and the site needs no backend: Prophecy discovers the configurator.orbital.vision iframe, reads the organisation, public key and product out of its URL, follows its current configuration and mounts a safe floating launcher. The created instance is exposed as window.prophecy.

Prophecy waits for the configurator rather than requiring it at parse time, so an iframe mounted after hydration, or on a later route of a single-page site, still activates. Include the tag once; a second copy will not mount a second launcher.

To place the launcher yourself, mark the button in your own markup:

<button type="button" data-prophecy-launcher>See it in your room</button>

Prophecy attaches to the first marked element, follows one that a single-page site mounts and unmounts per route, and stops offering its own floating launcher once a page has marked one.

Attributes only override discovery: data-prophecy-configurator for a specific iframe, data-prophecy-button for a fixed trigger, data-prophecy-endpoint for a non-production API host, data-prophecy-auto="false" to install programmatically instead. data-prophecy-session-endpoint="/api/orbital/prophecy/session" switches to the authenticated exchange below.

An external product can also be installed declaratively:

<button id="see-in-room" type="button">See it in your room</button>
<script
  src="https://cdn.jsdelivr.net/npm/@orbital.vision/prophecy@1/dist/prophecy.js"
  data-prophecy-button="#see-in-room"
  data-prophecy-product-reference="shopify:variant:SKU-42"
  data-prophecy-product-images='["https://shop.example/images/sku-42-front.webp","https://shop.example/images/sku-42-side.webp"]'
  data-prophecy-product-url="https://shop.example/products/sku-42"
  data-prophecy-session-endpoint="/api/orbital/prophecy/session"
></script>

For hooks, account history or custom controls, load the same script and initialise it programmatically:

<script src="https://cdn.jsdelivr.net/npm/@orbital.vision/prophecy@1/dist/prophecy.js"></script>
<script>
  const prophecy = Prophecy.create({
    configurator: '#product-configurator',
    endpoint: 'https://demo.orbital.vision',
    accountKey: signedInAccountVersion,
    sessionEndpoint: '/api/orbital/prophecy/session',
    history: accountHistory,
    onResult(result) {
      analytics.track('prophecy_generation_complete', result);
    }
  });

  prophecy.mount('#prophecy-launcher');
</script>

accountKey is a stable, non-sensitive browser value used only to notice authentication changes. It is never sent to Orbital. On login, logout or account switching, call:

await prophecy.setAccountContext(nextAccountKey);

This immediately invalidates cached browser sessions, aborts stale work, removes the previous account's photos/results from memory and reloads the new account's history. Do not use an email address or access token as the key.

npm installation

npm install @orbital.vision/prophecy
import Prophecy from '@orbital.vision/prophecy';

const prophecy = Prophecy.create({
  endpoint: 'https://demo.orbital.vision',
  sessionEndpoint: '/api/orbital/prophecy/session'
});

For any non-OV25 product:

const prophecy = Prophecy.create({
  endpoint: 'https://demo.orbital.vision',
  productReference: 'shopify:variant:SKU-42',
  productImages: [frontImageUrl, sideImageUrl, detailImageBlob],
  productUrl: location.href,
  sessionEndpoint: '/api/orbital/prophecy/session'
});

// SPA navigation: atomically replace identity, images and result CTA URL.
prophecy.setProduct({
  reference: 'shopify:variant:SKU-43',
  images: [nextFrontUrl, nextSideUrl],
  url: nextProductUrl
});

The package ships an ES module, an IIFE browser bundle, source maps and TypeScript declarations. window.OrbitalRoomAI remains as a deprecated migration alias for existing OV25 embeds.

The browser bundle is also available from unpkg at https://unpkg.com/@orbital.vision/prophecy@1/dist/prophecy.js. jsDelivr is the documented default; npm itself should not be used as the browser asset server.

Availability preflight

In production, Prophecy asks whether the site may offer Room AI before showing or enabling its launcher. For an anonymous OV25 install that is GET /api/configurator/room-ai/availability, which checks the public key against the organisation, that the origin is authorised, and that Room AI is enabled. It opens no session, spends none of the shopper's generation allowance and does not depend on the product, so one cached answer serves a whole catalogue and the result is fetched once per page. A session is created when the shopper actually opens the preview. Installs that use sessionEndpoint or sessionProvider still preflight by requesting the partner session, because only the retailer's backend can say whether one is obtainable.

If the preflight fails, Prophecy keeps the site-owned button hidden, emits availability-change and error, and logs the reason to the console instead of opening a broken dialog.

Use await prophecy.checkAvailability() when a custom interface needs the result directly. Localhost is development mode by default: the launcher stays available and session errors render inside the dialog for inspection. Set development explicitly only when a non-local development hostname needs the same behaviour.

Access modes

Each organisation picks one on Dashboard → Prophecy:

| Mode | Anonymous sessions | Retailer needs | | --- | --- | --- | | open (default) | allowed | The script tag | | free-trial | allowed until the browser's daily free allowance is spent | The script tag, plus a session endpoint | | signed-in | refused | A session endpoint |

free-trial counts against a marker Prophecy stores in the browser and sends with each session request. The server hashes it with the organisation ID before it reaches a counter. Whoever holds the browser can clear it, so it paces shoppers towards signing in rather than capping spend; the organisation's daily ceilings and token balance do that.

When the allowance is spent the generation request answers 403 { code: 'SIGN_IN_REQUIRED' }, Prophecy emits sign-in-required instead of a generation error, and the page starts its own login. getState() reports access and freeGenerations.

Prophecy.create({
  sessionEndpoint: '/api/orbital/prophecy/session',
  onSignInRequired({ freeGenerations }) {
    showSignInPrompt(`You have used your ${freeGenerations} free previews.`);
  }
});

Authenticated session exchange

The retailer backend derives its customer from its own signed-in session and exchanges a Prophecy server key for a short-lived browser token. That key type only buys previews: unlike the general private API key it cannot read the organisation's products, swatches or swatch rules.

const account = await requireSignedInAccount(request);
const { productId, productReference, productSource } = await request.json();

const response = await fetch(
  'https://demo.orbital.vision/api/configurator/room-ai/server/session',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.ORBITAL_PROPHECY_SERVER_KEY}`
    },
    body: JSON.stringify({
      productId,
      productReference,
      productSource,
      origin: new URL(request.url).origin,
      customerId: account.id
    })
  }
);

The browser calls a same-origin retailer endpoint through sessionEndpoint. That endpoint must require the retailer's normal authenticated session, derive customerId server-side, enforce CSRF/origin protections and validate the requested productId or productReference against the product currently being viewed. OV25 products use productSource: 'ov25-configurator'; supplied-image products use productSource: 'external-images' and may have a null productId.

Account history

History belongs in the retailer's database and private object storage - not browser local storage. Each route must derive the account from the retailer session rather than accepting an account ID from the SDK.

const accountHistory = {
  async load({ signal }) {
    const response = await fetch('/api/orbital/prophecy/history', { signal });
    if (!response.ok) throw new Error('History could not be loaded');
    return response.json();
  },

  async saveRoom(photo) {
    const body = new FormData();
    body.append('file', photo.blob, 'room.jpg');
    body.append('width', String(photo.width));
    body.append('height', String(photo.height));
    body.append('aspectRatio', photo.aspectRatio);
    const response = await fetch('/api/orbital/prophecy/rooms', {
      method: 'POST',
      body,
      signal: photo.signal
    });
    if (!response.ok) throw new Error('Room photo could not be saved');
    return response.json(); // { id }
  },

  async saveResult(result) {
    const response = await fetch('/api/orbital/prophecy/results', {
      method: 'POST',
      keepalive: true,
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(result),
      signal: result.signal
    });
    if (!response.ok) throw new Error('Result could not be saved');
    return response.json();
  },

  async clear({ signal }) {
    const response = await fetch('/api/orbital/prophecy/history', {
      method: 'DELETE',
      signal
    });
    if (!response.ok) throw new Error('History could not be cleared');
  }
};

Prophecy serialises writes and deletion so a pending save cannot recreate data after clearHistory(). Adapters should honour the supplied AbortSignal. Returned room-photo URLs must be same-origin or CORS-enabled; Prophecy streams them sequentially with 12 MB per-image and 48 MB total restoration limits.

Before saving a generated result, verify the job from the retailer backend:

const verified = await fetch(
  `https://demo.orbital.vision/api/configurator/room-ai/server/jobs/${jobId}`,
  {
    headers: {
      Authorization: `Bearer ${process.env.ORBITAL_PROPHECY_SERVER_KEY}`,
      'X-Orbital-Customer-Id': account.id
    }
  }
).then(response => response.json());

if (verified.status !== 'COMPLETED' || !verified.resultUrl) {
  throw new Error('Unverified Prophecy result');
}

// Persist these verified server fields, not the browser-submitted equivalents.
await saveResult({
  orbitalJobId: verified.jobId,
  productId: verified.productId,
  productReference: verified.productReference,
  productSource: verified.productSource,
  selectionString: verified.selectionString,
  sourceUrl: verified.resultUrl
});

Instance API

| Method | Purpose | | --- | --- | | checkAvailability() | Preflight setup, origin access and generation credits | | open() / close() / isOpen() | Control the dialog | | mount(target) | Mount Prophecy's default launcher | | mountOverlay(target?) | Put the widget over the configurator, or another element | | useLauncher(target) / releaseLauncher() | Move the launcher to another element, or detach it | | setProductId(id) | Synchronise an SPA product change | | setProduct({ reference, images, productId?, url? }) | Atomically switch an external product and its 1–4 images | | setAccountContext(key) | Fence a login, logout or account switch | | getState() | Read current public state | | refreshHistory() | Reload account history | | clearHistory() | Delete device history and invoke the account adapter | | on(name, handler) / off(name, handler) | Manage event subscriptions | | destroy() | Abort work and remove UI/listeners/object URLs |

Events and hooks

Subscribe through onEvent, a named option callback, prophecy.on(name, handler), prophecy.on('*', handler), or the bubbling prophecy DOM CustomEvent emitted from the launcher.

| Event | Option callback | | --- | --- | | ready | onReady | | availability-change | onAvailabilityChange | | sign-in-required | onSignInRequired | | order | onOrder | | open / close | onOpen / onClose | | view-change | onViewChange | | product-change | onProductChange | | account-change | onAccountChange | | selection-change | onSelectionChange | | photo-selected / photo-cleared | onPhotoSelected / onPhotoCleared | | phone-handoff-start / phone-photo-received | onPhoneHandoffStart / onPhonePhotoReceived | | generation-start / generation-progress | onGenerationStart / onGenerationProgress | | generation-complete | onResult | | generation-error | onGenerationError | | history-load / history-save / history-error | onHistoryLoad / onHistorySave / onHistoryError | | error | onError | | destroyed | onDestroy |

The legacy orbital-room-ai DOM event is emitted during the migration period.

Order this product

Every preview remembers the configurator's SKU for that exact configuration and the page it was generated from, query string and all. When a shopper presses Order this product, Prophecy tells the page first. Call preventDefault() to keep them on the page and take the order yourself; otherwise they go to that page.

Prophecy.create({
  onOrder(order) {
    order.preventDefault();
    addToBasket(order.sku); // e.g. "BLOOMSBURY-MED-TWEED-BOULDER"
    // order.pageUrl is the page the preview was made from, query string and all
  }
});

With the script tag, set the hook before the script loads:

<script>
  window.prophecyOptions = {
    onOrder(order) {
      order.preventDefault();
      addToBasket(order.sku);
    }
  };
</script>
<script src="https://cdn.jsdelivr.net/npm/@orbital.vision/prophecy@1/dist/prophecy.js"></script>

sku and pageUrl are also on every generation-complete result and every saved preview.

Local development

npm install
npm run dev
npm run check

CI runs the complete check and a package dry-run on Node 20 and Node 24 for every pull request and push to main.

The Vite playground exercises the production Shadow DOM UI. To build and copy generated artifacts into an OV25 checkout:

During OV25 development, point the host app directly at Vite so SDK edits only need a page refresh:

# Prophecy
npm run dev

# Optional OV25 override; this is the development default.
NEXT_PUBLIC_PROPHECY_SDK_SRC=http://127.0.0.1:5173/src/runtime.js
npm run sync:ov25 -- /absolute/path/to/OV25

Prophecy source lives here. The corresponding OV25 files are generated distribution artifacts and should not be edited by hand.

Publishing releases

Releases publish @orbital.vision/prophecy to the public npm registry from .github/workflows/release.yml. The workflow uses npm trusted publishing through GitHub OIDC; it does not require a long-lived npm write token.

The package is public under the @orbital.vision scope. Use the release workflow for normal releases; manual publishing is reserved for recovery:

npm login
npm run check
npm publish --access public

After 0.2.0 exists, configure its npm Trusted Publisher with these exact values:

  • GitHub organization: orbitalvision
  • Repository: Prophecy
  • Workflow filename: release.yml
  • Environment: npm
  • Allowed action: npm publish

Create the matching protected npm environment in GitHub, ideally with required maintainer approval. Once an OIDC release succeeds, set npm publishing access to require 2FA and disallow tokens, then revoke any bootstrap token.

For each subsequent release, npm version updates the manifest, lockfile and both runtime version constants, then creates the release commit and tag. Push both:

npm version patch # or minor / major
git push origin main --follow-tags

The v* tag starts the release workflow. It rejects a tag that does not exactly match package.json, rebuilds and tests the SDK, verifies the mandatory attribution and package contents, then publishes. Because this GitHub repository is private, npm cannot currently attach a public provenance attestation even though authentication still uses short-lived OIDC credentials.