@harness-fe/runtime 3.3.0 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Overlay plugin registry.
3
+ *
4
+ * Lets developers extend the in-page "H" overlay with their own action buttons
5
+ * — e.g. "send this scene to Slack" / "create a Jira issue" — without forking
6
+ * `@harness-fe/runtime`. A plugin is just a button label + an `onClick(ctx)`
7
+ * handler; the handler receives an {@link OverlayPluginContext} with on-demand,
8
+ * redaction-aware access to the current scene, logs, screenshot, and selected
9
+ * element.
10
+ *
11
+ * Registration order doesn't matter: plugins registered before the overlay
12
+ * mounts are buffered here, and the overlay subscribes so anything registered
13
+ * later renders immediately. See `index.ts` for the `window.HarnessFE` global
14
+ * and the `window.__HARNESS_FE_PLUGINS__` pre-boot queue.
15
+ */
16
+
17
+ import type {
18
+ PageLoadPayload,
19
+ ConsoleEntry,
20
+ NetworkEntry,
21
+ ErrorEntry,
22
+ TaskAttachment,
23
+ } from '@harness-fe/protocol';
24
+
25
+ /** Selector descriptor for a picked element (mirrors TaskSubmitPayload.selector). */
26
+ export interface OverlayPluginSelector {
27
+ /** Source location `file:line:col` from the build transform, if present. */
28
+ loc?: string;
29
+ /** Component display name from the build transform, if present. */
30
+ comp?: string;
31
+ /** Best-effort CSS path. */
32
+ css: string;
33
+ }
34
+
35
+ /** The element the user picked (only present for `requiresElement` plugins). */
36
+ export interface OverlayPluginSelectedElement {
37
+ /** Live DOM node. */
38
+ el: Element;
39
+ selector: OverlayPluginSelector;
40
+ /** outerHTML with internal instrumentation attrs stripped + truncated. */
41
+ outerHTML: string;
42
+ rect: { x: number; y: number; width: number; height: number };
43
+ }
44
+
45
+ export interface OverlayPluginLogs {
46
+ console: ConsoleEntry[];
47
+ network: NetworkEntry[];
48
+ errors: ErrorEntry[];
49
+ }
50
+
51
+ export interface OverlayPluginGetLogsOptions {
52
+ /** Max console entries (newest last). Default 0 — pass a count to include. */
53
+ console?: number;
54
+ /** Max network entries. Default 0. */
55
+ network?: number;
56
+ /** Max error entries. Default 0. */
57
+ errors?: number;
58
+ /**
59
+ * When `true` (the default), network entries are reduced to metadata:
60
+ * request/response bodies are dropped and `authorization` / `cookie`
61
+ * headers are stripped. Set `false` to receive raw entries — only do this
62
+ * when you control the destination, as bodies may contain secrets.
63
+ */
64
+ redact?: boolean;
65
+ }
66
+
67
+ /**
68
+ * Per-invocation context handed to a plugin's `onClick`. Getters are lazy: the
69
+ * snapshot / logs / screenshot are only computed when you call them, so a
70
+ * plugin pays for exactly what it uses.
71
+ */
72
+ export interface OverlayPluginContext {
73
+ readonly projectId: string;
74
+ readonly displayName?: string;
75
+ readonly buildId?: string;
76
+ readonly parentProjectId?: string;
77
+ readonly sessionId: string;
78
+ readonly tabId: string;
79
+ readonly visitorId?: string;
80
+ readonly userId?: string;
81
+ /** `location.href` at click time. */
82
+ readonly url: string;
83
+ readonly connectionState: 'connecting' | 'open' | 'closed';
84
+ /** Deep link to this session in the daemon dashboard, if the address is known. */
85
+ readonly dashboardUrl?: string;
86
+ /** Present only for plugins declared with `requiresElement: true`. */
87
+ readonly selectedElement?: OverlayPluginSelectedElement;
88
+
89
+ /** Shareable markdown summary (project / build / session / tab / url / time / conn). */
90
+ snapshotMarkdown(): string;
91
+ /** Structured page-load snapshot: page / viewport / storage / performance. */
92
+ snapshot(): PageLoadPayload;
93
+ /** Recent buffered logs. Redacted by default — see {@link OverlayPluginGetLogsOptions}. */
94
+ getLogs(opts?: OverlayPluginGetLogsOptions): OverlayPluginLogs;
95
+ /** Rasterize an element (default: the picked element, else `document.body`) to a PNG attachment. */
96
+ captureScreenshot(el?: Element): Promise<TaskAttachment | null>;
97
+ /** Daemon RPC over the whitelisted channel (e.g. `tasks.mine`). Undefined if unavailable. */
98
+ query?: <TResult = unknown>(method: string, args?: unknown) => Promise<TResult>;
99
+
100
+ /** Copy text to the clipboard (best-effort). */
101
+ copyToClipboard(text: string): Promise<void>;
102
+ /** Show a brief feedback toast on the overlay. */
103
+ toast(message: string, kind?: 'ok' | 'error'): void;
104
+ }
105
+
106
+ export interface OverlayPlugin {
107
+ /** Unique id. Re-registering the same id replaces the previous plugin. */
108
+ id: string;
109
+ /** Button label shown in the info card. */
110
+ label: string;
111
+ /** Optional leading icon (emoji or single char). */
112
+ icon?: string;
113
+ /**
114
+ * When `true`, clicking the button first enters element-picker mode; the
115
+ * picked element is then available as `ctx.selectedElement` in `onClick`.
116
+ */
117
+ requiresElement?: boolean;
118
+ /** Invoked when the button is clicked. May be async; rejections are toasted. */
119
+ onClick(ctx: OverlayPluginContext): void | Promise<void>;
120
+ }
121
+
122
+ const plugins = new Map<string, OverlayPlugin>();
123
+ const listeners = new Set<() => void>();
124
+
125
+ function notify(): void {
126
+ for (const fn of listeners) {
127
+ try {
128
+ fn();
129
+ } catch {
130
+ /* a bad listener must not break registration */
131
+ }
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Register an overlay plugin. Returns an unregister function (handy for HMR
137
+ * cleanup). Registering an id that already exists replaces it.
138
+ */
139
+ export function registerOverlayPlugin(plugin: OverlayPlugin): () => void {
140
+ if (!plugin || typeof plugin.id !== 'string' || plugin.id === '') {
141
+ throw new Error('registerOverlayPlugin: plugin.id is required');
142
+ }
143
+ if (typeof plugin.onClick !== 'function') {
144
+ throw new Error(`registerOverlayPlugin: plugin "${plugin.id}" needs an onClick handler`);
145
+ }
146
+ plugins.set(plugin.id, plugin);
147
+ notify();
148
+ return () => {
149
+ if (plugins.get(plugin.id) === plugin) {
150
+ plugins.delete(plugin.id);
151
+ notify();
152
+ }
153
+ };
154
+ }
155
+
156
+ /** Current plugins, in registration order. */
157
+ export function getOverlayPlugins(): OverlayPlugin[] {
158
+ return [...plugins.values()];
159
+ }
160
+
161
+ /** Subscribe to registry changes (add / replace / remove). Returns an unsubscribe fn. */
162
+ export function subscribeOverlayPlugins(fn: () => void): () => void {
163
+ listeners.add(fn);
164
+ return () => {
165
+ listeners.delete(fn);
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Drain a pre-boot queue of plugins. Developers whose script may run before the
171
+ * runtime loads can push plain plugin objects onto
172
+ * `window.__HARNESS_FE_PLUGINS__`; `index.ts` calls this once on boot.
173
+ */
174
+ export function drainPluginQueue(queue: unknown): void {
175
+ if (!Array.isArray(queue)) return;
176
+ for (const p of queue) {
177
+ try {
178
+ registerOverlayPlugin(p as OverlayPlugin);
179
+ } catch {
180
+ /* skip malformed queued entries */
181
+ }
182
+ }
183
+ }
184
+
185
+ /** Test-only: clear all plugins and listeners. */
186
+ export function __resetOverlayPlugins(): void {
187
+ plugins.clear();
188
+ listeners.clear();
189
+ }