@keeganrelume/partner-embed
The frontend SDK for Relume partners. One call puts the Relume builder on your
page; a few event handlers tie it to your wallet. You never touch the iframe
or postMessage.
Interim package name. This is published as
@keeganrelume/partner-embeduntil the@relumenpm scope is recovered, then as@relume/partner-embed. The API won't change; only the import path will.
It is the browser side of the
Relume Partner Protocol v1 (sections 4
and 11). Pair it with @keeganrelume/partner-node on your backend,
which mounts the session route this SDK calls.
Install
npm install @keeganrelume/partner-embed
import { Relume } from '@keeganrelume/partner-embed';
Or with a script tag, which defines window.Relume:
<script src="https://cdn.jsdelivr.net/npm/@keeganrelume/[email protected]"></script>
No runtime dependencies. About 6 KB minified.
Quickstart
const relume = Relume.init('#relume', 'pk_live_…');
relume.onTopUp(async ({ required, balance }) => {
await openCheckout(required - (balance ?? 0)); // your checkout
relume.refreshCredits(); // the builder re-reads the balance
});
relume.onCreditsChanged(({ balance }) => setHeaderBalance(balance));
relume.onExport(({ siteId, downloadUrl }) => toast('Your site is ready'));
relume.onClose(() => router.back());
relume.onError(({ code, message }) => report(code, message));
The builder fills #relume, so give it a size (for example
height: calc(100vh - 64px)). Below 640px wide it covers the whole viewport
instead; pass fullScreenOnMobile: false to keep it in the container. Your
page needs a <meta name="viewport" content="width=device-width"> for phones
to report their real width.
examples/index.html is a whole partner page.
What it does for you
- Creates the iframe on
https://embed.relume.ai/?pk=…and sizes it. - Calls your session route (
GET /relume/session, mounted by@keeganrelume/partner-node) whenever the builder asks: when it opens, before the session expires and after a 401. The builder renews without a reload. - Accepts messages only from the builder's own window on the embed origin, and posts only to that origin. Messages from other scripts or frames are ignored.
- Agrees a protocol version with the builder, so either side can change its messages without breaking the other.
- Checks your page's origin against the publishable key's allowed origins. The
browser refuses the frame on any other origin, and the frame can't say why,
so the SDK fires
onError({ code: 'origin_not_allowed' })and removes it.
Relume.init(target, publishableKey, options?)
target is a selector or an element. Throws a TypeError if it matches
nothing or the key isn't a pk_test_…/pk_live_… key.
| Option | Default | What it does |
|---|---|---|
sessionUrl | /relume/session on your page | Your session route. On another origin it is fetched with credentials, so its CORS must allow your page's origin exactly (never *). |
fullScreenOnMobile | true | Cover the viewport below 640px. |
embedUrl | https://embed.relume.ai | The builder's host. Relume gives you staging's (https://embed-staging.relume.ai). |
apiUrl | inferred from embedUrl | Relume's API, for the allowed-origins check. Known for Relume's hosts; without one the check is skipped and the builder's own applies. |
Events
Each on… method adds a listener and returns a function that removes it.
| Event | Payload | When |
|---|---|---|
onTopUp | { required, balance } | A charge was declined. required is the action's price in your credits. |
onCreditsChanged | { balance } | The balance the builder shows changed. Not fired when your wallet didn't answer. |
onExport | { siteId, exportId, downloadUrl } | An export is ready. |
onClose | none | The user closed the builder. |
onError | { code, message } | The builder couldn't start or carry on. Logged to the console when nobody listens. |
Error codes: session_failed, user_deactivated, user_suspended,
partner_suspended, origin_not_allowed, test_cap_reached and
charge_failed (protocol section 11).
New codes may be added; treat an unknown one like session_failed.
A handler that throws is logged and doesn't stop the others.
Methods
open({ site })opens one of the user's sites (asiteIdfrom the partner API oronExport). The builder reloads on that site.refreshCredits()asks the builder to re-read the balance from your wallet (credits.get), then firesonCreditsChangedif it changed. Call it after a top-up.destroy()removes the iframe and every listener, and stops any pending session request. The instance does nothing afterwards; callRelume.initagain to show the builder again.
Developing
npm ci
npm run typecheck
npm test # unit tests (happy-dom) and the example's size
npm run test:dist # builds, then loads the ESM and script-tag output
The postMessage messages live in src/protocol.ts and
mirror frontend/base/embed/embed_protocol.ts in the Relume app. Change both
together.
Publishing: npm publish --tag latest from this folder (it runs all of the
above first).