Zum Inhalt springen

UIAP SDK API

FeldWert
StatusDraft
Version0.1
Datum2026-03-27
Abhängigkeiten[UIAP-CORE], [UIAP-WEB]
EditorenPatrick

UIAP SDK API v0.1 definiert die Einbau- und Laufzeit-API für Web-Anwendungen, die:

  • einen PageGraph publizieren,
  • Actions registrieren,
  • Policy lokal auswerten,
  • Signale emittieren,
  • Frame-Bridges betreiben,
  • optionale Präsentation wie Overlay/Ghost Cursor rendern.

Das SDK ist der First-Party-Layer in der App. Genau dort gehört die Kontrolle hin, weil CSP externe Skripte begrenzen kann und die Same-Origin-Policy direkten DOM-Zugriff über Origins einschränkt. Für Cross-Origin-Kommunikation ist postMessage() der richtige Bridge-Mechanismus. ([MDN Web Docs][3])

Die Schlüsselwörter MUSS, DARF NICHT, SOLLTE, KANN in diesem Dokument sind normativ gemäss RFC 2119 und BCP 14 zu interpretieren, wenn und nur wenn sie in GROSSBUCHSTABEN erscheinen.

package "@uiap/web-sdk"
package "@uiap/web-sdk/overlay"
package "@uiap/web-sdk/frame-bridge"
  1. ESM / npm / first-party bundle Empfohlen für produktive Apps.

  2. CDN script Nur wenn script-src das erlaubt.

  3. Frame bridge Für kooperierende Child-/Parent-Kontexte via postMessage().

  4. External driver adapter Optional für Out-of-Process-Steuerung via WebDriver oder WebDriver BiDi. ([MDN Web Docs][3])


interface UIAPConfig {
app: {
id: string;
version: string;
profile: "[email protected]";
locale?: string;
};
transport: UIAPTransport;
observe?: ObserveConfig;
policy?: SDKPolicyConfig | false;
overlay?: OverlayConfig | false;
frames?: FrameConfig | false;
annotations?: AnnotationConfig;
debug?: boolean | "info" | "warn" | "error";
}
interface UIAPClient {
start(): Promise<void>;
stop(): Promise<void>;
destroy(): Promise<void>;
getSnapshot(input?: SnapshotOptions): Promise<PageGraph>;
publishSnapshot(input?: SnapshotOptions): Promise<PageGraph>;
emitSignal(signal: WebSignal): void;
registerAction(descriptor: ActionDescriptor, handler: ActionHandler): Unsubscribe;
unregisterAction(actionId: ActionId): void;
registerElementAdapter(adapter: UIAPElementAdapter): Unsubscribe;
registerPolicyEvaluator(evaluator: SDKPolicyEvaluator): Unsubscribe;
bindElement(node: Element, binding: ElementBinding): Unsubscribe;
bindScope(node: Element, binding: ScopeBinding): Unsubscribe;
setRouteProvider(provider: RouteProvider): void;
createFrameBridge(options: FrameBridgeOptions): FrameBridge;
on<T = unknown>(event: SDKEventName, listener: (payload: T) => void): Unsubscribe;
overlay?: OverlayController;
}
type Unsubscribe = () => void;

interface UIAPTransport {
send(message: UIAPEnvelope): Promise<void> | void;
close?(): Promise<void> | void;
onMessage(listener: (message: UIAPEnvelope) => void): Unsubscribe;
onError?(listener: (error: unknown) => void): Unsubscribe;
}
  • Das SDK MUSS transport-agnostisch bleiben.
  • Ein Transport MUSS Nachrichten in UIAP-Core-Envelopes liefern.
  • Das SDK DARF mehrere Transports adaptieren, solange genau einer pro aktiver Session write-primary ist.

Das SDK SOLLTE Rollen, Namen, Beschreibungen und Zustände aus Web-Semantik ableiten, nicht aus bloßer Pixel-Magie. Browser erzeugen einen Accessibility Tree mit Name, Rolle, Beschreibung und Zustand; aria-label und aria-labelledby sind zentrale Quellen für Accessible Names. Für Laufzeitänderungen ist MutationObserver das native Werkzeug. ([MDN Web Docs][4])

