Plugin SDK Reference

Complete reference for the host-plugin communication SDK — PluginBridge, message types, bridge classes, and Tier 1/2 development guides.

The Plugin SDK provides all infrastructure for bidirectional communication between the SCRIPE host application and plugin frontends. Tier 2 plugins communicate via iframe postMessage; Tier 1 plugins use Module Federation with shared React.

Message Protocol

All host-plugin communication uses a typed union of messages. The host sends HostToPluginMessage; the plugin sends PluginToHostMessage.

types.tsTypeScript
// Messages the HOST sends TO the plugin iframe
type HostToPluginMessage =
  | { type: "THEME_UPDATE";     payload: { mode: "dark"|"light"; accent: string; direction: "ltr"|"rtl" } }
  | { type: "AUTH_TOKEN";       payload: { accessToken: string; expiresAt: number } }
  | { type: "LANGUAGE_CHANGE";  payload: { language: "en"|"ar"; direction: "ltr"|"rtl" } }
  | { type: "TENANT_CONTEXT";   payload: { tenantId: string; tenantName: string; features: string[] } }
  | { type: "NAVIGATE_CONFIRMED"; payload: { path: string } }
  | { type: "PLUGIN_EVENT";     payload: { eventType: string; data: unknown } };

// Messages the PLUGIN sends TO the host
type PluginToHostMessage =
  | { type: "READY" }
  | { type: "NAVIGATE_REQUEST"; payload: { path: string } }
  | { type: "TOAST";            payload: { variant: "success"|"error"|"warning"|"info"; title: string; message?: string } }
  | { type: "RESIZE";           payload: { height: number } }
  | { type: "AUTH_TOKEN_REQUEST" }
  | { type: "OPEN_DIALOG";      payload: { title: string; content: string } }
  | { type: "CLOSE";            payload?: { reason?: string } };

PluginBridge

PluginBridge is the low-level communication channel. It validates event.origin on every incoming message to prevent spoofing, and targets messages to the correct iframe window.

PluginBridge usageTypeScript
const iframeRef = useRef<HTMLIFrameElement>(null);
const bridge = new PluginBridge("https://plugin.example.com", iframeRef);

bridge.mount(); // starts listening to window messages

const unsub = bridge.onMessage((msg) => {
  if (msg.type === "READY") console.log("Plugin loaded!");
  if (msg.type === "TOAST") showToast(msg.payload);
});

bridge.send({ type: "THEME_UPDATE", payload: { mode: "dark", accent: "#6366f1", direction: "ltr" } });

// Cleanup
bridge.unmount();
unsub();

PluginFrame

PluginFrame is the React component that renders a Tier 2 plugin in a sandboxed iframe. It automatically creates a PluginBridge, handles READY/RESIZE/NAVIGATE_REQUEST/TOAST messages, and shows a skeleton while loading.

PluginFrame usageTSX
import { PluginFrame } from "@core/plugins/plugin-sdk/PluginFrame";

<PluginFrame
  pluginKey="my-tier2-plugin"
  frontendUrl="https://plugin.example.com/ui"
  installationId={installationId}
  className="min-h-[500px]"
/>

Sandboxed Execution Lifecycle

Isolated runtime execution environment executing plugin logic with strict CPU, memory, network, and permission boundaries.

Iframe Sandbox Mount — Initialize secure origin isolation container inside parent viewport.
Frame to Handshake
PostMessage Handshake — Establish bi-directional bridge protocol with origin validation.
Handshake to Token Mint
Scoped Access Token — Mint short-lived OAuth token restricted to granted plugin permissions.
Token to Gateway Proxy
Gateway Proxy — Route authenticated API requests through tenant rate-limited proxy.
Proxy to Sandbox Check
Sandbox Enforcement — Apply CSP and iframe sandbox restrictions preventing unauthorized access.
Sandbox to Audit Log
Telemetry & Audit — Record execution metrics and security events to append-only audit stream.

Bridge Classes

Each bridge class handles a specific concern. Mount them after creating a PluginBridge and unmount on cleanup.

