@xapps-platform/widget-sdk
v0.2.1
Published
Typed browser bridge SDK for Xapps publisher widgets
Readme
@xapps-platform/widget-sdk
Browser bridge SDK for publisher widgets running inside Xapps iframes.
Install
npm install @xapps-platform/widget-sdkExports
createBridgefrom@xapps-platform/widget-sdkuseXappsBridge,useToolRequestfrom@xapps-platform/widget-sdk/reactXappsAdapter/initfrom@xapps-platform/widget-sdk/adapter
Minimal usage
import { createBridge } from "@xapps-platform/widget-sdk";
const bridge = createBridge();
const context = await bridge.getContext();
const tools = await bridge.listTools();
const result = await bridge.createRequest({
toolName: "example_tool",
payload: { hello: "world" },
});
const guard = await bridge.requestGuard({
guardSlug: "platform.confirmation",
trigger: "before:tool_run",
context: { requestId: "req_123" },
});Bridge API Surface
Request lifecycle:
createRequest(...)createMultipartRequest(...)getRequest(...)getResponse(...)getRequestEvents(...)getRequestArtifacts(...)attachRequestArtifact(...)subscribeRequest(...)unsubscribeRequest(...)
Upload lifecycle:
createUpload(...)createMultipartUpload(...)putMultipartUploadPart(...)listMultipartUploadParts(...)completeMultipartUpload(...)getMultipartUpload(...)
Guard/session/identity:
requestGuard(...)getVendorAssertion(...)signAction(...)requestTokenRefresh(...)getTools(...)/listTools(...)
Bridge listeners:
onTokenRefresh(...)onSessionExpired(...)onRequestStatusUpdate(...)onGuardStatus(...)onExpandResult(...)onThemeChanged(...)onFocusRequest(...)onFocusTrap(...)
Operational surfaces:
openOperationalSurface(...)
Bootstrap verification helper:
verifyWidgetBootstrap(...)
Expand / Focus / Fullscreen (Widget -> Host)
Widgets can request a larger host-managed presentation mode (for example overlay/focus mode, then fullscreen).
import { createBridge } from "@xapps-platform/widget-sdk";
const bridge = createBridge();
bridge.onExpandResult((result) => {
// result.hostManaged, result.stage, result.nativeFullscreen
console.log("expand result", result);
});
// First step: request focus mode (host overlay if supported).
bridge.requestExpand({
expanded: true,
stage: "focus",
source: "jsonforms",
});
// Optional second step: request fullscreen.
bridge.requestExpand({
expanded: true,
stage: "fullscreen",
source: "jsonforms",
});
// Exit back to inline.
bridge.requestExpand({
expanded: false,
stage: "inline",
source: "jsonforms",
});Notes:
- If the host does not support the expand bridge, widgets should keep a local fallback (inside iframe bounds).
- Host support is documented in
xapps-embed-sdkand the expansion spec.
Operational surface opens (Widget -> Host)
Widgets can ask the host to open user-facing operational surfaces for the current xapp context.
import { createBridge } from "@xapps-platform/widget-sdk";
const bridge = createBridge();
bridge.openOperationalSurface({
surface: "payments",
installationId: "inst_123",
paymentSessionId: "pay_123",
placement: "in_router",
});Supported surfaces:
requestspaymentsinvoicesnotifications
Optional focused record ids:
requestIdpaymentSessionIdinvoiceIdnotificationId
Placement hints:
in_routerside_panelfull_page
Current platform behavior still defaults to in_router. Additional placements are declared now so
hosts can evolve later without changing widget-side API usage.
Publisher-rendered widget shell helper (recommended):
import { createBridge, createExpandController } from "@xapps-platform/widget-sdk";
const bridge = createBridge();
const expand = createExpandController(bridge, {
source: "publisher-shell",
widgetId: "wid_123",
suggested: { renderer: "publisher", layout: "app" },
});
// Icon-only button: inline -> focus -> fullscreen -> inline
document.getElementById("expandBtn")?.addEventListener("click", () => {
expand.toggle();
});
expand.onChange((state) => {
// Hide local overlay chrome while host is managing focus/fullscreen.
document.body.classList.toggle("host-overlay-active", state.hostManaged && state.expanded);
});Request-widget bootstrap verification helper:
import { createBridge, verifyWidgetBootstrap } from "@xapps-platform/widget-sdk";
const bridge = createBridge();
const verified = await verifyWidgetBootstrap({
bridge,
endpoint: "/widgets/my-private-widget/bootstrap-verify",
body: {
widgetKind: "request_capable",
},
});
console.log(verified.context.hostOrigin);
console.log(verified.payload);Recommended contract:
- load public
iframe_urlbootstrap without secrets - use
verifyWidgetBootstrap(...)to send current widget context to your backend - let your backend verify the short-lived widget token against the gateway before exposing private request-capable runtime state
- if the page is opened directly outside an Xapps host/embed, keep private runtime blocked and show a clear "open from Xapps" message instead of treating direct access as a valid request-capable session
Optional stronger bootstrap transport already supported:
- set
widgets[].config.xapps.bootstrap_transport = "signed_ticket" - the publisher wrapper will append
xapps_bootstrap_ticket=...to the iframe URL hash verifyWidgetBootstrap(...)will consume that ticket from the URL and forward it asbootstrapTicketto your backend verify endpoint- this stays additive; the current default remains a public bootstrap shell
Guard helpers
import {
getGuardBlockedDetails,
getPaymentGuardRefResolution,
isGuardBlockedError,
isPaymentGuardGovernanceReason,
} from "@xapps-platform/widget-sdk";
try {
await bridge.createRequest({ toolName: "generate_report", payload: {} });
} catch (err) {
if (isGuardBlockedError(err)) {
const guard = getGuardBlockedDetails(err);
const refResolution = getPaymentGuardRefResolution(err);
// guard?.guardSlug, guard?.reason, guard?.action, refResolution?.source
if (isPaymentGuardGovernanceReason(guard?.reason)) {
// reason is payment_guard_override_not_allowed or payment_guard_pricing_floor_violation
}
}
}Payment Guard UX Helpers
Use payment helpers so Pay/Submit state stays correct after replay/consume outcomes:
import {
attachPaymentEvidenceToGuardOrchestration,
resolveGuardPrimaryActionLabel,
reconcilePaymentEvidenceFromGuardBlocked,
} from "@xapps-platform/widget-sdk";
try {
await bridge.createRequest({ toolName: "submit_wizard_application", payload });
} catch (err) {
// Marks current payment evidence as consumed when reason is payment_receipt_already_used.
reconcilePaymentEvidenceFromGuardBlocked({ error: err });
}
const ctaLabel = resolveGuardPrimaryActionLabel({
guard: activeGuard,
submitLabel: "Submit",
payLabel: "Pay",
});Integration note:
- Host pages should use
@xapps-platform/embed-sdkpayment return helpers (resolvePaymentReturnContextpreferred) and pass payment params once on resume. - Widget pages should use
@xapps-platform/widget-sdkhelpers above to avoid stale submit state after replay-rejected evidence. @xapps-platform/marketplace-uican carry host/payment context across marketplace routes; widget-sdk remains the widget-side API surface.- Canonical payment evidence requires
xapps_payment_issuerto be present in forwardedxapps_payment_*params.
Notes
- Runtime: browser iframe context.
- React hooks are provided via
@xapps-platform/widget-sdk/react. - Guard bridge helpers:
requestGuard(...)andonGuardStatus(...). - See package/runtime ownership:
docs/guides/12-package-usage-and-ownership.md.