interface ObserveConfig {
root?: Document | ShadowRoot;
includeHidden?: boolean; // default false
includeNonInteractive?: boolean; // default false
throttleMs?: number; // default 100
maxNodes?: number; // default implementation-defined
signals?: WebSignalKind[]; // default supported set
}
interface SnapshotOptions {
scopes?: ScopeId[];
documents?: DocumentId[];
includeHidden?: boolean;
includeNonInteractive?: boolean;
}
  1. Das SDK MUSS mindestens einen Snapshot erzeugen können.
  2. Das SDK SOLLTE Deltas aus DOM-/State-Änderungen ableiten.
  3. Das SDK MUSS Bounding Boxes, Fokus und sichtbare Interaktionszustände aktualisieren können.
  4. Das SDK SOLLTE Route-, Dialog-, Toast- und Validierungssignale erkennen oder von der App entgegennehmen.

interface ElementBinding {
id: StableId;
scopeId?: ScopeId;
meaning?: string;
name?: string;
defaultAction?: ActionId;
risk?: RiskLevel;
sensitive?: boolean;
success?: SuccessSignal[];
metadata?: Record<string, unknown>;
}
interface ScopeBinding {
id: ScopeId;
kind: ScopeKind;
parentScopeId?: ScopeId;
name?: string;
metadata?: Record<string, unknown>;
}
  • bindElement() und bindScope() SOLLTEN die entsprechenden data-uiap-*-Metadaten spiegeln oder intern registrieren.
  • Explizite Bindings MÜSSEN Heuristiken schlagen.
  • Ein SDK DARF Bindings sowohl über DOM-Attribute als auch über in-memory Registry führen.
data-uiap-id="video.submit"
data-uiap-scope="video.create.form"
data-uiap-meaning="use_case"
data-uiap-action="video.create"
data-uiap-risk="confirm"
data-uiap-sensitive="true"
data-uiap-ignore="true"

interface ActionHandlerContext {
actionHandle: string;
action: ActionDescriptor;
target?: ResolvedTarget;
args: Record<string, unknown>;
snapshot: PageGraph;
policy?: PolicyDecision;
emitSignal(signal: WebSignal): void;
requestConfirmation(input: ActionConfirmationRequestPayload): Promise<"granted" | "denied">;
waitForUser(note: string): Promise<void>;
overlay?: OverlayController;
}
type ActionHandlerResult =
| {
status: "succeeded";
returnValue?: Record<string, unknown>;
sideEffectState?: "none" | "applied" | "unknown";
verification?: VerificationOutcome;
}
| {
status: "failed";
error: RuntimeErrorDescriptor;
sideEffectState?: "none" | "applied" | "unknown";
};
type ActionHandler =
(ctx: ActionHandlerContext) => Promise<ActionHandlerResult>;
  1. registerAction() MUSS Domain Actions und primitive Actions unterstützen.
  2. Für registrierte Domain Actions SOLLTE appAction bevorzugt werden.
  3. Handler MÜSSEN sideEffectState korrekt setzen.
  4. Nicht-idempotente Handler SOLLTEN keine stillen Retries ausführen.

interface SDKPolicyConfig {
mode?: "local-only" | "delegated" | "hybrid"; // default hybrid
document?: PolicyDocument;
}
type SDKPolicyEvaluator =
(context: PolicyContext) => Promise<PolicyDecision> | PolicyDecision;
  • local-only: Entscheidung ausschließlich im SDK
  • delegated: Entscheidung an externe Policy-Gegenstelle
  • hybrid: lokale Preflight-Regeln plus optionale Delegation
  1. Das SDK MUSS vor jeder Action mindestens eine Policy-Entscheidung haben.
  2. Eine lokale deny-Entscheidung MUSS final sein.
  3. confirm MUSS in den Runtime-Confirmation-Flow überführt werden.
  4. handoff MUSS waitForUser() oder äquivalente UX auslösen.

Das SDK SOLLTE bei requireUserActivation den Browserzustand berücksichtigen. navigator.userActivation macht diesen Zustand abfragbar; genau deshalb gehört diese Prüfung lokal ins SDK und nicht erst in irgendein spätes Backend-Orakel. ([MDN Web Docs][2])


Für komplexe Widgets, Design-System-Komponenten und freundliche Frontend-Exzesse, die visuell wie Controls aussehen, semantisch aber halbe Kunstinstallationen sind, braucht das SDK Adapter.