ClassResponsibilityMessage Handled
PluginThemeSyncPush host theme (mode, accent, direction) to iframeTHEME_UPDATE
PluginAuthRelayServe scoped tokens when iframe requests oneAUTH_TOKEN_REQUEST → AUTH_TOKEN
PluginNavigationBridgeAllow iframe to trigger host-side navigationNAVIGATE_REQUEST → NAVIGATE_CONFIRMED
PluginToastBridgeForward iframe toast requests to host notification systemTOAST
Mounting all relaysTypeScript
const navBridge   = new PluginNavigationBridge(bridge, (path) => router.push(path));
const toastBridge = new PluginToastBridge(bridge, { success, error, info, warning });
const authRelay   = new PluginAuthRelay(bridge, () => fetchScopedToken(installationId));

navBridge.mount();
toastBridge.mount();
authRelay.mount();

// On unmount:
return () => { navBridge.unmount(); toastBridge.unmount(); authRelay.unmount(); };

PluginHostProvider

PluginHostProvider is a React context that wires all bridges and relays together. Wrap plugin pages with it to provide createBridgeFor, syncTheme, and mountRelays to child components.

PluginHostProvider setupTSX
import { PluginHostProvider } from "@core/plugins/plugin-host/PluginHostProvider";

// In your root layout or plugin page:
<PluginHostProvider getPluginToken={(installationId) => fetchPluginToken(installationId)}>
  <PluginFrame ... />
</PluginHostProvider>

// Inside a child component:
const { createBridgeFor, syncTheme, mountRelays } = usePluginHostContext();

PluginEventBus

The in-process PluginEventBus allows any part of the host application to react to plugin lifecycle events without direct coupling. A singleton pluginEventBus is exported for convenience.

PluginEventBusTypeScript
import { pluginEventBus } from "@core/plugins/plugin-sdk/PluginEventBus";

// Subscribe anywhere
const unsub = pluginEventBus.on<{ installationId: string }>("plugin.activated", (data) => {
  console.log("Plugin activated:", data.installationId);
});

// Emit from lifecycle handlers
pluginEventBus.emit("plugin.activated", { installationId: "abc-123" });

// Cleanup
unsub();

Tier 1 Plugin Development

Tier 1 plugins integrate at the .NET level via IPluginStartup and at the frontend level via Module Federation. They share the host's React instance.

C#
// Implement IPluginStartup in your plugin project
public class MyPluginStartup : IPluginStartup
{
    public void ConfigureServices(IServiceCollection services, IConfiguration config)
    {
        services.AddScoped<IMyService, MyService>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // optional middleware
    }
}

Tier 2 Plugin Development

Tier 2 plugins are independent web applications hosted at their own URL. The host embeds them in a sandboxed iframe. The plugin must implement the postMessage protocol.

Tier 2 iframe contractTypeScript
// 1. Announce readiness (required — host shows skeleton until received)
window.parent.postMessage({ type: "READY" }, "*");

// 2. Request a scoped auth token
window.parent.postMessage({ type: "AUTH_TOKEN_REQUEST" }, "*");

// 3. Listen for host messages (ALWAYS validate origin)
window.addEventListener("message", (event) => {
  if (event.origin !== "https://your-scripe-host.com") return;

  const msg = event.data;
  if (msg.type === "AUTH_TOKEN") {
    myApiClient.setToken(msg.payload.accessToken);
  }
  if (msg.type === "THEME_UPDATE") {
    document.documentElement.dataset.theme = msg.payload.mode;
    document.dir = msg.payload.direction;
  }
});

// 4. Trigger host notifications
window.parent.postMessage({ type: "TOAST", payload: { variant: "success", title: "Saved!" } }, "*");

// 5. Request internal navigation (only "/" prefix allowed)
window.parent.postMessage({ type: "NAVIGATE_REQUEST", payload: { path: "/plugins/installed" } }, "*");

// 6. Resize the container
window.parent.postMessage({ type: "RESIZE", payload: { height: 800 } }, "*");

Data Store API

Tier 2 plugins get an isolated key-value store. All keys are scoped to the installation ID + namespace. Maximum value size is 64KB.

Data Store APIHTTP
# Upsert a key (max 64KB value)
PUT /api/v1/plugin-api/v1/data/{installationId}/{namespace}/{key}
X-Plugin-Api-Key: {your-api-key}
Content-Type: application/json
Body: { "value": "{\"language\":\"en\"}" }

# Read all keys in namespace
GET /api/v1/plugin-api/v1/data/{installationId}/{namespace}

# Delete a key
DELETE /api/v1/plugin-api/v1/data/{installationId}/{namespace}/{key}