interface UIAPElementAdapter {
id: string;
match(node: Element): boolean;
describe(
node: Element,
ctx: {
route?: RouteContext;
scopeId?: ScopeId;
}
): Partial<UIElement> | null;
execute?(
node: Element,
request: ActionRequestPayload,
ctx: ActionHandlerContext
): Promise<ActionHandlerResult | null>;
}
  • Adapter SOLLTEN vor generischen Heuristiken laufen.
  • Adapter DÜRFEN zusätzliche Semantik und Actions publizieren.
  • Adapter SOLLTEN für Custom Components und Open Shadow Roots genutzt werden.

Open und closed Shadow Roots müssen unterschiedlich behandelt werden: offene Roots können vom Skript traversiert werden, geschlossene nicht. Für closed Shadow DOM braucht ihr deshalb entweder Host-seitige Bindings oder explizite Adapter auf dem Host, statt zu hoffen, dass JavaScript plötzlich Gedanken lesen lernt. ([MDN Web Docs][5])


interface RouteProvider {
getRoute(): RouteContext;
subscribe?(listener: (route: RouteContext) => void): Unsubscribe;
}
  • Das SDK SOLLTE einen App-spezifischen routeId bevorzugen.
  • Wenn kein Provider gesetzt ist, DARF das SDK URL-basierte Fallbacks nutzen.

interface SignalEmitter {
emitSignal(signal: WebSignal): void;
}
  • route.changed
  • toast.shown
  • status.changed
  • validation.changed
  • dialog.opened
  • dialog.closed
  • submission.started
  • submission.finished

Das SDK SOLLTE zusätzlich DOM-seitige Integrationspunkte über CustomEvent anbieten, weil CustomEvent.detail App-Daten sauber mittransportieren kann. ([MDN Web Docs][6])

Empfohlene Events:

  • uiap:ready
  • uiap:snapshot
  • uiap:signal
  • uiap:action-request
  • uiap:action-result
  • uiap:policy-decision

Cross-Origin-DOM bleibt durch die Same-Origin-Policy begrenzt. Wenn Parent und Child zusammenarbeiten sollen, muss die Bridge deshalb auf Messaging beruhen, nicht auf direktem DOM-Zugriff. window.postMessage() ist dafür der native Mechanismus. ([MDN Web Docs][7])

interface FrameConfig {
enabled?: boolean;
channel?: string; // default "uiap"
}
interface FrameBridgeOptions {
mode: "parent" | "child";
targetWindow: Window;
targetOrigin: string;
channel?: string;
}
interface FrameBridge {
connect(): void;
disconnect(): void;
send(message: UIAPEnvelope): void;
}
  1. targetOrigin MUSS explizit gesetzt werden.
  2. Eine Bridge MUSS eingehende origin und channel prüfen.
  3. Cross-Origin-Frames ohne Bridge MÜSSEN als opaque behandelt werden.
  4. Bridged Frames SOLLTEN eine eigene documentId und optional bridgeSessionId publizieren.

interface OverlayConfig {
ghostCursor?: boolean;
highlightStyle?: "outline" | "spotlight" | "pulse";
narration?: boolean;
zIndex?: number;
}
interface OverlayController {
showCursor(target: ResolvedTarget, options?: { pace?: "instant" | "humanized" }): void;
highlight(target: ResolvedTarget, options?: { style?: "outline" | "spotlight" | "pulse" }): void;
announce(text: string): void;
clear(): void;
}
  • Overlay ist Präsentation, nicht Wahrheit.
  • Overlay DARF nie Erfolg vortäuschen, wenn action.result fehlt.
  • ghostCursor SOLLTE einen separaten visuellen Cursor verwenden.

interface ExternalDriverAdapter {
execute(
request: ActionRequestPayload,
target: ResolvedTarget | undefined
): Promise<ActionHandlerResult>;
}

Für Out-of-Process-Automation DARF das SDK einen Driver-Adapter verwenden, der auf WebDriver oder WebDriver BiDi aufsetzt. Wichtig ist nur, dass der UIAP-Lifecycle gleich bleibt: gleiche Policy, gleiche Verification, gleiche Fehlerklassen. ([W3C][8])


type SDKEventName =
| "ready"
| "snapshot"
| "signal"
| "action:accepted"
| "action:progress"
| "action:result"
| "policy:decision"
| "error";

Ein konformes @uiap/web-sdk MUSS:

  • createUIAP() oder äquivalente Initialisierung anbieten,
  • Snapshots publizieren können,
  • mindestens registerAction, registerPolicyEvaluator, bindElement, bindScope, emitSignal unterstützen,
  • lokale Policy-Entscheidungen durchsetzen,
  • offene/geschlossene Shadow-Boundaries korrekt behandeln,
  • opaque Cross-Origin-Kontexte korrekt als unzugänglich markieren,
  • Action- und Policy-Lifecycle-Events emittieren.

Ein konformes SDK SOLLTE:

  • Deltas publizieren,
  • MutationObserver nutzen,
  • CustomEvent-Hooks anbieten,
  • postMessage-Frame-Bridges unterstützen,
  • Overlay optional anbieten.

import { createUIAP } from "@uiap/web-sdk";
const uiap = createUIAP({
app: {
id: "videoland",
version: "1.4.2",
profile: "[email protected]",
locale: "de-CH"
},
transport: myTransport,
observe: {
throttleMs: 120,
includeHidden: false,
includeNonInteractive: false
},
policy: {
mode: "hybrid",
document: {
modelVersion: "0.1",
extension: "uiap.policy",
defaults: {
onSafeRisk: "allow",
onConfirmRisk: "confirm",
onBlockedRisk: "handoff",
onUnknownAction: "deny",
onSensitiveRead: "confirm",
onSecretRead: "deny"
},
rules: []
}
},
overlay: {
ghostCursor: true,
highlightStyle: "spotlight",
narration: true
}
});
uiap.bindScope(document.querySelector("[data-form='video-create']")!, {
id: "video.create.form",
kind: "form",
name: "Video erstellen"
});
uiap.bindElement(document.querySelector("#title")!, {
id: "video.title",
scopeId: "video.create.form",
meaning: "title"
});
uiap.bindElement(document.querySelector("#submit")!, {
id: "video.submit",
scopeId: "video.create.form",
defaultAction: "video.create",
risk: "confirm",
success: [
{ kind: "route.changed", pattern: "/videos/:id" },
{ kind: "toast.contains", text: "erstellt" }
]
});
uiap.registerAction(
{
id: "video.create",
kind: "domain",
targetKinds: ["scope"],
executionModes: ["appAction", "semanticUi"],
risk: { level: "confirm", tags: ["external_effect"] }
},
async (ctx) => {
const granted = await ctx.requestConfirmation({
actionHandle: ctx.actionHandle,
actionId: "video.create",
risk: { level: "confirm", tags: ["external_effect"] },
preview: { summary: "Video erstellen" }
});
if (granted !== "granted") {
return {
status: "failed",
sideEffectState: "none",
error: {
code: "confirmation_denied",
message: "Aktion wurde nicht bestätigt"
}
};
}
// fachliche App-Aktion
await app.actions.createVideo();
ctx.emitSignal({
signalId: "sig_1",
kind: "toast.shown",
level: "success",
text: "Video erstellt"
});
return {
status: "succeeded",
sideEffectState: "applied"
};
}
);
uiap.registerPolicyEvaluator((context) => {
if (context.dataClasses?.includes("credential")) {
return {
decision: "handoff",
reasonCodes: ["credential_data", "human_actor_required"]
};
}
if (context.risk?.level === "confirm") {
return {
decision: "confirm",
reasonCodes: ["risk_confirm"]
};
}
return {
decision: "allow",
reasonCodes: ["policy_default"]
};
});
await uiap.start();

  • [UIAP-CORE] UIAP Core v0.1
  • [UIAP-WEB] UIAP Web Profile v0.1
  • [RFC2119] Key words for use in RFCs to Indicate Requirement Levels, BCP 14
  • Das SDK MUSS im gleichen Origin-Kontext wie die Host-Anwendung laufen; externe Skript-Injection DARF NICHT ermöglicht werden.
  • Transport-Credentials DÜRFEN NICHT im Client-seitigen Code hardcodiert werden.
  • targetOrigin bei Frame-Bridges MUSS explizit gesetzt werden, niemals "*".
  • Policy-Evaluatoren MÜSSEN lokal ausgeführt werden, bevor Actions an den Transport übergeben werden.
  • Overlay-Elemente DÜRFEN keine echten UI-Elemente der Host-Anwendung verdecken oder Klicks abfangen.
VersionDatumÄnderungen
0.12026-03-27Initialer Entwurf