@monad-inc/embed 0.2.0 → 0.3.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.
- package/CHANGELOG.md +18 -0
- package/USAGE.md +74 -8
- package/dist/connect/index.cjs +2 -1
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +81 -5
- package/dist/connect/index.d.ts +81 -5
- package/dist/connect/index.js +2 -1
- package/dist/connect/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 11e82ea: Docs: refer to Monad "teams" as "sub-orgs" (sub-organizations) in the README, USAGE guide, OpenAPI spec and JSDoc. No API changes.
|
|
8
|
+
|
|
9
|
+
## 0.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 8998710: Add `configMetaOverrides` for customizing connector field copy.
|
|
14
|
+
|
|
15
|
+
A new optional, backward-compatible option on `createConnectorFrame` (and the underlying `init` protocol message) that overrides the `name`, `description`, and `placeholder` of individual config-form fields, so embedders can word the form to fit their own product.
|
|
16
|
+
|
|
17
|
+
- Keys mirror the connector-type response, so they are the connector's own field keys (the Datadog output's source field is `ddsource`, not `source`). Keys that don't match the connector are ignored.
|
|
18
|
+
- Nested fields are reached with `children`, and multiple-choice variants with `discriminator.one_of`.
|
|
19
|
+
- Copy only: a field's type, requiredness, allowed values, and default are fixed by the connector. `placeholder` is a hint — a field the user never touches still submits an empty value.
|
|
20
|
+
|
|
3
21
|
## 0.2.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/USAGE.md
CHANGED
|
@@ -22,7 +22,7 @@ Zero runtime dependencies.
|
|
|
22
22
|
## How it works
|
|
23
23
|
|
|
24
24
|
Two pieces of code: a **session-mint endpoint on your backend** mints
|
|
25
|
-
a short-lived,
|
|
25
|
+
a short-lived, sub-org-scoped token from your long-lived Monad API key,
|
|
26
26
|
and a **`createConnectorFrame()` call on your frontend** mounts the
|
|
27
27
|
iframe using that token. Both shown below.
|
|
28
28
|
|
|
@@ -31,14 +31,14 @@ iframe using that token. Both shown below.
|
|
|
31
31
|
## Step 1 — Mint a session token from your backend
|
|
32
32
|
|
|
33
33
|
Your backend exchanges its long-lived Monad API key for a short-lived,
|
|
34
|
-
|
|
34
|
+
sub-org-scoped session token, then hands that token to your frontend.
|
|
35
35
|
|
|
36
36
|
Example (any HTTP framework — shown here as a Next.js Route Handler):
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
39
|
// app/api/monad/session/route.ts
|
|
40
40
|
export async function POST(req: Request) {
|
|
41
|
-
const {
|
|
41
|
+
const { subOrgId } = await req.json(); // your tenant → Monad sub-org mapping
|
|
42
42
|
|
|
43
43
|
const r = await fetch('https://app.monad.com/api/v3/sessions', {
|
|
44
44
|
method: 'POST',
|
|
@@ -47,12 +47,12 @@ export async function POST(req: Request) {
|
|
|
47
47
|
Authorization: `ApiKey ${process.env.MONAD_API_KEY}`
|
|
48
48
|
},
|
|
49
49
|
body: JSON.stringify({
|
|
50
|
-
organization_id:
|
|
50
|
+
organization_id: subOrgId,
|
|
51
51
|
ttl_seconds: 1800
|
|
52
52
|
})
|
|
53
53
|
});
|
|
54
54
|
const { session_token, expires_at } = await r.json();
|
|
55
|
-
return Response.json({ session_token, expires_at, organizationId:
|
|
55
|
+
return Response.json({ session_token, expires_at, organizationId: subOrgId });
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -79,7 +79,7 @@ import { createConnectorFrame } from '@monad-inc/embed/connect';
|
|
|
79
79
|
// Fetch a fresh session from your own backend.
|
|
80
80
|
const { session_token, organizationId } = await fetch('/api/monad/session', {
|
|
81
81
|
method: 'POST',
|
|
82
|
-
body: JSON.stringify({
|
|
82
|
+
body: JSON.stringify({ subOrgId: currentUser.subOrgId })
|
|
83
83
|
}).then((r) => r.json());
|
|
84
84
|
|
|
85
85
|
const frame = createConnectorFrame({
|
|
@@ -109,7 +109,7 @@ controls the surrounding chrome.
|
|
|
109
109
|
| ----------------------- | ---------------------------- | -------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
110
110
|
| `container` | `HTMLElement \| string` | ✅ | — | Element (or CSS selector) the iframe is appended to. |
|
|
111
111
|
| `sessionToken` | `string` | ✅ | — | Short-lived token from `POST /v3/sessions` (your backend mints this). |
|
|
112
|
-
| `organizationId` | `string` | ✅ | — | Monad
|
|
112
|
+
| `organizationId` | `string` | ✅ | — | Monad sub-org id the session is scoped to. Same value you passed to `/v3/sessions`. |
|
|
113
113
|
| `kind` | `'input' \| 'output'` | ✅ | — | Which catalog the connector belongs to. |
|
|
114
114
|
| `typeId` | `string` | ✅ | — | Connector type slug, e.g. `'aws-cloudtrail'`, `'okta-systemlog'`. |
|
|
115
115
|
| `existingId` | `string` | ⛔ | — | Pass to edit an existing connector. Omit to create a new one. |
|
|
@@ -119,6 +119,8 @@ controls the surrounding chrome.
|
|
|
119
119
|
| `isNameEditable` | `boolean` | ⛔ | `false` | Show an editable name input in the iframe (prefilled with `name` if provided, else the type name). A non-empty name is required when shown. |
|
|
120
120
|
| `isDescriptionEditable` | `boolean` | ⛔ | `false` | Show an editable description input in the iframe (prefilled with `description` if provided). |
|
|
121
121
|
| `appearance` | `Appearance` | ⛔ | Monad defaults | Theming tokens (see below). |
|
|
122
|
+
| `stylesheets` | `string[]` | ⛔ | Monad's built-in stylesheet | Your own stylesheet URLs. All-or-nothing: if set, Monad's defaults are not loaded (see below). |
|
|
123
|
+
| `configMetaOverrides` | `ConfigMetaOverrides` | ⛔ | the connector's own copy | Per-field label / description / placeholder overrides (see below). |
|
|
122
124
|
| `synthetic` | `boolean` | ⛔ | `false` | Exposes Monad's internal synthetic-data toggle. Keep off in production. |
|
|
123
125
|
| `frameOrigin` | `string` | ⛔ | `https://app.monad.com/embed` | Override only for non-prod testing. |
|
|
124
126
|
| `apiBase` | `string` | ⛔ | `https://app.monad.com/api` | Override only for non-prod testing. |
|
|
@@ -159,13 +161,77 @@ control than these six tokens give, file an issue.
|
|
|
159
161
|
|
|
160
162
|
---
|
|
161
163
|
|
|
164
|
+
## Customizing field copy with `configMetaOverrides`
|
|
165
|
+
|
|
166
|
+
Every connector ships its own field labels, descriptions, and
|
|
167
|
+
placeholders. `configMetaOverrides` lets you replace that copy with
|
|
168
|
+
wording that fits your product, without changing how the form behaves.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
createConnectorFrame({
|
|
172
|
+
...,
|
|
173
|
+
typeId: 'datadog',
|
|
174
|
+
kind: 'output',
|
|
175
|
+
configMetaOverrides: {
|
|
176
|
+
settings: {
|
|
177
|
+
ddsource: { placeholder: 'northstar' },
|
|
178
|
+
service: { name: 'Service name', placeholder: 'northstar-web' },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Keys are the connector's own field keys.** They come from the
|
|
185
|
+
connector-type response, not from the labels shown in the form, so check
|
|
186
|
+
that response rather than guessing — the Datadog output's source field is
|
|
187
|
+
`ddsource`, and its tags field is `ddtags`. A key that doesn't match the
|
|
188
|
+
connector is ignored, so a typo is silent: the form renders with Monad's
|
|
189
|
+
original copy.
|
|
190
|
+
|
|
191
|
+
**`placeholder` is a hint, not a default.** It shows as grey text and
|
|
192
|
+
disappears as soon as the user types. A field the user never touches
|
|
193
|
+
still submits an empty value. There is no option to pre-fill a real
|
|
194
|
+
value, because that would change the data your connector is configured
|
|
195
|
+
with.
|
|
196
|
+
|
|
197
|
+
### What you can override
|
|
198
|
+
|
|
199
|
+
Per field: `name`, `description`, `placeholder`.
|
|
200
|
+
|
|
201
|
+
Nested fields are reached with `children`, and the variants of a
|
|
202
|
+
multiple-choice field with `discriminator.one_of`:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
configMetaOverrides: {
|
|
206
|
+
secrets: {
|
|
207
|
+
api_key: { children: { value: { placeholder: 'Datadog API key' } } },
|
|
208
|
+
},
|
|
209
|
+
settings: {
|
|
210
|
+
auth: {
|
|
211
|
+
discriminator: {
|
|
212
|
+
name: 'Auth method',
|
|
213
|
+
one_of: { basic: { children: { username: { name: 'Login' } } } },
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
A field's type, whether it is required, its allowed values, and its
|
|
221
|
+
default are fixed by the connector and cannot be overridden. Some
|
|
222
|
+
controls have nowhere to show a placeholder — dropdowns, checkboxes, and
|
|
223
|
+
code editors ignore it. If you need a field to _behave_ differently,
|
|
224
|
+
that's a change to the connector itself; file an issue.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
162
228
|
## Security model
|
|
163
229
|
|
|
164
230
|
- **Cross-origin iframe.** The form runs on `app.monad.com`, not
|
|
165
231
|
on your domain. Browser same-origin policy means your JavaScript
|
|
166
232
|
cannot read the iframe's DOM or any value the user types.
|
|
167
233
|
- **Session tokens are short-lived.** Default 30 min, scoped to a
|
|
168
|
-
single
|
|
234
|
+
single sub-org. Compromise of a session token is bounded; compromise
|
|
169
235
|
of the long-lived API key is not — keep it on your backend.
|
|
170
236
|
- **postMessage with strict origin checks.** Every message between
|
|
171
237
|
your page and the iframe is validated against the expected origin
|
package/dist/connect/index.cjs
CHANGED
|
@@ -228,7 +228,8 @@ function createConnectorFrame(opts) {
|
|
|
228
228
|
existingId: opts.existingId,
|
|
229
229
|
synthetic: opts.synthetic,
|
|
230
230
|
appearance: opts.appearance,
|
|
231
|
-
stylesheets: opts.stylesheets
|
|
231
|
+
stylesheets: opts.stylesheets,
|
|
232
|
+
configMetaOverrides: opts.configMetaOverrides
|
|
232
233
|
});
|
|
233
234
|
break;
|
|
234
235
|
case "resize": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/connect/index.ts","../../src/connect/protocol.ts","../../src/connect/lifecycle.ts"],"sourcesContent":["/**\n * @monad-inc/embed/connect — host-side SDK for embedding Monad's\n * connector-config UI as a secure iframe.\n *\n * The customer's page calls `createConnectorFrame()`; this injects an\n * <iframe> pointing at a Monad-hosted page (@monad-inc/embed-frame). The\n * connector form — and every secret the user types into it — runs\n * inside that iframe on Monad's origin. Same-origin policy means the\n * customer's own JavaScript can never read those secrets. The customer\n * gets back only a connector id (a safe reference), the same way a\n * Stripe integration only ever sees a `pm_…` token.\n *\n * The host is still responsible for one thing: minting a short-lived\n * embed session token from its backend (POST /v1/embed/sessions, with\n * the long-lived API key) and handing it to `createConnectorFrame`.\n * That token travels into the iframe over postMessage, never the URL.\n */\nimport {\n\tPROTOCOL_SOURCE,\n\tPROTOCOL_VERSION,\n\ttype Appearance,\n\ttype ComponentKind,\n\ttype FrameMessage,\n\ttype HostMessage\n} from './protocol';\n\nexport {\n\tPROTOCOL_SOURCE,\n\tPROTOCOL_VERSION,\n\ttype Appearance,\n\ttype ComponentKind,\n\ttype FrameMessage,\n\ttype HostMessage,\n\ttype InitMessage\n} from './protocol';\n\nexport {\n\tfindIntegrationPipeline,\n\tsetIntegrationEnabled,\n\tdisableIntegration,\n\tenableIntegration,\n\tdeleteIntegration,\n\tbuildDevNullPipeline,\n\tCLEANUP_FULL,\n\tCLEANUP_KEEP_OUTPUT,\n\tCLEANUP_PIPELINE_ONLY,\n\ttype MonadRequest,\n\ttype ResolvedIntegration,\n\ttype SetEnabledOptions,\n\ttype DeleteIntegrationOptions,\n\ttype CleanupPolicy,\n\ttype BuildDevNullPipelineOptions,\n\ttype BuiltPipeline\n} from './lifecycle';\n\nexport interface ConnectorFrameOptions {\n\t/** DOM node (or selector) the iframe is appended to. */\n\tcontainer: HTMLElement | string;\n\t/**\n\t * URL of the Monad embed-frame page. Defaults to\n\t * \"https://app.monad.com/embed\" (production). Override for staging\n\t * (\"https://app.monad.security/embed\") or local testing\n\t * (\"http://localhost:5173/embed\"). MUST be a different origin than\n\t * the host page — that cross-origin boundary is what isolates the\n\t * secrets. A path component is allowed (so the embed page can live\n\t * at a sub-route of the main app); the postMessage security checks\n\t * compare against the URL's origin, not its full href.\n\t */\n\tframeOrigin?: string;\n\t/**\n\t * Monad API base the iframe should call. Defaults to\n\t * \"https://app.monad.com/api\" (production). Override for non-prod\n\t * environments. Set this together with `frameOrigin` — mixing\n\t * environments will not work.\n\t */\n\tapiBase?: string;\n\t/** Connector type id, e.g. \"runzero-findings\". */\n\ttypeId: string;\n\tkind: ComponentKind;\n\t/** Short-lived embed session token (host backend mints it). */\n\tsessionToken: string;\n\t/**\n\t * Team org id the session is scoped to. The host backend already knows\n\t * this — it's the same value passed to `/v1/embed/sessions` when minting\n\t * the token. Forwarded into the iframe so it can build `/v1/<org>/*` and\n\t * `/v2/<org>/*` URLs.\n\t */\n\torganizationId: string;\n\t/** Display name shown in the frame header. */\n\tdisplayName?: string;\n\t/**\n\t * Name for the created connector. On create, defaults to the connector\n\t * type's name plus a short unique suffix. On edit, omit to keep the\n\t * existing name.\n\t */\n\tname?: string;\n\t/**\n\t * Description for the created connector. On create, defaults to the\n\t * connector type's description. On edit, omit to keep the existing\n\t * description.\n\t */\n\tdescription?: string;\n\t/**\n\t * Show an editable name input inside the iframe (prefilled with the\n\t * resolved name) so the end user can name the connector themselves.\n\t * A non-empty name is required when shown. Defaults to false.\n\t */\n\tisNameEditable?: boolean;\n\t/**\n\t * Show an editable description input inside the iframe (prefilled with\n\t * the resolved description). Defaults to false.\n\t */\n\tisDescriptionEditable?: boolean;\n\t/** Existing connector id — pass for edit mode, omit to create. */\n\texistingId?: string;\n\t/**\n\t * Whether the synthetic-data toggle is exposed in the form. Defaults\n\t * to `false` — production embed flows keep it hidden.\n\t */\n\tsynthetic?: boolean;\n\t/** Theming tokens applied as CSS variables inside the iframe. */\n\tappearance?: Appearance;\n\t/**\n\t * Stylesheet URLs to apply inside the iframe. All-or-nothing: if\n\t * any URL is provided, the iframe loads ONLY these and skips its\n\t * built-in Monad UI defaults; if omitted/empty, the iframe loads\n\t * the defaults and nothing else. URLs must be reachable from the\n\t * iframe — typically served by the host page's own origin.\n\t */\n\tstylesheets?: string[];\n\t/** Fired when the user saves successfully. */\n\tonSave?: (connector: { id: string; name?: string }) => void;\n\t/** Fired when the user cancels. */\n\tonCancel?: () => void;\n\t/** Fired on a save/test/load failure inside the iframe. */\n\tonError?: (message: string) => void;\n}\n\nexport interface ConnectorFrame {\n\t/** Remove the iframe and stop listening for its messages. */\n\tdestroy(): void;\n}\n\n/**\n * Production defaults. Customers integrating against prod don't pass\n * `frameOrigin`/`apiBase` at all; non-prod environments override both\n * together. Treat these as a public API contract — changing a hostname\n * is a breaking change for anyone on the default.\n */\nconst DEFAULT_FRAME_ORIGIN = 'https://app.monad.com/embed';\nconst DEFAULT_API_BASE = 'https://app.monad.com/api';\n\n/**\n * Inject the Monad connector iframe into `container` and wire the\n * postMessage channel. Returns a handle whose `destroy()` tears it all\n * down.\n *\n * Handshake:\n * 1. iframe loads, posts `ready`;\n * 2. host replies with `init` (session token, type, appearance);\n * 3. iframe mounts the form and streams `resize` as content grows;\n * 4. on save, iframe posts `saved {connector}`; on cancel, `cancel`.\n */\nexport function createConnectorFrame(opts: ConnectorFrameOptions): ConnectorFrame {\n\tconst container =\n\t\ttypeof opts.container === 'string'\n\t\t\t? document.querySelector<HTMLElement>(opts.container)\n\t\t\t: opts.container;\n\tif (!container) {\n\t\tthrow new Error('[monad-inc/connect] container element not found');\n\t}\n\n\tconst frameInput = opts.frameOrigin ?? DEFAULT_FRAME_ORIGIN;\n\tconst apiBase = opts.apiBase ?? DEFAULT_API_BASE;\n\n\t// Split the input URL into two pieces:\n\t// * `frameUrl` keeps the path (e.g. /embed) — used as iframe.src.\n\t// * `frameOrigin` is the bare origin — used for the postMessage\n\t// security checks below, which must compare against the value\n\t// the browser surfaces on MessageEvent.origin (always a bare\n\t// origin, never a path).\n\t// Previously these were collapsed into one `new URL(...).href`,\n\t// which silently broke any frameOrigin with a path — the equality\n\t// check `event.origin !== frameOrigin` would always fail and `init`\n\t// never made it back to the iframe.\n\tconst frameUrl = new URL(frameInput);\n\tconst frameOrigin = frameUrl.origin;\n\tconst framePath = frameUrl.pathname.replace(/\\/$/, '');\n\n\tconst iframe = document.createElement('iframe');\n\tiframe.className = 'monad-connect-frame';\n\tiframe.title = 'Monad connector configuration';\n\tiframe.style.width = '100%';\n\tiframe.style.border = '0';\n\tiframe.style.display = 'block';\n\t// The iframe needs its own scripts and same-origin access to the\n\t// Monad API; it must NOT get same-origin access to the host. A\n\t// cross-origin iframe already can't reach the host — no sandbox\n\t// attribute is needed for isolation, and adding one would block the\n\t// form's own scripts. Forms/popups stay enabled for OAuth-style\n\t// connectors that may open a window.\n\tiframe.allow = 'clipboard-write';\n\t// parentOrigin lets the iframe scope its postMessage target + sender\n\t// check. It is NOT a secret and NOT a capability — lying about it\n\t// only breaks the liar's own embed (see protocol.ts).\n\tiframe.src = `${frameOrigin}${framePath}/?parentOrigin=${encodeURIComponent(window.location.origin)}`;\n\n\tlet destroyed = false;\n\n\tfunction postToFrame(msg: HostMessage): void {\n\t\tiframe.contentWindow?.postMessage(msg, frameOrigin);\n\t}\n\n\tfunction onMessage(event: MessageEvent): void {\n\t\tif (destroyed) return;\n\t\t// --- security boundary: validate before trusting anything ---\n\t\tif (event.origin !== frameOrigin) return;\n\t\tif (event.source !== iframe.contentWindow) return;\n\t\tconst msg = event.data as FrameMessage | undefined;\n\t\tif (!msg || msg.source !== PROTOCOL_SOURCE) return;\n\n\t\tswitch (msg.type) {\n\t\t\tcase 'ready':\n\t\t\t\tpostToFrame({\n\t\t\t\t\tsource: PROTOCOL_SOURCE,\n\t\t\t\t\ttype: 'init',\n\t\t\t\t\tversion: PROTOCOL_VERSION,\n\t\t\t\t\tsessionToken: opts.sessionToken,\n\t\t\t\t\tapiBase,\n\t\t\t\t\torganizationId: opts.organizationId,\n\t\t\t\t\ttypeId: opts.typeId,\n\t\t\t\t\tkind: opts.kind,\n\t\t\t\t\tdisplayName: opts.displayName,\n\t\t\t\t\tname: opts.name,\n\t\t\t\t\tdescription: opts.description,\n\t\t\t\t\tisNameEditable: opts.isNameEditable,\n\t\t\t\t\tisDescriptionEditable: opts.isDescriptionEditable,\n\t\t\t\t\texistingId: opts.existingId,\n\t\t\t\t\tsynthetic: opts.synthetic,\n\t\t\t\t\tappearance: opts.appearance,\n\t\t\t\t\tstylesheets: opts.stylesheets\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase 'resize': {\n\t\t\t\t// Clamp to a sane range so a renderer bug in the iframe\n\t\t\t\t// can't blow up the host layout with a multi-million-px\n\t\t\t\t// height. The origin check above already gates who can\n\t\t\t\t// send this — clamping is defense in depth. Number.isFinite\n\t\t\t\t// guards against non-numeric heights that would otherwise\n\t\t\t\t// produce \"NaNpx\" and leave the iframe collapsed.\n\t\t\t\tconst h = Number.isFinite(msg.height) ? msg.height : 0;\n\t\t\t\tiframe.style.height = `${Math.min(Math.max(h, 0), 20000)}px`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'saved':\n\t\t\t\topts.onSave?.(msg.connector);\n\t\t\t\tbreak;\n\t\t\tcase 'error':\n\t\t\t\topts.onError?.(msg.message);\n\t\t\t\tbreak;\n\t\t\tcase 'cancel':\n\t\t\t\topts.onCancel?.();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\twindow.addEventListener('message', onMessage);\n\tcontainer.appendChild(iframe);\n\n\treturn {\n\t\tdestroy(): void {\n\t\t\tdestroyed = true;\n\t\t\twindow.removeEventListener('message', onMessage);\n\t\t\tiframe.remove();\n\t\t}\n\t};\n}\n","/**\n * postMessage contract between the host page (@monad-inc/embed/connect) and\n * the Monad-hosted iframe (@monad-inc/embed-frame).\n *\n * Why a protocol module: both sides must agree on message shapes, and a\n * drifting contract is a silent failure (a message just gets ignored).\n * @monad-inc/embed/connect owns it because it's the public-facing SDK; the\n * iframe app imports these types from here.\n *\n * Security model:\n * - Origin is the real boundary. Every listener validates\n * `event.origin` against an expected value before trusting a\n * message. The iframe knows its parent's origin (passed as a URL\n * param); the host knows the frame's origin (it chose it).\n * - `event.source` is also checked host-side so a sibling iframe on\n * the same origin can't impersonate ours.\n * - The `source: 'monad-embed'` tag is NOT security — it's just a\n * cheap filter so unrelated postMessage traffic (analytics SDKs,\n * devtools, wallet extensions) is ignored without parsing.\n * - The session token travels in the `init` message body, never in\n * the iframe URL, so it stays out of browser history and Referer.\n */\n\nexport const PROTOCOL_SOURCE = 'monad-embed' as const;\nexport const PROTOCOL_VERSION = 1 as const;\n\nexport type ComponentKind = 'input' | 'output';\n\n/**\n * Appearance tokens the host may pass to theme the iframe. The iframe\n * applies each as a `--monad-{token}` CSS variable. The host cannot\n * style across the iframe boundary directly (same-origin policy), so\n * this is the only theming channel — same idea as Stripe's `appearance`.\n */\nexport interface Appearance {\n\tcolorPrimary?: string;\n\tcolorText?: string;\n\tcolorBackground?: string;\n\tcolorBorder?: string;\n\tfontFamily?: string;\n\tborderRadius?: string;\n}\n\n/* ===== iframe -> host ===== */\n\nexport interface ReadyMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'ready';\n\tversion: number;\n}\n\nexport interface ResizeMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'resize';\n\t/** Content height in CSS pixels — the host sizes the <iframe> to this. */\n\theight: number;\n}\n\nexport interface SavedMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'saved';\n\t/** Safe reference to the created/updated connector — no secret material. */\n\tconnector: { id: string; name?: string };\n}\n\nexport interface ErrorMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'error';\n\tmessage: string;\n}\n\nexport interface CancelMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'cancel';\n}\n\nexport type FrameMessage =\n\t| ReadyMessage\n\t| ResizeMessage\n\t| SavedMessage\n\t| ErrorMessage\n\t| CancelMessage;\n\n/* ===== host -> iframe ===== */\n\nexport interface InitMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'init';\n\tversion: number;\n\t/** Short-lived embed session token minted by the host's backend. */\n\tsessionToken: string;\n\t/** Monad API base, e.g. \"https://api.monad.com\" or dev \"http://localhost/api\". */\n\tapiBase: string;\n\t/**\n\t * Team org id the session token is scoped to. The iframe uses this to\n\t * build `/v1/<org>/*` and `/v2/<org>/*` URLs. Must match the token's\n\t * `org` claim — server-side `org_access.Middleware` will 403 if not.\n\t */\n\torganizationId: string;\n\t/** Connector type id, e.g. \"runzero-findings\". */\n\ttypeId: string;\n\tkind: ComponentKind;\n\t/** Display name shown in the frame header. */\n\tdisplayName?: string;\n\t/**\n\t * Name for the created connector. On create, defaults to the connector\n\t * type's name plus a short unique suffix. On edit, omit to keep the\n\t * existing name.\n\t */\n\tname?: string;\n\t/**\n\t * Description for the created connector. On create, defaults to the\n\t * connector type's description. On edit, omit to keep the existing\n\t * description.\n\t */\n\tdescription?: string;\n\t/**\n\t * Show an editable name input inside the iframe, prefilled with the\n\t * resolved name, so the end user can name the connector themselves.\n\t * A non-empty name is required when shown. Defaults to false.\n\t */\n\tisNameEditable?: boolean;\n\t/**\n\t * Show an editable description input inside the iframe, prefilled with\n\t * the resolved description. Defaults to false.\n\t */\n\tisDescriptionEditable?: boolean;\n\t/** Existing connector id — present for edit mode, absent for create. */\n\texistingId?: string;\n\t/**\n\t * Whether the synthetic-data toggle is exposed in the form. Defaults\n\t * to `false` (toggle hidden). Production embed customers keep this\n\t * off — synthetic mode is a Monad-internal testing affordance.\n\t */\n\tsynthetic?: boolean;\n\t/** Theming tokens applied as CSS variables inside the iframe. */\n\tappearance?: Appearance;\n\t/**\n\t * Stylesheet URLs the host wants to apply to the iframe page. The\n\t * iframe loads these as `<link rel=\"stylesheet\">` tags before\n\t * rendering the form.\n\t *\n\t * All-or-nothing: if this array is present and non-empty, the\n\t * iframe loads ONLY these stylesheets and does NOT load its\n\t * built-in Monad UI defaults. If it's absent or empty, the iframe\n\t * loads the built-in defaults and nothing else. Mixing host CSS\n\t * with the defaults is intentionally not supported — partial\n\t * styling tends to produce confusing half-themed forms.\n\t *\n\t * URLs MUST be reachable from the iframe origin (typically by\n\t * being served from the host page's own origin, which the browser\n\t * permits for stylesheet `<link>` loads even cross-origin).\n\t */\n\tstylesheets?: string[];\n}\n\nexport type HostMessage = InitMessage;\n","/**\n * Connector-lifecycle helpers — the \"wiring harness\" a host (Okta, in\n * the demo's fiction) drops into its own backend to power Disable /\n * Enable / Delete buttons in its own UI.\n *\n * Unlike createConnectorFrame (browser-only), these are isomorphic:\n * they just sequence Monad's standard `/v2/{org}/*` + `/v1/{org}/*`\n * API calls. They run wherever the host holds its API key — almost\n * always its backend.\n *\n * The host supplies a `MonadRequest` thunk that performs an\n * authenticated request and returns parsed JSON. The harness owns the\n * non-obvious parts: which endpoint + verb each action maps to, the\n * delete ordering (pipeline before the input/output it references),\n * and resolving an input id to its pipeline without the host having to\n * store that mapping.\n */\n\n/**\n * Authenticated request against the Monad API, returning parsed JSON\n * (or undefined for empty responses). The host implements this with\n * its long-lived API key and base URL. Path is the full API path,\n * e.g. `/v2/{org}/pipelines/{id}`.\n */\nexport type MonadRequest = (path: string, init?: RequestInit) => Promise<any>;\n\n/* ===== resolve ===== */\n\n/**\n * Where an integration's resources live and their current state.\n * Returned by findIntegrationPipeline so the host can act on an\n * integration knowing only the input id.\n */\nexport interface ResolvedIntegration {\n\torg: string;\n\tpipelineId: string;\n\t/** Whether the pipeline is currently enabled. */\n\tenabled: boolean;\n\tinputId: string;\n\t/** Output id of the pipeline's destination node, if it has one. */\n\toutputId?: string;\n}\n\ninterface PipelineNode {\n\tcomponent_id?: string;\n\tcomponent_type?: string;\n}\n\n/**\n * Find the pipeline an input feeds. Lets the host act on an integration\n * (disable/enable/delete) knowing only the input id — no need to\n * persist an input→pipeline map of its own.\n *\n * The pipelines list is slim (no node wiring), so this fetches each\n * pipeline's detail and scans for the input node. Fine for the modest\n * pipeline counts a single tenant team has; a host managing many\n * pipelines per team should instead persist the `pipelineId`\n * buildDevNullPipeline returns and skip the lookup.\n *\n * Returns null when the input has no pipeline.\n */\nexport async function findIntegrationPipeline(opts: {\n\trequest: MonadRequest;\n\torg: string;\n\tinputId: string;\n}): Promise<ResolvedIntegration | null> {\n\tconst { request, org, inputId } = opts;\n\tconst listed = await request(`/v2/${org}/pipelines/`);\n\tconst pipelines: any[] = Array.isArray(listed)\n\t\t? listed\n\t\t: (listed?.pipelines ?? listed?.data ?? []);\n\n\tfor (const summary of pipelines) {\n\t\tif (!summary?.id) continue;\n\t\tlet detail: any;\n\t\ttry {\n\t\t\tdetail = await request(`/v2/${org}/pipelines/${summary.id}`);\n\t\t} catch {\n\t\t\tcontinue; // skip a pipeline we can't read rather than failing\n\t\t}\n\t\tconst pipeline = detail?.config ?? detail;\n\t\tconst nodes: PipelineNode[] = pipeline?.nodes ?? [];\n\t\tconst inputNode = nodes.find((n) => n.component_type === 'input' && n.component_id === inputId);\n\t\tif (!inputNode) continue;\n\t\tconst outputNode = nodes.find((n) => n.component_type === 'output');\n\t\treturn {\n\t\t\torg,\n\t\t\tpipelineId: summary.id,\n\t\t\tenabled: Boolean(pipeline.enabled),\n\t\t\tinputId,\n\t\t\toutputId: outputNode?.component_id\n\t\t};\n\t}\n\treturn null;\n}\n\n/* ===== enable / disable ===== */\n\nexport interface SetEnabledOptions {\n\trequest: MonadRequest;\n\torg: string;\n\tpipelineId: string;\n\t/** true → resume processing, false → stop (config is kept either way). */\n\tenabled: boolean;\n}\n\n/**\n * Enable or disable an integration's pipeline. Disabling stops data\n * flowing but destroys nothing — the input, output, and pipeline\n * config all survive, so a later enable resumes it as it was.\n *\n * The pipeline PATCH endpoint replaces the whole config rather than\n * accepting a partial, so this reads the current pipeline and sends it\n * back with only the `enabled` flag flipped.\n *\n * Race warning: this is GET-then-PATCH on the same resource, so a\n * concurrent edit between the two requests gets clobbered. Fine for\n * human-driven toggles. If you call this from automation, fan-out, or\n * a retry loop, ask the API team for a server-side `{ enabled }`\n * partial PATCH instead — the round-trip we do here is only a\n * workaround for the current full-config replace semantics.\n */\nexport async function setIntegrationEnabled(opts: SetEnabledOptions): Promise<void> {\n\tconst { request, org, pipelineId, enabled } = opts;\n\tconst detail = await request(`/v2/${org}/pipelines/${pipelineId}`);\n\tconst pipeline = detail?.config ?? detail;\n\tawait request(`/v2/${org}/pipelines/${pipelineId}`, {\n\t\tmethod: 'PATCH',\n\t\tbody: JSON.stringify({\n\t\t\tname: pipeline.name,\n\t\t\tdescription: pipeline.description ?? '',\n\t\t\tenabled,\n\t\t\tnodes: (pipeline.nodes ?? []).map((n: any) => ({\n\t\t\t\tid: n.id,\n\t\t\t\tslug: n.slug,\n\t\t\t\tcomponent_id: n.component_id,\n\t\t\t\tcomponent_type: n.component_type,\n\t\t\t\tenabled: n.enabled ?? true\n\t\t\t})),\n\t\t\tedges: (pipeline.edges ?? []).map((e: any) => ({\n\t\t\t\tname: e.name,\n\t\t\t\tdescription: e.description ?? '',\n\t\t\t\tfrom_node_instance_id: e.from_node_instance_id,\n\t\t\t\tto_node_instance_id: e.to_node_instance_id,\n\t\t\t\tdisabled: e.disabled ?? false,\n\t\t\t\tconditions: e.conditions\n\t\t\t}))\n\t\t})\n\t});\n}\n\n/** Convenience: disable the pipeline (stop without deleting). */\nexport function disableIntegration(opts: Omit<SetEnabledOptions, 'enabled'>): Promise<void> {\n\treturn setIntegrationEnabled({ ...opts, enabled: false });\n}\n\n/** Convenience: re-enable a disabled pipeline. */\nexport function enableIntegration(opts: Omit<SetEnabledOptions, 'enabled'>): Promise<void> {\n\treturn setIntegrationEnabled({ ...opts, enabled: true });\n}\n\n/* ===== delete ===== */\n\n/**\n * Which resources a delete removes. An embed integration is a pipeline\n * + an input + (in the dev/null flow) an output — but hosts provision\n * and own tenant resources differently. Some keep a shared output,\n * some keep the input so it can be re-wired later, some only ever tear\n * down the pipeline. Choose the policy that matches how you provision;\n * a flag left undefined defaults to `true` (delete it).\n *\n * Three ready-made policies are exported — CLEANUP_FULL,\n * CLEANUP_KEEP_OUTPUT, CLEANUP_PIPELINE_ONLY — or pass your own.\n */\nexport interface CleanupPolicy {\n\t/** Delete the pipeline. Default: true. */\n\tpipeline?: boolean;\n\t/** Delete the input connector. Default: true. */\n\tinput?: boolean;\n\t/** Delete the output connector. Default: true. */\n\toutput?: boolean;\n}\n\n/** Tear down everything — pipeline, input, and output. */\nexport const CLEANUP_FULL: CleanupPolicy = { pipeline: true, input: true, output: true };\n\n/** Remove the pipeline and input but keep the output (e.g. a shared sink). */\nexport const CLEANUP_KEEP_OUTPUT: CleanupPolicy = {\n\tpipeline: true,\n\tinput: true,\n\toutput: false\n};\n\n/** Remove only the pipeline; keep the input so it can be re-wired. */\nexport const CLEANUP_PIPELINE_ONLY: CleanupPolicy = {\n\tpipeline: true,\n\tinput: false,\n\toutput: false\n};\n\nexport interface DeleteIntegrationOptions {\n\trequest: MonadRequest;\n\torg: string;\n\t/** Pipeline to remove. Omit when the input has no pipeline. */\n\tpipelineId?: string;\n\t/** Input to remove. Omit to leave it. */\n\tinputId?: string;\n\t/** Output to remove (the auto-created dev/null sink). Omit to leave it. */\n\toutputId?: string;\n\t/**\n\t * What to actually delete. Defaults to everything supplied\n\t * (CLEANUP_FULL). Set a flag false to keep that resource even when\n\t * its id is provided.\n\t */\n\tcleanup?: CleanupPolicy;\n}\n\n/**\n * Delete an integration per a cleanup policy. Order matters: the\n * pipeline references the input and output, so it goes first. Only\n * resources whose id is supplied AND whose policy flag is true are\n * removed — so nothing the host wants to keep is touched, and nothing\n * it wants gone is left orphaned.\n */\nexport async function deleteIntegration(opts: DeleteIntegrationOptions): Promise<void> {\n\tconst { request, org, pipelineId, inputId, outputId } = opts;\n\tconst policy: Required<CleanupPolicy> = {\n\t\tpipeline: true,\n\t\tinput: true,\n\t\toutput: true,\n\t\t...opts.cleanup\n\t};\n\tif (pipelineId && policy.pipeline) {\n\t\tawait request(`/v2/${org}/pipelines/${pipelineId}`, { method: 'DELETE' });\n\t}\n\tif (inputId && policy.input) {\n\t\tawait request(`/v1/${org}/inputs/${inputId}`, { method: 'DELETE' });\n\t}\n\tif (outputId && policy.output) {\n\t\tawait request(`/v1/${org}/outputs/${outputId}`, { method: 'DELETE' });\n\t}\n}\n\n/* ===== build ===== */\n\nexport interface BuildDevNullPipelineOptions {\n\trequest: MonadRequest;\n\torg: string;\n\tinputId: string;\n\t/** Used to name the auto-created output and pipeline. */\n\tinputName: string;\n\t/** Status polls before giving up (default 15, ~2s apart). */\n\tpollAttempts?: number;\n}\n\nexport interface BuiltPipeline {\n\tpipelineId: string;\n\toutputId: string;\n\tstatus: string;\n\t/** True once the pipeline reports Running. */\n\tactive: boolean;\n}\n\n/**\n * Stand up a dev/null pipeline for a freshly-configured input: create a\n * dev/null output, create an enabled pipeline wiring input → output,\n * then poll until it reports Running. The POC's \"what happens after an\n * input is configured\" path, packaged as a reusable harness call.\n */\nexport async function buildDevNullPipeline(\n\topts: BuildDevNullPipelineOptions\n): Promise<BuiltPipeline> {\n\tconst { request, org, inputId, inputName, pollAttempts = 15 } = opts;\n\tconst label = inputName || 'Input';\n\n\tconst output = await request(`/v2/${org}/outputs`, {\n\t\tmethod: 'POST',\n\t\tbody: JSON.stringify({\n\t\t\toutput_type: 'dev-null',\n\t\t\tname: `${label} → /dev/null`,\n\t\t\tdescription: 'Auto-created sink for embed pipeline',\n\t\t\tpromise_id: '',\n\t\t\tconfig: { settings: {}, secrets: {} }\n\t\t})\n\t});\n\n\tconst pipeline = await request(`/v2/${org}/pipelines/`, {\n\t\tmethod: 'POST',\n\t\tbody: JSON.stringify({\n\t\t\tname: `${label} pipeline`,\n\t\t\tdescription: 'Auto-created when the input was configured via embed',\n\t\t\tenabled: true,\n\t\t\tnodes: [\n\t\t\t\t{ slug: 'in', component_id: inputId, component_type: 'input', enabled: true },\n\t\t\t\t{ slug: 'out', component_id: output.id, component_type: 'output', enabled: true }\n\t\t\t],\n\t\t\tedges: [\n\t\t\t\t{\n\t\t\t\t\tfrom_node_instance_id: 'in',\n\t\t\t\t\tto_node_instance_id: 'out',\n\t\t\t\t\tdescription: 'all records',\n\t\t\t\t\tconditions: { operator: 'always' }\n\t\t\t\t}\n\t\t\t]\n\t\t})\n\t});\n\n\tlet status = 'Pending';\n\tfor (let attempt = 0; attempt < pollAttempts; attempt++) {\n\t\tconst s = await request(`/v2/${org}/pipelines/${pipeline.id}/status`);\n\t\tstatus = s?.status ?? 'Unknown';\n\t\tif (status === 'Running' || status === 'Erroring') break;\n\t\tawait new Promise((r) => setTimeout(r, 2000));\n\t}\n\n\treturn {\n\t\tpipelineId: pipeline.id,\n\t\toutputId: output.id,\n\t\tstatus,\n\t\tactive: status === 'Running'\n\t};\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/connect/index.ts","../../src/connect/protocol.ts","../../src/connect/lifecycle.ts"],"sourcesContent":["/**\n * @monad-inc/embed/connect — host-side SDK for embedding Monad's\n * connector-config UI as a secure iframe.\n *\n * The customer's page calls `createConnectorFrame()`; this injects an\n * <iframe> pointing at a Monad-hosted page (@monad-inc/embed-frame). The\n * connector form — and every secret the user types into it — runs\n * inside that iframe on Monad's origin. Same-origin policy means the\n * customer's own JavaScript can never read those secrets. The customer\n * gets back only a connector id (a safe reference), the same way a\n * Stripe integration only ever sees a `pm_…` token.\n *\n * The host is still responsible for one thing: minting a short-lived\n * embed session token from its backend (POST /v1/embed/sessions, with\n * the long-lived API key) and handing it to `createConnectorFrame`.\n * That token travels into the iframe over postMessage, never the URL.\n */\nimport {\n\tPROTOCOL_SOURCE,\n\tPROTOCOL_VERSION,\n\ttype Appearance,\n\ttype ComponentKind,\n\ttype ConfigMetaOverrides,\n\ttype FrameMessage,\n\ttype HostMessage\n} from './protocol';\n\nexport {\n\tPROTOCOL_SOURCE,\n\tPROTOCOL_VERSION,\n\ttype Appearance,\n\ttype ComponentKind,\n\ttype ConfigMetaOverrides,\n\ttype DiscriminatorMetaOverride,\n\ttype FieldMetaOverride,\n\ttype FrameMessage,\n\ttype HostMessage,\n\ttype InitMessage\n} from './protocol';\n\nexport {\n\tfindIntegrationPipeline,\n\tsetIntegrationEnabled,\n\tdisableIntegration,\n\tenableIntegration,\n\tdeleteIntegration,\n\tbuildDevNullPipeline,\n\tCLEANUP_FULL,\n\tCLEANUP_KEEP_OUTPUT,\n\tCLEANUP_PIPELINE_ONLY,\n\ttype MonadRequest,\n\ttype ResolvedIntegration,\n\ttype SetEnabledOptions,\n\ttype DeleteIntegrationOptions,\n\ttype CleanupPolicy,\n\ttype BuildDevNullPipelineOptions,\n\ttype BuiltPipeline\n} from './lifecycle';\n\nexport interface ConnectorFrameOptions {\n\t/** DOM node (or selector) the iframe is appended to. */\n\tcontainer: HTMLElement | string;\n\t/**\n\t * URL of the Monad embed-frame page. Defaults to\n\t * \"https://app.monad.com/embed\" (production). Override for staging\n\t * (\"https://app.monad.security/embed\") or local testing\n\t * (\"http://localhost:5173/embed\"). MUST be a different origin than\n\t * the host page — that cross-origin boundary is what isolates the\n\t * secrets. A path component is allowed (so the embed page can live\n\t * at a sub-route of the main app); the postMessage security checks\n\t * compare against the URL's origin, not its full href.\n\t */\n\tframeOrigin?: string;\n\t/**\n\t * Monad API base the iframe should call. Defaults to\n\t * \"https://app.monad.com/api\" (production). Override for non-prod\n\t * environments. Set this together with `frameOrigin` — mixing\n\t * environments will not work.\n\t */\n\tapiBase?: string;\n\t/** Connector type id, e.g. \"runzero-findings\". */\n\ttypeId: string;\n\tkind: ComponentKind;\n\t/** Short-lived embed session token (host backend mints it). */\n\tsessionToken: string;\n\t/**\n\t * Sub-org id the session is scoped to. The host backend already knows\n\t * this — it's the same value passed to `/v1/embed/sessions` when minting\n\t * the token. Forwarded into the iframe so it can build `/v1/<org>/*` and\n\t * `/v2/<org>/*` URLs.\n\t */\n\torganizationId: string;\n\t/** Display name shown in the frame header. */\n\tdisplayName?: string;\n\t/**\n\t * Name for the created connector. On create, defaults to the connector\n\t * type's name plus a short unique suffix. On edit, omit to keep the\n\t * existing name.\n\t */\n\tname?: string;\n\t/**\n\t * Description for the created connector. On create, defaults to the\n\t * connector type's description. On edit, omit to keep the existing\n\t * description.\n\t */\n\tdescription?: string;\n\t/**\n\t * Show an editable name input inside the iframe (prefilled with the\n\t * resolved name) so the end user can name the connector themselves.\n\t * A non-empty name is required when shown. Defaults to false.\n\t */\n\tisNameEditable?: boolean;\n\t/**\n\t * Show an editable description input inside the iframe (prefilled with\n\t * the resolved description). Defaults to false.\n\t */\n\tisDescriptionEditable?: boolean;\n\t/** Existing connector id — pass for edit mode, omit to create. */\n\texistingId?: string;\n\t/**\n\t * Whether the synthetic-data toggle is exposed in the form. Defaults\n\t * to `false` — production embed flows keep it hidden.\n\t */\n\tsynthetic?: boolean;\n\t/** Theming tokens applied as CSS variables inside the iframe. */\n\tappearance?: Appearance;\n\t/**\n\t * Stylesheet URLs to apply inside the iframe. All-or-nothing: if\n\t * any URL is provided, the iframe loads ONLY these and skips its\n\t * built-in Monad UI defaults; if omitted/empty, the iframe loads\n\t * the defaults and nothing else. URLs must be reachable from the\n\t * iframe — typically served by the host page's own origin.\n\t */\n\tstylesheets?: string[];\n\t/**\n\t * Per-field copy overrides for the connector's config form: label,\n\t * description, placeholder. Copy only — nothing that changes a\n\t * field's type, requiredness, or submitted value.\n\t *\n\t * configMetaOverrides: {\n\t * settings: {\n\t * ddsource: { placeholder: 'northstar' },\n\t * service: { placeholder: 'northstar-web' },\n\t * },\n\t * }\n\t *\n\t * Keys are the connector's own wire keys, so check the connector-type\n\t * response rather than guessing — the Datadog output's source field is\n\t * `ddsource`, not `source`. Unmatched keys are ignored.\n\t *\n\t * `placeholder` is a hint, not a default: a field the user never\n\t * touches still submits `\"\"`. Nested fields are reached with\n\t * `children`, union variants with `discriminator.one_of.<variant>`.\n\t */\n\tconfigMetaOverrides?: ConfigMetaOverrides;\n\t/** Fired when the user saves successfully. */\n\tonSave?: (connector: { id: string; name?: string }) => void;\n\t/** Fired when the user cancels. */\n\tonCancel?: () => void;\n\t/** Fired on a save/test/load failure inside the iframe. */\n\tonError?: (message: string) => void;\n}\n\nexport interface ConnectorFrame {\n\t/** Remove the iframe and stop listening for its messages. */\n\tdestroy(): void;\n}\n\n/**\n * Production defaults. Customers integrating against prod don't pass\n * `frameOrigin`/`apiBase` at all; non-prod environments override both\n * together. Treat these as a public API contract — changing a hostname\n * is a breaking change for anyone on the default.\n */\nconst DEFAULT_FRAME_ORIGIN = 'https://app.monad.com/embed';\nconst DEFAULT_API_BASE = 'https://app.monad.com/api';\n\n/**\n * Inject the Monad connector iframe into `container` and wire the\n * postMessage channel. Returns a handle whose `destroy()` tears it all\n * down.\n *\n * Handshake:\n * 1. iframe loads, posts `ready`;\n * 2. host replies with `init` (session token, type, appearance);\n * 3. iframe mounts the form and streams `resize` as content grows;\n * 4. on save, iframe posts `saved {connector}`; on cancel, `cancel`.\n */\nexport function createConnectorFrame(opts: ConnectorFrameOptions): ConnectorFrame {\n\tconst container =\n\t\ttypeof opts.container === 'string'\n\t\t\t? document.querySelector<HTMLElement>(opts.container)\n\t\t\t: opts.container;\n\tif (!container) {\n\t\tthrow new Error('[monad-inc/connect] container element not found');\n\t}\n\n\tconst frameInput = opts.frameOrigin ?? DEFAULT_FRAME_ORIGIN;\n\tconst apiBase = opts.apiBase ?? DEFAULT_API_BASE;\n\n\t// Split the input URL into two pieces:\n\t// * `frameUrl` keeps the path (e.g. /embed) — used as iframe.src.\n\t// * `frameOrigin` is the bare origin — used for the postMessage\n\t// security checks below, which must compare against the value\n\t// the browser surfaces on MessageEvent.origin (always a bare\n\t// origin, never a path).\n\t// Previously these were collapsed into one `new URL(...).href`,\n\t// which silently broke any frameOrigin with a path — the equality\n\t// check `event.origin !== frameOrigin` would always fail and `init`\n\t// never made it back to the iframe.\n\tconst frameUrl = new URL(frameInput);\n\tconst frameOrigin = frameUrl.origin;\n\tconst framePath = frameUrl.pathname.replace(/\\/$/, '');\n\n\tconst iframe = document.createElement('iframe');\n\tiframe.className = 'monad-connect-frame';\n\tiframe.title = 'Monad connector configuration';\n\tiframe.style.width = '100%';\n\tiframe.style.border = '0';\n\tiframe.style.display = 'block';\n\t// The iframe needs its own scripts and same-origin access to the\n\t// Monad API; it must NOT get same-origin access to the host. A\n\t// cross-origin iframe already can't reach the host — no sandbox\n\t// attribute is needed for isolation, and adding one would block the\n\t// form's own scripts. Forms/popups stay enabled for OAuth-style\n\t// connectors that may open a window.\n\tiframe.allow = 'clipboard-write';\n\t// parentOrigin lets the iframe scope its postMessage target + sender\n\t// check. It is NOT a secret and NOT a capability — lying about it\n\t// only breaks the liar's own embed (see protocol.ts).\n\tiframe.src = `${frameOrigin}${framePath}/?parentOrigin=${encodeURIComponent(window.location.origin)}`;\n\n\tlet destroyed = false;\n\n\tfunction postToFrame(msg: HostMessage): void {\n\t\tiframe.contentWindow?.postMessage(msg, frameOrigin);\n\t}\n\n\tfunction onMessage(event: MessageEvent): void {\n\t\tif (destroyed) return;\n\t\t// --- security boundary: validate before trusting anything ---\n\t\tif (event.origin !== frameOrigin) return;\n\t\tif (event.source !== iframe.contentWindow) return;\n\t\tconst msg = event.data as FrameMessage | undefined;\n\t\tif (!msg || msg.source !== PROTOCOL_SOURCE) return;\n\n\t\tswitch (msg.type) {\n\t\t\tcase 'ready':\n\t\t\t\tpostToFrame({\n\t\t\t\t\tsource: PROTOCOL_SOURCE,\n\t\t\t\t\ttype: 'init',\n\t\t\t\t\tversion: PROTOCOL_VERSION,\n\t\t\t\t\tsessionToken: opts.sessionToken,\n\t\t\t\t\tapiBase,\n\t\t\t\t\torganizationId: opts.organizationId,\n\t\t\t\t\ttypeId: opts.typeId,\n\t\t\t\t\tkind: opts.kind,\n\t\t\t\t\tdisplayName: opts.displayName,\n\t\t\t\t\tname: opts.name,\n\t\t\t\t\tdescription: opts.description,\n\t\t\t\t\tisNameEditable: opts.isNameEditable,\n\t\t\t\t\tisDescriptionEditable: opts.isDescriptionEditable,\n\t\t\t\t\texistingId: opts.existingId,\n\t\t\t\t\tsynthetic: opts.synthetic,\n\t\t\t\t\tappearance: opts.appearance,\n\t\t\t\t\tstylesheets: opts.stylesheets,\n\t\t\t\t\tconfigMetaOverrides: opts.configMetaOverrides\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase 'resize': {\n\t\t\t\t// Clamp to a sane range so a renderer bug in the iframe\n\t\t\t\t// can't blow up the host layout with a multi-million-px\n\t\t\t\t// height. The origin check above already gates who can\n\t\t\t\t// send this — clamping is defense in depth. Number.isFinite\n\t\t\t\t// guards against non-numeric heights that would otherwise\n\t\t\t\t// produce \"NaNpx\" and leave the iframe collapsed.\n\t\t\t\tconst h = Number.isFinite(msg.height) ? msg.height : 0;\n\t\t\t\tiframe.style.height = `${Math.min(Math.max(h, 0), 20000)}px`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'saved':\n\t\t\t\topts.onSave?.(msg.connector);\n\t\t\t\tbreak;\n\t\t\tcase 'error':\n\t\t\t\topts.onError?.(msg.message);\n\t\t\t\tbreak;\n\t\t\tcase 'cancel':\n\t\t\t\topts.onCancel?.();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\twindow.addEventListener('message', onMessage);\n\tcontainer.appendChild(iframe);\n\n\treturn {\n\t\tdestroy(): void {\n\t\t\tdestroyed = true;\n\t\t\twindow.removeEventListener('message', onMessage);\n\t\t\tiframe.remove();\n\t\t}\n\t};\n}\n","/**\n * postMessage contract between the host page (@monad-inc/embed/connect) and\n * the Monad-hosted iframe (@monad-inc/embed-frame).\n *\n * Why a protocol module: both sides must agree on message shapes, and a\n * drifting contract is a silent failure (a message just gets ignored).\n * @monad-inc/embed/connect owns it because it's the public-facing SDK; the\n * iframe app imports these types from here.\n *\n * Security model:\n * - Origin is the real boundary. Every listener validates\n * `event.origin` against an expected value before trusting a\n * message. The iframe knows its parent's origin (passed as a URL\n * param); the host knows the frame's origin (it chose it).\n * - `event.source` is also checked host-side so a sibling iframe on\n * the same origin can't impersonate ours.\n * - The `source: 'monad-embed'` tag is NOT security — it's just a\n * cheap filter so unrelated postMessage traffic (analytics SDKs,\n * devtools, wallet extensions) is ignored without parsing.\n * - The session token travels in the `init` message body, never in\n * the iframe URL, so it stays out of browser history and Referer.\n */\n\nexport const PROTOCOL_SOURCE = 'monad-embed' as const;\nexport const PROTOCOL_VERSION = 1 as const;\n\nexport type ComponentKind = 'input' | 'output';\n\n/**\n * Appearance tokens the host may pass to theme the iframe. The iframe\n * applies each as a `--monad-{token}` CSS variable. The host cannot\n * style across the iframe boundary directly (same-origin policy), so\n * this is the only theming channel — same idea as Stripe's `appearance`.\n */\nexport interface Appearance {\n\tcolorPrimary?: string;\n\tcolorText?: string;\n\tcolorBackground?: string;\n\tcolorBorder?: string;\n\tfontFamily?: string;\n\tborderRadius?: string;\n}\n\n/**\n * Copy overrides for a single field of a connector's config form.\n *\n * Only copy can be overridden. A field's type, requiredness, allowed\n * values, and default are fixed by the connector and cannot be changed\n * here.\n *\n * `placeholder` is a hint, not a default: a field the user never touches\n * still submits an empty value.\n *\n * Keys mirror the config shape returned for the connector type, so they\n * are snake_case — unlike the camelCase options on\n * `ConnectorFrameOptions`. Field keys are the connector's own keys; the\n * Datadog output's source field is `ddsource`, not `source`. Keys that\n * don't match the connector are ignored.\n */\nexport interface FieldMetaOverride {\n\t/** Field label. An empty string is ignored — unlabelled fields are a bug. */\n\tname?: string;\n\t/** Helper text shown with the control. Empty string blanks it. */\n\tdescription?: string;\n\t/** Ephemeral hint text inside the control. Empty string blanks it. */\n\tplaceholder?: string;\n\t/** Overrides for a nested object's child fields, keyed by child wire key. */\n\tchildren?: Record<string, FieldMetaOverride>;\n\t/** Overrides for a discriminated-union (one_of) field. */\n\tdiscriminator?: DiscriminatorMetaOverride;\n}\n\n/**\n * Copy overrides for a one_of field's variant selector. The set of\n * variants, their order, and which one is selected by default are fixed\n * by the connector.\n */\nexport interface DiscriminatorMetaOverride {\n\t/** Label on the variant selector (defaults to \"Type\"). */\n\tname?: string;\n\t/** Helper text for the variant selector. */\n\tdescription?: string;\n\t/** Per-variant overrides, keyed by discriminator value. */\n\tone_of?: Record<string, FieldMetaOverride>;\n}\n\n/**\n * Config-form copy overrides for one connector, mirroring the two\n * sections returned for the connector type. Overrides never add fields —\n * keys that don't match the connector are ignored.\n */\nexport interface ConfigMetaOverrides {\n\tsettings?: Record<string, FieldMetaOverride>;\n\tsecrets?: Record<string, FieldMetaOverride>;\n}\n\n/* ===== iframe -> host ===== */\n\nexport interface ReadyMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'ready';\n\tversion: number;\n}\n\nexport interface ResizeMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'resize';\n\t/** Content height in CSS pixels — the host sizes the <iframe> to this. */\n\theight: number;\n}\n\nexport interface SavedMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'saved';\n\t/** Safe reference to the created/updated connector — no secret material. */\n\tconnector: { id: string; name?: string };\n}\n\nexport interface ErrorMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'error';\n\tmessage: string;\n}\n\nexport interface CancelMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'cancel';\n}\n\nexport type FrameMessage =\n\t| ReadyMessage\n\t| ResizeMessage\n\t| SavedMessage\n\t| ErrorMessage\n\t| CancelMessage;\n\n/* ===== host -> iframe ===== */\n\nexport interface InitMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'init';\n\tversion: number;\n\t/** Short-lived embed session token minted by the host's backend. */\n\tsessionToken: string;\n\t/** Monad API base, e.g. \"https://api.monad.com\" or dev \"http://localhost/api\". */\n\tapiBase: string;\n\t/**\n\t * Sub-org id the session token is scoped to. The iframe uses this to\n\t * build `/v1/<org>/*` and `/v2/<org>/*` URLs. Must match the token's\n\t * `org` claim — server-side `org_access.Middleware` will 403 if not.\n\t */\n\torganizationId: string;\n\t/** Connector type id, e.g. \"runzero-findings\". */\n\ttypeId: string;\n\tkind: ComponentKind;\n\t/** Display name shown in the frame header. */\n\tdisplayName?: string;\n\t/**\n\t * Name for the created connector. On create, defaults to the connector\n\t * type's name plus a short unique suffix. On edit, omit to keep the\n\t * existing name.\n\t */\n\tname?: string;\n\t/**\n\t * Description for the created connector. On create, defaults to the\n\t * connector type's description. On edit, omit to keep the existing\n\t * description.\n\t */\n\tdescription?: string;\n\t/**\n\t * Show an editable name input inside the iframe, prefilled with the\n\t * resolved name, so the end user can name the connector themselves.\n\t * A non-empty name is required when shown. Defaults to false.\n\t */\n\tisNameEditable?: boolean;\n\t/**\n\t * Show an editable description input inside the iframe, prefilled with\n\t * the resolved description. Defaults to false.\n\t */\n\tisDescriptionEditable?: boolean;\n\t/** Existing connector id — present for edit mode, absent for create. */\n\texistingId?: string;\n\t/**\n\t * Whether the synthetic-data toggle is exposed in the form. Defaults\n\t * to `false` (toggle hidden). Production embed customers keep this\n\t * off — synthetic mode is a Monad-internal testing affordance.\n\t */\n\tsynthetic?: boolean;\n\t/** Theming tokens applied as CSS variables inside the iframe. */\n\tappearance?: Appearance;\n\t/**\n\t * Stylesheet URLs the host wants to apply to the iframe page. The\n\t * iframe loads these as `<link rel=\"stylesheet\">` tags before\n\t * rendering the form.\n\t *\n\t * All-or-nothing: if this array is present and non-empty, the\n\t * iframe loads ONLY these stylesheets and does NOT load its\n\t * built-in Monad UI defaults. If it's absent or empty, the iframe\n\t * loads the built-in defaults and nothing else. Mixing host CSS\n\t * with the defaults is intentionally not supported — partial\n\t * styling tends to produce confusing half-themed forms.\n\t *\n\t * URLs MUST be reachable from the iframe origin (typically by\n\t * being served from the host page's own origin, which the browser\n\t * permits for stylesheet `<link>` loads even cross-origin).\n\t */\n\tstylesheets?: string[];\n\t/**\n\t * Per-field copy overrides merged onto the connector's config form.\n\t * Copy only — see FieldMetaOverride.\n\t */\n\tconfigMetaOverrides?: ConfigMetaOverrides;\n}\n\nexport type HostMessage = InitMessage;\n","/**\n * Connector-lifecycle helpers — the \"wiring harness\" a host (Okta, in\n * the demo's fiction) drops into its own backend to power Disable /\n * Enable / Delete buttons in its own UI.\n *\n * Unlike createConnectorFrame (browser-only), these are isomorphic:\n * they just sequence Monad's standard `/v2/{org}/*` + `/v1/{org}/*`\n * API calls. They run wherever the host holds its API key — almost\n * always its backend.\n *\n * The host supplies a `MonadRequest` thunk that performs an\n * authenticated request and returns parsed JSON. The harness owns the\n * non-obvious parts: which endpoint + verb each action maps to, the\n * delete ordering (pipeline before the input/output it references),\n * and resolving an input id to its pipeline without the host having to\n * store that mapping.\n */\n\n/**\n * Authenticated request against the Monad API, returning parsed JSON\n * (or undefined for empty responses). The host implements this with\n * its long-lived API key and base URL. Path is the full API path,\n * e.g. `/v2/{org}/pipelines/{id}`.\n */\nexport type MonadRequest = (path: string, init?: RequestInit) => Promise<any>;\n\n/* ===== resolve ===== */\n\n/**\n * Where an integration's resources live and their current state.\n * Returned by findIntegrationPipeline so the host can act on an\n * integration knowing only the input id.\n */\nexport interface ResolvedIntegration {\n\torg: string;\n\tpipelineId: string;\n\t/** Whether the pipeline is currently enabled. */\n\tenabled: boolean;\n\tinputId: string;\n\t/** Output id of the pipeline's destination node, if it has one. */\n\toutputId?: string;\n}\n\ninterface PipelineNode {\n\tcomponent_id?: string;\n\tcomponent_type?: string;\n}\n\n/**\n * Find the pipeline an input feeds. Lets the host act on an integration\n * (disable/enable/delete) knowing only the input id — no need to\n * persist an input→pipeline map of its own.\n *\n * The pipelines list is slim (no node wiring), so this fetches each\n * pipeline's detail and scans for the input node. Fine for the modest\n * pipeline counts a single tenant sub-org has; a host managing many\n * pipelines per sub-org should instead persist the `pipelineId`\n * buildDevNullPipeline returns and skip the lookup.\n *\n * Returns null when the input has no pipeline.\n */\nexport async function findIntegrationPipeline(opts: {\n\trequest: MonadRequest;\n\torg: string;\n\tinputId: string;\n}): Promise<ResolvedIntegration | null> {\n\tconst { request, org, inputId } = opts;\n\tconst listed = await request(`/v2/${org}/pipelines/`);\n\tconst pipelines: any[] = Array.isArray(listed)\n\t\t? listed\n\t\t: (listed?.pipelines ?? listed?.data ?? []);\n\n\tfor (const summary of pipelines) {\n\t\tif (!summary?.id) continue;\n\t\tlet detail: any;\n\t\ttry {\n\t\t\tdetail = await request(`/v2/${org}/pipelines/${summary.id}`);\n\t\t} catch {\n\t\t\tcontinue; // skip a pipeline we can't read rather than failing\n\t\t}\n\t\tconst pipeline = detail?.config ?? detail;\n\t\tconst nodes: PipelineNode[] = pipeline?.nodes ?? [];\n\t\tconst inputNode = nodes.find((n) => n.component_type === 'input' && n.component_id === inputId);\n\t\tif (!inputNode) continue;\n\t\tconst outputNode = nodes.find((n) => n.component_type === 'output');\n\t\treturn {\n\t\t\torg,\n\t\t\tpipelineId: summary.id,\n\t\t\tenabled: Boolean(pipeline.enabled),\n\t\t\tinputId,\n\t\t\toutputId: outputNode?.component_id\n\t\t};\n\t}\n\treturn null;\n}\n\n/* ===== enable / disable ===== */\n\nexport interface SetEnabledOptions {\n\trequest: MonadRequest;\n\torg: string;\n\tpipelineId: string;\n\t/** true → resume processing, false → stop (config is kept either way). */\n\tenabled: boolean;\n}\n\n/**\n * Enable or disable an integration's pipeline. Disabling stops data\n * flowing but destroys nothing — the input, output, and pipeline\n * config all survive, so a later enable resumes it as it was.\n *\n * The pipeline PATCH endpoint replaces the whole config rather than\n * accepting a partial, so this reads the current pipeline and sends it\n * back with only the `enabled` flag flipped.\n *\n * Race warning: this is GET-then-PATCH on the same resource, so a\n * concurrent edit between the two requests gets clobbered. Fine for\n * human-driven toggles. If you call this from automation, fan-out, or\n * a retry loop, ask the API team for a server-side `{ enabled }`\n * partial PATCH instead — the round-trip we do here is only a\n * workaround for the current full-config replace semantics.\n */\nexport async function setIntegrationEnabled(opts: SetEnabledOptions): Promise<void> {\n\tconst { request, org, pipelineId, enabled } = opts;\n\tconst detail = await request(`/v2/${org}/pipelines/${pipelineId}`);\n\tconst pipeline = detail?.config ?? detail;\n\tawait request(`/v2/${org}/pipelines/${pipelineId}`, {\n\t\tmethod: 'PATCH',\n\t\tbody: JSON.stringify({\n\t\t\tname: pipeline.name,\n\t\t\tdescription: pipeline.description ?? '',\n\t\t\tenabled,\n\t\t\tnodes: (pipeline.nodes ?? []).map((n: any) => ({\n\t\t\t\tid: n.id,\n\t\t\t\tslug: n.slug,\n\t\t\t\tcomponent_id: n.component_id,\n\t\t\t\tcomponent_type: n.component_type,\n\t\t\t\tenabled: n.enabled ?? true\n\t\t\t})),\n\t\t\tedges: (pipeline.edges ?? []).map((e: any) => ({\n\t\t\t\tname: e.name,\n\t\t\t\tdescription: e.description ?? '',\n\t\t\t\tfrom_node_instance_id: e.from_node_instance_id,\n\t\t\t\tto_node_instance_id: e.to_node_instance_id,\n\t\t\t\tdisabled: e.disabled ?? false,\n\t\t\t\tconditions: e.conditions\n\t\t\t}))\n\t\t})\n\t});\n}\n\n/** Convenience: disable the pipeline (stop without deleting). */\nexport function disableIntegration(opts: Omit<SetEnabledOptions, 'enabled'>): Promise<void> {\n\treturn setIntegrationEnabled({ ...opts, enabled: false });\n}\n\n/** Convenience: re-enable a disabled pipeline. */\nexport function enableIntegration(opts: Omit<SetEnabledOptions, 'enabled'>): Promise<void> {\n\treturn setIntegrationEnabled({ ...opts, enabled: true });\n}\n\n/* ===== delete ===== */\n\n/**\n * Which resources a delete removes. An embed integration is a pipeline\n * + an input + (in the dev/null flow) an output — but hosts provision\n * and own tenant resources differently. Some keep a shared output,\n * some keep the input so it can be re-wired later, some only ever tear\n * down the pipeline. Choose the policy that matches how you provision;\n * a flag left undefined defaults to `true` (delete it).\n *\n * Three ready-made policies are exported — CLEANUP_FULL,\n * CLEANUP_KEEP_OUTPUT, CLEANUP_PIPELINE_ONLY — or pass your own.\n */\nexport interface CleanupPolicy {\n\t/** Delete the pipeline. Default: true. */\n\tpipeline?: boolean;\n\t/** Delete the input connector. Default: true. */\n\tinput?: boolean;\n\t/** Delete the output connector. Default: true. */\n\toutput?: boolean;\n}\n\n/** Tear down everything — pipeline, input, and output. */\nexport const CLEANUP_FULL: CleanupPolicy = { pipeline: true, input: true, output: true };\n\n/** Remove the pipeline and input but keep the output (e.g. a shared sink). */\nexport const CLEANUP_KEEP_OUTPUT: CleanupPolicy = {\n\tpipeline: true,\n\tinput: true,\n\toutput: false\n};\n\n/** Remove only the pipeline; keep the input so it can be re-wired. */\nexport const CLEANUP_PIPELINE_ONLY: CleanupPolicy = {\n\tpipeline: true,\n\tinput: false,\n\toutput: false\n};\n\nexport interface DeleteIntegrationOptions {\n\trequest: MonadRequest;\n\torg: string;\n\t/** Pipeline to remove. Omit when the input has no pipeline. */\n\tpipelineId?: string;\n\t/** Input to remove. Omit to leave it. */\n\tinputId?: string;\n\t/** Output to remove (the auto-created dev/null sink). Omit to leave it. */\n\toutputId?: string;\n\t/**\n\t * What to actually delete. Defaults to everything supplied\n\t * (CLEANUP_FULL). Set a flag false to keep that resource even when\n\t * its id is provided.\n\t */\n\tcleanup?: CleanupPolicy;\n}\n\n/**\n * Delete an integration per a cleanup policy. Order matters: the\n * pipeline references the input and output, so it goes first. Only\n * resources whose id is supplied AND whose policy flag is true are\n * removed — so nothing the host wants to keep is touched, and nothing\n * it wants gone is left orphaned.\n */\nexport async function deleteIntegration(opts: DeleteIntegrationOptions): Promise<void> {\n\tconst { request, org, pipelineId, inputId, outputId } = opts;\n\tconst policy: Required<CleanupPolicy> = {\n\t\tpipeline: true,\n\t\tinput: true,\n\t\toutput: true,\n\t\t...opts.cleanup\n\t};\n\tif (pipelineId && policy.pipeline) {\n\t\tawait request(`/v2/${org}/pipelines/${pipelineId}`, { method: 'DELETE' });\n\t}\n\tif (inputId && policy.input) {\n\t\tawait request(`/v1/${org}/inputs/${inputId}`, { method: 'DELETE' });\n\t}\n\tif (outputId && policy.output) {\n\t\tawait request(`/v1/${org}/outputs/${outputId}`, { method: 'DELETE' });\n\t}\n}\n\n/* ===== build ===== */\n\nexport interface BuildDevNullPipelineOptions {\n\trequest: MonadRequest;\n\torg: string;\n\tinputId: string;\n\t/** Used to name the auto-created output and pipeline. */\n\tinputName: string;\n\t/** Status polls before giving up (default 15, ~2s apart). */\n\tpollAttempts?: number;\n}\n\nexport interface BuiltPipeline {\n\tpipelineId: string;\n\toutputId: string;\n\tstatus: string;\n\t/** True once the pipeline reports Running. */\n\tactive: boolean;\n}\n\n/**\n * Stand up a dev/null pipeline for a freshly-configured input: create a\n * dev/null output, create an enabled pipeline wiring input → output,\n * then poll until it reports Running. The POC's \"what happens after an\n * input is configured\" path, packaged as a reusable harness call.\n */\nexport async function buildDevNullPipeline(\n\topts: BuildDevNullPipelineOptions\n): Promise<BuiltPipeline> {\n\tconst { request, org, inputId, inputName, pollAttempts = 15 } = opts;\n\tconst label = inputName || 'Input';\n\n\tconst output = await request(`/v2/${org}/outputs`, {\n\t\tmethod: 'POST',\n\t\tbody: JSON.stringify({\n\t\t\toutput_type: 'dev-null',\n\t\t\tname: `${label} → /dev/null`,\n\t\t\tdescription: 'Auto-created sink for embed pipeline',\n\t\t\tpromise_id: '',\n\t\t\tconfig: { settings: {}, secrets: {} }\n\t\t})\n\t});\n\n\tconst pipeline = await request(`/v2/${org}/pipelines/`, {\n\t\tmethod: 'POST',\n\t\tbody: JSON.stringify({\n\t\t\tname: `${label} pipeline`,\n\t\t\tdescription: 'Auto-created when the input was configured via embed',\n\t\t\tenabled: true,\n\t\t\tnodes: [\n\t\t\t\t{ slug: 'in', component_id: inputId, component_type: 'input', enabled: true },\n\t\t\t\t{ slug: 'out', component_id: output.id, component_type: 'output', enabled: true }\n\t\t\t],\n\t\t\tedges: [\n\t\t\t\t{\n\t\t\t\t\tfrom_node_instance_id: 'in',\n\t\t\t\t\tto_node_instance_id: 'out',\n\t\t\t\t\tdescription: 'all records',\n\t\t\t\t\tconditions: { operator: 'always' }\n\t\t\t\t}\n\t\t\t]\n\t\t})\n\t});\n\n\tlet status = 'Pending';\n\tfor (let attempt = 0; attempt < pollAttempts; attempt++) {\n\t\tconst s = await request(`/v2/${org}/pipelines/${pipeline.id}/status`);\n\t\tstatus = s?.status ?? 'Unknown';\n\t\tif (status === 'Running' || status === 'Erroring') break;\n\t\tawait new Promise((r) => setTimeout(r, 2000));\n\t}\n\n\treturn {\n\t\tpipelineId: pipeline.id,\n\t\toutputId: output.id,\n\t\tstatus,\n\t\tactive: status === 'Running'\n\t};\n}\n"],"mappings":"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","names":[]}
|
package/dist/connect/index.d.cts
CHANGED
|
@@ -37,6 +37,56 @@ interface Appearance {
|
|
|
37
37
|
fontFamily?: string;
|
|
38
38
|
borderRadius?: string;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Copy overrides for a single field of a connector's config form.
|
|
42
|
+
*
|
|
43
|
+
* Only copy can be overridden. A field's type, requiredness, allowed
|
|
44
|
+
* values, and default are fixed by the connector and cannot be changed
|
|
45
|
+
* here.
|
|
46
|
+
*
|
|
47
|
+
* `placeholder` is a hint, not a default: a field the user never touches
|
|
48
|
+
* still submits an empty value.
|
|
49
|
+
*
|
|
50
|
+
* Keys mirror the config shape returned for the connector type, so they
|
|
51
|
+
* are snake_case — unlike the camelCase options on
|
|
52
|
+
* `ConnectorFrameOptions`. Field keys are the connector's own keys; the
|
|
53
|
+
* Datadog output's source field is `ddsource`, not `source`. Keys that
|
|
54
|
+
* don't match the connector are ignored.
|
|
55
|
+
*/
|
|
56
|
+
interface FieldMetaOverride {
|
|
57
|
+
/** Field label. An empty string is ignored — unlabelled fields are a bug. */
|
|
58
|
+
name?: string;
|
|
59
|
+
/** Helper text shown with the control. Empty string blanks it. */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Ephemeral hint text inside the control. Empty string blanks it. */
|
|
62
|
+
placeholder?: string;
|
|
63
|
+
/** Overrides for a nested object's child fields, keyed by child wire key. */
|
|
64
|
+
children?: Record<string, FieldMetaOverride>;
|
|
65
|
+
/** Overrides for a discriminated-union (one_of) field. */
|
|
66
|
+
discriminator?: DiscriminatorMetaOverride;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Copy overrides for a one_of field's variant selector. The set of
|
|
70
|
+
* variants, their order, and which one is selected by default are fixed
|
|
71
|
+
* by the connector.
|
|
72
|
+
*/
|
|
73
|
+
interface DiscriminatorMetaOverride {
|
|
74
|
+
/** Label on the variant selector (defaults to "Type"). */
|
|
75
|
+
name?: string;
|
|
76
|
+
/** Helper text for the variant selector. */
|
|
77
|
+
description?: string;
|
|
78
|
+
/** Per-variant overrides, keyed by discriminator value. */
|
|
79
|
+
one_of?: Record<string, FieldMetaOverride>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Config-form copy overrides for one connector, mirroring the two
|
|
83
|
+
* sections returned for the connector type. Overrides never add fields —
|
|
84
|
+
* keys that don't match the connector are ignored.
|
|
85
|
+
*/
|
|
86
|
+
interface ConfigMetaOverrides {
|
|
87
|
+
settings?: Record<string, FieldMetaOverride>;
|
|
88
|
+
secrets?: Record<string, FieldMetaOverride>;
|
|
89
|
+
}
|
|
40
90
|
interface ReadyMessage {
|
|
41
91
|
source: typeof PROTOCOL_SOURCE;
|
|
42
92
|
type: 'ready';
|
|
@@ -76,7 +126,7 @@ interface InitMessage {
|
|
|
76
126
|
/** Monad API base, e.g. "https://api.monad.com" or dev "http://localhost/api". */
|
|
77
127
|
apiBase: string;
|
|
78
128
|
/**
|
|
79
|
-
*
|
|
129
|
+
* Sub-org id the session token is scoped to. The iframe uses this to
|
|
80
130
|
* build `/v1/<org>/*` and `/v2/<org>/*` URLs. Must match the token's
|
|
81
131
|
* `org` claim — server-side `org_access.Middleware` will 403 if not.
|
|
82
132
|
*/
|
|
@@ -136,6 +186,11 @@ interface InitMessage {
|
|
|
136
186
|
* permits for stylesheet `<link>` loads even cross-origin).
|
|
137
187
|
*/
|
|
138
188
|
stylesheets?: string[];
|
|
189
|
+
/**
|
|
190
|
+
* Per-field copy overrides merged onto the connector's config form.
|
|
191
|
+
* Copy only — see FieldMetaOverride.
|
|
192
|
+
*/
|
|
193
|
+
configMetaOverrides?: ConfigMetaOverrides;
|
|
139
194
|
}
|
|
140
195
|
type HostMessage = InitMessage;
|
|
141
196
|
|
|
@@ -184,8 +239,8 @@ interface ResolvedIntegration {
|
|
|
184
239
|
*
|
|
185
240
|
* The pipelines list is slim (no node wiring), so this fetches each
|
|
186
241
|
* pipeline's detail and scans for the input node. Fine for the modest
|
|
187
|
-
* pipeline counts a single tenant
|
|
188
|
-
* pipelines per
|
|
242
|
+
* pipeline counts a single tenant sub-org has; a host managing many
|
|
243
|
+
* pipelines per sub-org should instead persist the `pipelineId`
|
|
189
244
|
* buildDevNullPipeline returns and skip the lookup.
|
|
190
245
|
*
|
|
191
246
|
* Returns null when the input has no pipeline.
|
|
@@ -341,7 +396,7 @@ interface ConnectorFrameOptions {
|
|
|
341
396
|
/** Short-lived embed session token (host backend mints it). */
|
|
342
397
|
sessionToken: string;
|
|
343
398
|
/**
|
|
344
|
-
*
|
|
399
|
+
* Sub-org id the session is scoped to. The host backend already knows
|
|
345
400
|
* this — it's the same value passed to `/v1/embed/sessions` when minting
|
|
346
401
|
* the token. Forwarded into the iframe so it can build `/v1/<org>/*` and
|
|
347
402
|
* `/v2/<org>/*` URLs.
|
|
@@ -389,6 +444,27 @@ interface ConnectorFrameOptions {
|
|
|
389
444
|
* iframe — typically served by the host page's own origin.
|
|
390
445
|
*/
|
|
391
446
|
stylesheets?: string[];
|
|
447
|
+
/**
|
|
448
|
+
* Per-field copy overrides for the connector's config form: label,
|
|
449
|
+
* description, placeholder. Copy only — nothing that changes a
|
|
450
|
+
* field's type, requiredness, or submitted value.
|
|
451
|
+
*
|
|
452
|
+
* configMetaOverrides: {
|
|
453
|
+
* settings: {
|
|
454
|
+
* ddsource: { placeholder: 'northstar' },
|
|
455
|
+
* service: { placeholder: 'northstar-web' },
|
|
456
|
+
* },
|
|
457
|
+
* }
|
|
458
|
+
*
|
|
459
|
+
* Keys are the connector's own wire keys, so check the connector-type
|
|
460
|
+
* response rather than guessing — the Datadog output's source field is
|
|
461
|
+
* `ddsource`, not `source`. Unmatched keys are ignored.
|
|
462
|
+
*
|
|
463
|
+
* `placeholder` is a hint, not a default: a field the user never
|
|
464
|
+
* touches still submits `""`. Nested fields are reached with
|
|
465
|
+
* `children`, union variants with `discriminator.one_of.<variant>`.
|
|
466
|
+
*/
|
|
467
|
+
configMetaOverrides?: ConfigMetaOverrides;
|
|
392
468
|
/** Fired when the user saves successfully. */
|
|
393
469
|
onSave?: (connector: {
|
|
394
470
|
id: string;
|
|
@@ -416,4 +492,4 @@ interface ConnectorFrame {
|
|
|
416
492
|
*/
|
|
417
493
|
declare function createConnectorFrame(opts: ConnectorFrameOptions): ConnectorFrame;
|
|
418
494
|
|
|
419
|
-
export { type Appearance, type BuildDevNullPipelineOptions, type BuiltPipeline, CLEANUP_FULL, CLEANUP_KEEP_OUTPUT, CLEANUP_PIPELINE_ONLY, type CleanupPolicy, type ComponentKind, type ConnectorFrame, type ConnectorFrameOptions, type DeleteIntegrationOptions, type FrameMessage, type HostMessage, type InitMessage, type MonadRequest, PROTOCOL_SOURCE, PROTOCOL_VERSION, type ResolvedIntegration, type SetEnabledOptions, buildDevNullPipeline, createConnectorFrame, deleteIntegration, disableIntegration, enableIntegration, findIntegrationPipeline, setIntegrationEnabled };
|
|
495
|
+
export { type Appearance, type BuildDevNullPipelineOptions, type BuiltPipeline, CLEANUP_FULL, CLEANUP_KEEP_OUTPUT, CLEANUP_PIPELINE_ONLY, type CleanupPolicy, type ComponentKind, type ConfigMetaOverrides, type ConnectorFrame, type ConnectorFrameOptions, type DeleteIntegrationOptions, type DiscriminatorMetaOverride, type FieldMetaOverride, type FrameMessage, type HostMessage, type InitMessage, type MonadRequest, PROTOCOL_SOURCE, PROTOCOL_VERSION, type ResolvedIntegration, type SetEnabledOptions, buildDevNullPipeline, createConnectorFrame, deleteIntegration, disableIntegration, enableIntegration, findIntegrationPipeline, setIntegrationEnabled };
|
package/dist/connect/index.d.ts
CHANGED
|
@@ -37,6 +37,56 @@ interface Appearance {
|
|
|
37
37
|
fontFamily?: string;
|
|
38
38
|
borderRadius?: string;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Copy overrides for a single field of a connector's config form.
|
|
42
|
+
*
|
|
43
|
+
* Only copy can be overridden. A field's type, requiredness, allowed
|
|
44
|
+
* values, and default are fixed by the connector and cannot be changed
|
|
45
|
+
* here.
|
|
46
|
+
*
|
|
47
|
+
* `placeholder` is a hint, not a default: a field the user never touches
|
|
48
|
+
* still submits an empty value.
|
|
49
|
+
*
|
|
50
|
+
* Keys mirror the config shape returned for the connector type, so they
|
|
51
|
+
* are snake_case — unlike the camelCase options on
|
|
52
|
+
* `ConnectorFrameOptions`. Field keys are the connector's own keys; the
|
|
53
|
+
* Datadog output's source field is `ddsource`, not `source`. Keys that
|
|
54
|
+
* don't match the connector are ignored.
|
|
55
|
+
*/
|
|
56
|
+
interface FieldMetaOverride {
|
|
57
|
+
/** Field label. An empty string is ignored — unlabelled fields are a bug. */
|
|
58
|
+
name?: string;
|
|
59
|
+
/** Helper text shown with the control. Empty string blanks it. */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Ephemeral hint text inside the control. Empty string blanks it. */
|
|
62
|
+
placeholder?: string;
|
|
63
|
+
/** Overrides for a nested object's child fields, keyed by child wire key. */
|
|
64
|
+
children?: Record<string, FieldMetaOverride>;
|
|
65
|
+
/** Overrides for a discriminated-union (one_of) field. */
|
|
66
|
+
discriminator?: DiscriminatorMetaOverride;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Copy overrides for a one_of field's variant selector. The set of
|
|
70
|
+
* variants, their order, and which one is selected by default are fixed
|
|
71
|
+
* by the connector.
|
|
72
|
+
*/
|
|
73
|
+
interface DiscriminatorMetaOverride {
|
|
74
|
+
/** Label on the variant selector (defaults to "Type"). */
|
|
75
|
+
name?: string;
|
|
76
|
+
/** Helper text for the variant selector. */
|
|
77
|
+
description?: string;
|
|
78
|
+
/** Per-variant overrides, keyed by discriminator value. */
|
|
79
|
+
one_of?: Record<string, FieldMetaOverride>;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Config-form copy overrides for one connector, mirroring the two
|
|
83
|
+
* sections returned for the connector type. Overrides never add fields —
|
|
84
|
+
* keys that don't match the connector are ignored.
|
|
85
|
+
*/
|
|
86
|
+
interface ConfigMetaOverrides {
|
|
87
|
+
settings?: Record<string, FieldMetaOverride>;
|
|
88
|
+
secrets?: Record<string, FieldMetaOverride>;
|
|
89
|
+
}
|
|
40
90
|
interface ReadyMessage {
|
|
41
91
|
source: typeof PROTOCOL_SOURCE;
|
|
42
92
|
type: 'ready';
|
|
@@ -76,7 +126,7 @@ interface InitMessage {
|
|
|
76
126
|
/** Monad API base, e.g. "https://api.monad.com" or dev "http://localhost/api". */
|
|
77
127
|
apiBase: string;
|
|
78
128
|
/**
|
|
79
|
-
*
|
|
129
|
+
* Sub-org id the session token is scoped to. The iframe uses this to
|
|
80
130
|
* build `/v1/<org>/*` and `/v2/<org>/*` URLs. Must match the token's
|
|
81
131
|
* `org` claim — server-side `org_access.Middleware` will 403 if not.
|
|
82
132
|
*/
|
|
@@ -136,6 +186,11 @@ interface InitMessage {
|
|
|
136
186
|
* permits for stylesheet `<link>` loads even cross-origin).
|
|
137
187
|
*/
|
|
138
188
|
stylesheets?: string[];
|
|
189
|
+
/**
|
|
190
|
+
* Per-field copy overrides merged onto the connector's config form.
|
|
191
|
+
* Copy only — see FieldMetaOverride.
|
|
192
|
+
*/
|
|
193
|
+
configMetaOverrides?: ConfigMetaOverrides;
|
|
139
194
|
}
|
|
140
195
|
type HostMessage = InitMessage;
|
|
141
196
|
|
|
@@ -184,8 +239,8 @@ interface ResolvedIntegration {
|
|
|
184
239
|
*
|
|
185
240
|
* The pipelines list is slim (no node wiring), so this fetches each
|
|
186
241
|
* pipeline's detail and scans for the input node. Fine for the modest
|
|
187
|
-
* pipeline counts a single tenant
|
|
188
|
-
* pipelines per
|
|
242
|
+
* pipeline counts a single tenant sub-org has; a host managing many
|
|
243
|
+
* pipelines per sub-org should instead persist the `pipelineId`
|
|
189
244
|
* buildDevNullPipeline returns and skip the lookup.
|
|
190
245
|
*
|
|
191
246
|
* Returns null when the input has no pipeline.
|
|
@@ -341,7 +396,7 @@ interface ConnectorFrameOptions {
|
|
|
341
396
|
/** Short-lived embed session token (host backend mints it). */
|
|
342
397
|
sessionToken: string;
|
|
343
398
|
/**
|
|
344
|
-
*
|
|
399
|
+
* Sub-org id the session is scoped to. The host backend already knows
|
|
345
400
|
* this — it's the same value passed to `/v1/embed/sessions` when minting
|
|
346
401
|
* the token. Forwarded into the iframe so it can build `/v1/<org>/*` and
|
|
347
402
|
* `/v2/<org>/*` URLs.
|
|
@@ -389,6 +444,27 @@ interface ConnectorFrameOptions {
|
|
|
389
444
|
* iframe — typically served by the host page's own origin.
|
|
390
445
|
*/
|
|
391
446
|
stylesheets?: string[];
|
|
447
|
+
/**
|
|
448
|
+
* Per-field copy overrides for the connector's config form: label,
|
|
449
|
+
* description, placeholder. Copy only — nothing that changes a
|
|
450
|
+
* field's type, requiredness, or submitted value.
|
|
451
|
+
*
|
|
452
|
+
* configMetaOverrides: {
|
|
453
|
+
* settings: {
|
|
454
|
+
* ddsource: { placeholder: 'northstar' },
|
|
455
|
+
* service: { placeholder: 'northstar-web' },
|
|
456
|
+
* },
|
|
457
|
+
* }
|
|
458
|
+
*
|
|
459
|
+
* Keys are the connector's own wire keys, so check the connector-type
|
|
460
|
+
* response rather than guessing — the Datadog output's source field is
|
|
461
|
+
* `ddsource`, not `source`. Unmatched keys are ignored.
|
|
462
|
+
*
|
|
463
|
+
* `placeholder` is a hint, not a default: a field the user never
|
|
464
|
+
* touches still submits `""`. Nested fields are reached with
|
|
465
|
+
* `children`, union variants with `discriminator.one_of.<variant>`.
|
|
466
|
+
*/
|
|
467
|
+
configMetaOverrides?: ConfigMetaOverrides;
|
|
392
468
|
/** Fired when the user saves successfully. */
|
|
393
469
|
onSave?: (connector: {
|
|
394
470
|
id: string;
|
|
@@ -416,4 +492,4 @@ interface ConnectorFrame {
|
|
|
416
492
|
*/
|
|
417
493
|
declare function createConnectorFrame(opts: ConnectorFrameOptions): ConnectorFrame;
|
|
418
494
|
|
|
419
|
-
export { type Appearance, type BuildDevNullPipelineOptions, type BuiltPipeline, CLEANUP_FULL, CLEANUP_KEEP_OUTPUT, CLEANUP_PIPELINE_ONLY, type CleanupPolicy, type ComponentKind, type ConnectorFrame, type ConnectorFrameOptions, type DeleteIntegrationOptions, type FrameMessage, type HostMessage, type InitMessage, type MonadRequest, PROTOCOL_SOURCE, PROTOCOL_VERSION, type ResolvedIntegration, type SetEnabledOptions, buildDevNullPipeline, createConnectorFrame, deleteIntegration, disableIntegration, enableIntegration, findIntegrationPipeline, setIntegrationEnabled };
|
|
495
|
+
export { type Appearance, type BuildDevNullPipelineOptions, type BuiltPipeline, CLEANUP_FULL, CLEANUP_KEEP_OUTPUT, CLEANUP_PIPELINE_ONLY, type CleanupPolicy, type ComponentKind, type ConfigMetaOverrides, type ConnectorFrame, type ConnectorFrameOptions, type DeleteIntegrationOptions, type DiscriminatorMetaOverride, type FieldMetaOverride, type FrameMessage, type HostMessage, type InitMessage, type MonadRequest, PROTOCOL_SOURCE, PROTOCOL_VERSION, type ResolvedIntegration, type SetEnabledOptions, buildDevNullPipeline, createConnectorFrame, deleteIntegration, disableIntegration, enableIntegration, findIntegrationPipeline, setIntegrationEnabled };
|
package/dist/connect/index.js
CHANGED
|
@@ -191,7 +191,8 @@ function createConnectorFrame(opts) {
|
|
|
191
191
|
existingId: opts.existingId,
|
|
192
192
|
synthetic: opts.synthetic,
|
|
193
193
|
appearance: opts.appearance,
|
|
194
|
-
stylesheets: opts.stylesheets
|
|
194
|
+
stylesheets: opts.stylesheets,
|
|
195
|
+
configMetaOverrides: opts.configMetaOverrides
|
|
195
196
|
});
|
|
196
197
|
break;
|
|
197
198
|
case "resize": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/connect/protocol.ts","../../src/connect/lifecycle.ts","../../src/connect/index.ts"],"sourcesContent":["/**\n * postMessage contract between the host page (@monad-inc/embed/connect) and\n * the Monad-hosted iframe (@monad-inc/embed-frame).\n *\n * Why a protocol module: both sides must agree on message shapes, and a\n * drifting contract is a silent failure (a message just gets ignored).\n * @monad-inc/embed/connect owns it because it's the public-facing SDK; the\n * iframe app imports these types from here.\n *\n * Security model:\n * - Origin is the real boundary. Every listener validates\n * `event.origin` against an expected value before trusting a\n * message. The iframe knows its parent's origin (passed as a URL\n * param); the host knows the frame's origin (it chose it).\n * - `event.source` is also checked host-side so a sibling iframe on\n * the same origin can't impersonate ours.\n * - The `source: 'monad-embed'` tag is NOT security — it's just a\n * cheap filter so unrelated postMessage traffic (analytics SDKs,\n * devtools, wallet extensions) is ignored without parsing.\n * - The session token travels in the `init` message body, never in\n * the iframe URL, so it stays out of browser history and Referer.\n */\n\nexport const PROTOCOL_SOURCE = 'monad-embed' as const;\nexport const PROTOCOL_VERSION = 1 as const;\n\nexport type ComponentKind = 'input' | 'output';\n\n/**\n * Appearance tokens the host may pass to theme the iframe. The iframe\n * applies each as a `--monad-{token}` CSS variable. The host cannot\n * style across the iframe boundary directly (same-origin policy), so\n * this is the only theming channel — same idea as Stripe's `appearance`.\n */\nexport interface Appearance {\n\tcolorPrimary?: string;\n\tcolorText?: string;\n\tcolorBackground?: string;\n\tcolorBorder?: string;\n\tfontFamily?: string;\n\tborderRadius?: string;\n}\n\n/* ===== iframe -> host ===== */\n\nexport interface ReadyMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'ready';\n\tversion: number;\n}\n\nexport interface ResizeMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'resize';\n\t/** Content height in CSS pixels — the host sizes the <iframe> to this. */\n\theight: number;\n}\n\nexport interface SavedMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'saved';\n\t/** Safe reference to the created/updated connector — no secret material. */\n\tconnector: { id: string; name?: string };\n}\n\nexport interface ErrorMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'error';\n\tmessage: string;\n}\n\nexport interface CancelMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'cancel';\n}\n\nexport type FrameMessage =\n\t| ReadyMessage\n\t| ResizeMessage\n\t| SavedMessage\n\t| ErrorMessage\n\t| CancelMessage;\n\n/* ===== host -> iframe ===== */\n\nexport interface InitMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'init';\n\tversion: number;\n\t/** Short-lived embed session token minted by the host's backend. */\n\tsessionToken: string;\n\t/** Monad API base, e.g. \"https://api.monad.com\" or dev \"http://localhost/api\". */\n\tapiBase: string;\n\t/**\n\t * Team org id the session token is scoped to. The iframe uses this to\n\t * build `/v1/<org>/*` and `/v2/<org>/*` URLs. Must match the token's\n\t * `org` claim — server-side `org_access.Middleware` will 403 if not.\n\t */\n\torganizationId: string;\n\t/** Connector type id, e.g. \"runzero-findings\". */\n\ttypeId: string;\n\tkind: ComponentKind;\n\t/** Display name shown in the frame header. */\n\tdisplayName?: string;\n\t/**\n\t * Name for the created connector. On create, defaults to the connector\n\t * type's name plus a short unique suffix. On edit, omit to keep the\n\t * existing name.\n\t */\n\tname?: string;\n\t/**\n\t * Description for the created connector. On create, defaults to the\n\t * connector type's description. On edit, omit to keep the existing\n\t * description.\n\t */\n\tdescription?: string;\n\t/**\n\t * Show an editable name input inside the iframe, prefilled with the\n\t * resolved name, so the end user can name the connector themselves.\n\t * A non-empty name is required when shown. Defaults to false.\n\t */\n\tisNameEditable?: boolean;\n\t/**\n\t * Show an editable description input inside the iframe, prefilled with\n\t * the resolved description. Defaults to false.\n\t */\n\tisDescriptionEditable?: boolean;\n\t/** Existing connector id — present for edit mode, absent for create. */\n\texistingId?: string;\n\t/**\n\t * Whether the synthetic-data toggle is exposed in the form. Defaults\n\t * to `false` (toggle hidden). Production embed customers keep this\n\t * off — synthetic mode is a Monad-internal testing affordance.\n\t */\n\tsynthetic?: boolean;\n\t/** Theming tokens applied as CSS variables inside the iframe. */\n\tappearance?: Appearance;\n\t/**\n\t * Stylesheet URLs the host wants to apply to the iframe page. The\n\t * iframe loads these as `<link rel=\"stylesheet\">` tags before\n\t * rendering the form.\n\t *\n\t * All-or-nothing: if this array is present and non-empty, the\n\t * iframe loads ONLY these stylesheets and does NOT load its\n\t * built-in Monad UI defaults. If it's absent or empty, the iframe\n\t * loads the built-in defaults and nothing else. Mixing host CSS\n\t * with the defaults is intentionally not supported — partial\n\t * styling tends to produce confusing half-themed forms.\n\t *\n\t * URLs MUST be reachable from the iframe origin (typically by\n\t * being served from the host page's own origin, which the browser\n\t * permits for stylesheet `<link>` loads even cross-origin).\n\t */\n\tstylesheets?: string[];\n}\n\nexport type HostMessage = InitMessage;\n","/**\n * Connector-lifecycle helpers — the \"wiring harness\" a host (Okta, in\n * the demo's fiction) drops into its own backend to power Disable /\n * Enable / Delete buttons in its own UI.\n *\n * Unlike createConnectorFrame (browser-only), these are isomorphic:\n * they just sequence Monad's standard `/v2/{org}/*` + `/v1/{org}/*`\n * API calls. They run wherever the host holds its API key — almost\n * always its backend.\n *\n * The host supplies a `MonadRequest` thunk that performs an\n * authenticated request and returns parsed JSON. The harness owns the\n * non-obvious parts: which endpoint + verb each action maps to, the\n * delete ordering (pipeline before the input/output it references),\n * and resolving an input id to its pipeline without the host having to\n * store that mapping.\n */\n\n/**\n * Authenticated request against the Monad API, returning parsed JSON\n * (or undefined for empty responses). The host implements this with\n * its long-lived API key and base URL. Path is the full API path,\n * e.g. `/v2/{org}/pipelines/{id}`.\n */\nexport type MonadRequest = (path: string, init?: RequestInit) => Promise<any>;\n\n/* ===== resolve ===== */\n\n/**\n * Where an integration's resources live and their current state.\n * Returned by findIntegrationPipeline so the host can act on an\n * integration knowing only the input id.\n */\nexport interface ResolvedIntegration {\n\torg: string;\n\tpipelineId: string;\n\t/** Whether the pipeline is currently enabled. */\n\tenabled: boolean;\n\tinputId: string;\n\t/** Output id of the pipeline's destination node, if it has one. */\n\toutputId?: string;\n}\n\ninterface PipelineNode {\n\tcomponent_id?: string;\n\tcomponent_type?: string;\n}\n\n/**\n * Find the pipeline an input feeds. Lets the host act on an integration\n * (disable/enable/delete) knowing only the input id — no need to\n * persist an input→pipeline map of its own.\n *\n * The pipelines list is slim (no node wiring), so this fetches each\n * pipeline's detail and scans for the input node. Fine for the modest\n * pipeline counts a single tenant team has; a host managing many\n * pipelines per team should instead persist the `pipelineId`\n * buildDevNullPipeline returns and skip the lookup.\n *\n * Returns null when the input has no pipeline.\n */\nexport async function findIntegrationPipeline(opts: {\n\trequest: MonadRequest;\n\torg: string;\n\tinputId: string;\n}): Promise<ResolvedIntegration | null> {\n\tconst { request, org, inputId } = opts;\n\tconst listed = await request(`/v2/${org}/pipelines/`);\n\tconst pipelines: any[] = Array.isArray(listed)\n\t\t? listed\n\t\t: (listed?.pipelines ?? listed?.data ?? []);\n\n\tfor (const summary of pipelines) {\n\t\tif (!summary?.id) continue;\n\t\tlet detail: any;\n\t\ttry {\n\t\t\tdetail = await request(`/v2/${org}/pipelines/${summary.id}`);\n\t\t} catch {\n\t\t\tcontinue; // skip a pipeline we can't read rather than failing\n\t\t}\n\t\tconst pipeline = detail?.config ?? detail;\n\t\tconst nodes: PipelineNode[] = pipeline?.nodes ?? [];\n\t\tconst inputNode = nodes.find((n) => n.component_type === 'input' && n.component_id === inputId);\n\t\tif (!inputNode) continue;\n\t\tconst outputNode = nodes.find((n) => n.component_type === 'output');\n\t\treturn {\n\t\t\torg,\n\t\t\tpipelineId: summary.id,\n\t\t\tenabled: Boolean(pipeline.enabled),\n\t\t\tinputId,\n\t\t\toutputId: outputNode?.component_id\n\t\t};\n\t}\n\treturn null;\n}\n\n/* ===== enable / disable ===== */\n\nexport interface SetEnabledOptions {\n\trequest: MonadRequest;\n\torg: string;\n\tpipelineId: string;\n\t/** true → resume processing, false → stop (config is kept either way). */\n\tenabled: boolean;\n}\n\n/**\n * Enable or disable an integration's pipeline. Disabling stops data\n * flowing but destroys nothing — the input, output, and pipeline\n * config all survive, so a later enable resumes it as it was.\n *\n * The pipeline PATCH endpoint replaces the whole config rather than\n * accepting a partial, so this reads the current pipeline and sends it\n * back with only the `enabled` flag flipped.\n *\n * Race warning: this is GET-then-PATCH on the same resource, so a\n * concurrent edit between the two requests gets clobbered. Fine for\n * human-driven toggles. If you call this from automation, fan-out, or\n * a retry loop, ask the API team for a server-side `{ enabled }`\n * partial PATCH instead — the round-trip we do here is only a\n * workaround for the current full-config replace semantics.\n */\nexport async function setIntegrationEnabled(opts: SetEnabledOptions): Promise<void> {\n\tconst { request, org, pipelineId, enabled } = opts;\n\tconst detail = await request(`/v2/${org}/pipelines/${pipelineId}`);\n\tconst pipeline = detail?.config ?? detail;\n\tawait request(`/v2/${org}/pipelines/${pipelineId}`, {\n\t\tmethod: 'PATCH',\n\t\tbody: JSON.stringify({\n\t\t\tname: pipeline.name,\n\t\t\tdescription: pipeline.description ?? '',\n\t\t\tenabled,\n\t\t\tnodes: (pipeline.nodes ?? []).map((n: any) => ({\n\t\t\t\tid: n.id,\n\t\t\t\tslug: n.slug,\n\t\t\t\tcomponent_id: n.component_id,\n\t\t\t\tcomponent_type: n.component_type,\n\t\t\t\tenabled: n.enabled ?? true\n\t\t\t})),\n\t\t\tedges: (pipeline.edges ?? []).map((e: any) => ({\n\t\t\t\tname: e.name,\n\t\t\t\tdescription: e.description ?? '',\n\t\t\t\tfrom_node_instance_id: e.from_node_instance_id,\n\t\t\t\tto_node_instance_id: e.to_node_instance_id,\n\t\t\t\tdisabled: e.disabled ?? false,\n\t\t\t\tconditions: e.conditions\n\t\t\t}))\n\t\t})\n\t});\n}\n\n/** Convenience: disable the pipeline (stop without deleting). */\nexport function disableIntegration(opts: Omit<SetEnabledOptions, 'enabled'>): Promise<void> {\n\treturn setIntegrationEnabled({ ...opts, enabled: false });\n}\n\n/** Convenience: re-enable a disabled pipeline. */\nexport function enableIntegration(opts: Omit<SetEnabledOptions, 'enabled'>): Promise<void> {\n\treturn setIntegrationEnabled({ ...opts, enabled: true });\n}\n\n/* ===== delete ===== */\n\n/**\n * Which resources a delete removes. An embed integration is a pipeline\n * + an input + (in the dev/null flow) an output — but hosts provision\n * and own tenant resources differently. Some keep a shared output,\n * some keep the input so it can be re-wired later, some only ever tear\n * down the pipeline. Choose the policy that matches how you provision;\n * a flag left undefined defaults to `true` (delete it).\n *\n * Three ready-made policies are exported — CLEANUP_FULL,\n * CLEANUP_KEEP_OUTPUT, CLEANUP_PIPELINE_ONLY — or pass your own.\n */\nexport interface CleanupPolicy {\n\t/** Delete the pipeline. Default: true. */\n\tpipeline?: boolean;\n\t/** Delete the input connector. Default: true. */\n\tinput?: boolean;\n\t/** Delete the output connector. Default: true. */\n\toutput?: boolean;\n}\n\n/** Tear down everything — pipeline, input, and output. */\nexport const CLEANUP_FULL: CleanupPolicy = { pipeline: true, input: true, output: true };\n\n/** Remove the pipeline and input but keep the output (e.g. a shared sink). */\nexport const CLEANUP_KEEP_OUTPUT: CleanupPolicy = {\n\tpipeline: true,\n\tinput: true,\n\toutput: false\n};\n\n/** Remove only the pipeline; keep the input so it can be re-wired. */\nexport const CLEANUP_PIPELINE_ONLY: CleanupPolicy = {\n\tpipeline: true,\n\tinput: false,\n\toutput: false\n};\n\nexport interface DeleteIntegrationOptions {\n\trequest: MonadRequest;\n\torg: string;\n\t/** Pipeline to remove. Omit when the input has no pipeline. */\n\tpipelineId?: string;\n\t/** Input to remove. Omit to leave it. */\n\tinputId?: string;\n\t/** Output to remove (the auto-created dev/null sink). Omit to leave it. */\n\toutputId?: string;\n\t/**\n\t * What to actually delete. Defaults to everything supplied\n\t * (CLEANUP_FULL). Set a flag false to keep that resource even when\n\t * its id is provided.\n\t */\n\tcleanup?: CleanupPolicy;\n}\n\n/**\n * Delete an integration per a cleanup policy. Order matters: the\n * pipeline references the input and output, so it goes first. Only\n * resources whose id is supplied AND whose policy flag is true are\n * removed — so nothing the host wants to keep is touched, and nothing\n * it wants gone is left orphaned.\n */\nexport async function deleteIntegration(opts: DeleteIntegrationOptions): Promise<void> {\n\tconst { request, org, pipelineId, inputId, outputId } = opts;\n\tconst policy: Required<CleanupPolicy> = {\n\t\tpipeline: true,\n\t\tinput: true,\n\t\toutput: true,\n\t\t...opts.cleanup\n\t};\n\tif (pipelineId && policy.pipeline) {\n\t\tawait request(`/v2/${org}/pipelines/${pipelineId}`, { method: 'DELETE' });\n\t}\n\tif (inputId && policy.input) {\n\t\tawait request(`/v1/${org}/inputs/${inputId}`, { method: 'DELETE' });\n\t}\n\tif (outputId && policy.output) {\n\t\tawait request(`/v1/${org}/outputs/${outputId}`, { method: 'DELETE' });\n\t}\n}\n\n/* ===== build ===== */\n\nexport interface BuildDevNullPipelineOptions {\n\trequest: MonadRequest;\n\torg: string;\n\tinputId: string;\n\t/** Used to name the auto-created output and pipeline. */\n\tinputName: string;\n\t/** Status polls before giving up (default 15, ~2s apart). */\n\tpollAttempts?: number;\n}\n\nexport interface BuiltPipeline {\n\tpipelineId: string;\n\toutputId: string;\n\tstatus: string;\n\t/** True once the pipeline reports Running. */\n\tactive: boolean;\n}\n\n/**\n * Stand up a dev/null pipeline for a freshly-configured input: create a\n * dev/null output, create an enabled pipeline wiring input → output,\n * then poll until it reports Running. The POC's \"what happens after an\n * input is configured\" path, packaged as a reusable harness call.\n */\nexport async function buildDevNullPipeline(\n\topts: BuildDevNullPipelineOptions\n): Promise<BuiltPipeline> {\n\tconst { request, org, inputId, inputName, pollAttempts = 15 } = opts;\n\tconst label = inputName || 'Input';\n\n\tconst output = await request(`/v2/${org}/outputs`, {\n\t\tmethod: 'POST',\n\t\tbody: JSON.stringify({\n\t\t\toutput_type: 'dev-null',\n\t\t\tname: `${label} → /dev/null`,\n\t\t\tdescription: 'Auto-created sink for embed pipeline',\n\t\t\tpromise_id: '',\n\t\t\tconfig: { settings: {}, secrets: {} }\n\t\t})\n\t});\n\n\tconst pipeline = await request(`/v2/${org}/pipelines/`, {\n\t\tmethod: 'POST',\n\t\tbody: JSON.stringify({\n\t\t\tname: `${label} pipeline`,\n\t\t\tdescription: 'Auto-created when the input was configured via embed',\n\t\t\tenabled: true,\n\t\t\tnodes: [\n\t\t\t\t{ slug: 'in', component_id: inputId, component_type: 'input', enabled: true },\n\t\t\t\t{ slug: 'out', component_id: output.id, component_type: 'output', enabled: true }\n\t\t\t],\n\t\t\tedges: [\n\t\t\t\t{\n\t\t\t\t\tfrom_node_instance_id: 'in',\n\t\t\t\t\tto_node_instance_id: 'out',\n\t\t\t\t\tdescription: 'all records',\n\t\t\t\t\tconditions: { operator: 'always' }\n\t\t\t\t}\n\t\t\t]\n\t\t})\n\t});\n\n\tlet status = 'Pending';\n\tfor (let attempt = 0; attempt < pollAttempts; attempt++) {\n\t\tconst s = await request(`/v2/${org}/pipelines/${pipeline.id}/status`);\n\t\tstatus = s?.status ?? 'Unknown';\n\t\tif (status === 'Running' || status === 'Erroring') break;\n\t\tawait new Promise((r) => setTimeout(r, 2000));\n\t}\n\n\treturn {\n\t\tpipelineId: pipeline.id,\n\t\toutputId: output.id,\n\t\tstatus,\n\t\tactive: status === 'Running'\n\t};\n}\n","/**\n * @monad-inc/embed/connect — host-side SDK for embedding Monad's\n * connector-config UI as a secure iframe.\n *\n * The customer's page calls `createConnectorFrame()`; this injects an\n * <iframe> pointing at a Monad-hosted page (@monad-inc/embed-frame). The\n * connector form — and every secret the user types into it — runs\n * inside that iframe on Monad's origin. Same-origin policy means the\n * customer's own JavaScript can never read those secrets. The customer\n * gets back only a connector id (a safe reference), the same way a\n * Stripe integration only ever sees a `pm_…` token.\n *\n * The host is still responsible for one thing: minting a short-lived\n * embed session token from its backend (POST /v1/embed/sessions, with\n * the long-lived API key) and handing it to `createConnectorFrame`.\n * That token travels into the iframe over postMessage, never the URL.\n */\nimport {\n\tPROTOCOL_SOURCE,\n\tPROTOCOL_VERSION,\n\ttype Appearance,\n\ttype ComponentKind,\n\ttype FrameMessage,\n\ttype HostMessage\n} from './protocol';\n\nexport {\n\tPROTOCOL_SOURCE,\n\tPROTOCOL_VERSION,\n\ttype Appearance,\n\ttype ComponentKind,\n\ttype FrameMessage,\n\ttype HostMessage,\n\ttype InitMessage\n} from './protocol';\n\nexport {\n\tfindIntegrationPipeline,\n\tsetIntegrationEnabled,\n\tdisableIntegration,\n\tenableIntegration,\n\tdeleteIntegration,\n\tbuildDevNullPipeline,\n\tCLEANUP_FULL,\n\tCLEANUP_KEEP_OUTPUT,\n\tCLEANUP_PIPELINE_ONLY,\n\ttype MonadRequest,\n\ttype ResolvedIntegration,\n\ttype SetEnabledOptions,\n\ttype DeleteIntegrationOptions,\n\ttype CleanupPolicy,\n\ttype BuildDevNullPipelineOptions,\n\ttype BuiltPipeline\n} from './lifecycle';\n\nexport interface ConnectorFrameOptions {\n\t/** DOM node (or selector) the iframe is appended to. */\n\tcontainer: HTMLElement | string;\n\t/**\n\t * URL of the Monad embed-frame page. Defaults to\n\t * \"https://app.monad.com/embed\" (production). Override for staging\n\t * (\"https://app.monad.security/embed\") or local testing\n\t * (\"http://localhost:5173/embed\"). MUST be a different origin than\n\t * the host page — that cross-origin boundary is what isolates the\n\t * secrets. A path component is allowed (so the embed page can live\n\t * at a sub-route of the main app); the postMessage security checks\n\t * compare against the URL's origin, not its full href.\n\t */\n\tframeOrigin?: string;\n\t/**\n\t * Monad API base the iframe should call. Defaults to\n\t * \"https://app.monad.com/api\" (production). Override for non-prod\n\t * environments. Set this together with `frameOrigin` — mixing\n\t * environments will not work.\n\t */\n\tapiBase?: string;\n\t/** Connector type id, e.g. \"runzero-findings\". */\n\ttypeId: string;\n\tkind: ComponentKind;\n\t/** Short-lived embed session token (host backend mints it). */\n\tsessionToken: string;\n\t/**\n\t * Team org id the session is scoped to. The host backend already knows\n\t * this — it's the same value passed to `/v1/embed/sessions` when minting\n\t * the token. Forwarded into the iframe so it can build `/v1/<org>/*` and\n\t * `/v2/<org>/*` URLs.\n\t */\n\torganizationId: string;\n\t/** Display name shown in the frame header. */\n\tdisplayName?: string;\n\t/**\n\t * Name for the created connector. On create, defaults to the connector\n\t * type's name plus a short unique suffix. On edit, omit to keep the\n\t * existing name.\n\t */\n\tname?: string;\n\t/**\n\t * Description for the created connector. On create, defaults to the\n\t * connector type's description. On edit, omit to keep the existing\n\t * description.\n\t */\n\tdescription?: string;\n\t/**\n\t * Show an editable name input inside the iframe (prefilled with the\n\t * resolved name) so the end user can name the connector themselves.\n\t * A non-empty name is required when shown. Defaults to false.\n\t */\n\tisNameEditable?: boolean;\n\t/**\n\t * Show an editable description input inside the iframe (prefilled with\n\t * the resolved description). Defaults to false.\n\t */\n\tisDescriptionEditable?: boolean;\n\t/** Existing connector id — pass for edit mode, omit to create. */\n\texistingId?: string;\n\t/**\n\t * Whether the synthetic-data toggle is exposed in the form. Defaults\n\t * to `false` — production embed flows keep it hidden.\n\t */\n\tsynthetic?: boolean;\n\t/** Theming tokens applied as CSS variables inside the iframe. */\n\tappearance?: Appearance;\n\t/**\n\t * Stylesheet URLs to apply inside the iframe. All-or-nothing: if\n\t * any URL is provided, the iframe loads ONLY these and skips its\n\t * built-in Monad UI defaults; if omitted/empty, the iframe loads\n\t * the defaults and nothing else. URLs must be reachable from the\n\t * iframe — typically served by the host page's own origin.\n\t */\n\tstylesheets?: string[];\n\t/** Fired when the user saves successfully. */\n\tonSave?: (connector: { id: string; name?: string }) => void;\n\t/** Fired when the user cancels. */\n\tonCancel?: () => void;\n\t/** Fired on a save/test/load failure inside the iframe. */\n\tonError?: (message: string) => void;\n}\n\nexport interface ConnectorFrame {\n\t/** Remove the iframe and stop listening for its messages. */\n\tdestroy(): void;\n}\n\n/**\n * Production defaults. Customers integrating against prod don't pass\n * `frameOrigin`/`apiBase` at all; non-prod environments override both\n * together. Treat these as a public API contract — changing a hostname\n * is a breaking change for anyone on the default.\n */\nconst DEFAULT_FRAME_ORIGIN = 'https://app.monad.com/embed';\nconst DEFAULT_API_BASE = 'https://app.monad.com/api';\n\n/**\n * Inject the Monad connector iframe into `container` and wire the\n * postMessage channel. Returns a handle whose `destroy()` tears it all\n * down.\n *\n * Handshake:\n * 1. iframe loads, posts `ready`;\n * 2. host replies with `init` (session token, type, appearance);\n * 3. iframe mounts the form and streams `resize` as content grows;\n * 4. on save, iframe posts `saved {connector}`; on cancel, `cancel`.\n */\nexport function createConnectorFrame(opts: ConnectorFrameOptions): ConnectorFrame {\n\tconst container =\n\t\ttypeof opts.container === 'string'\n\t\t\t? document.querySelector<HTMLElement>(opts.container)\n\t\t\t: opts.container;\n\tif (!container) {\n\t\tthrow new Error('[monad-inc/connect] container element not found');\n\t}\n\n\tconst frameInput = opts.frameOrigin ?? DEFAULT_FRAME_ORIGIN;\n\tconst apiBase = opts.apiBase ?? DEFAULT_API_BASE;\n\n\t// Split the input URL into two pieces:\n\t// * `frameUrl` keeps the path (e.g. /embed) — used as iframe.src.\n\t// * `frameOrigin` is the bare origin — used for the postMessage\n\t// security checks below, which must compare against the value\n\t// the browser surfaces on MessageEvent.origin (always a bare\n\t// origin, never a path).\n\t// Previously these were collapsed into one `new URL(...).href`,\n\t// which silently broke any frameOrigin with a path — the equality\n\t// check `event.origin !== frameOrigin` would always fail and `init`\n\t// never made it back to the iframe.\n\tconst frameUrl = new URL(frameInput);\n\tconst frameOrigin = frameUrl.origin;\n\tconst framePath = frameUrl.pathname.replace(/\\/$/, '');\n\n\tconst iframe = document.createElement('iframe');\n\tiframe.className = 'monad-connect-frame';\n\tiframe.title = 'Monad connector configuration';\n\tiframe.style.width = '100%';\n\tiframe.style.border = '0';\n\tiframe.style.display = 'block';\n\t// The iframe needs its own scripts and same-origin access to the\n\t// Monad API; it must NOT get same-origin access to the host. A\n\t// cross-origin iframe already can't reach the host — no sandbox\n\t// attribute is needed for isolation, and adding one would block the\n\t// form's own scripts. Forms/popups stay enabled for OAuth-style\n\t// connectors that may open a window.\n\tiframe.allow = 'clipboard-write';\n\t// parentOrigin lets the iframe scope its postMessage target + sender\n\t// check. It is NOT a secret and NOT a capability — lying about it\n\t// only breaks the liar's own embed (see protocol.ts).\n\tiframe.src = `${frameOrigin}${framePath}/?parentOrigin=${encodeURIComponent(window.location.origin)}`;\n\n\tlet destroyed = false;\n\n\tfunction postToFrame(msg: HostMessage): void {\n\t\tiframe.contentWindow?.postMessage(msg, frameOrigin);\n\t}\n\n\tfunction onMessage(event: MessageEvent): void {\n\t\tif (destroyed) return;\n\t\t// --- security boundary: validate before trusting anything ---\n\t\tif (event.origin !== frameOrigin) return;\n\t\tif (event.source !== iframe.contentWindow) return;\n\t\tconst msg = event.data as FrameMessage | undefined;\n\t\tif (!msg || msg.source !== PROTOCOL_SOURCE) return;\n\n\t\tswitch (msg.type) {\n\t\t\tcase 'ready':\n\t\t\t\tpostToFrame({\n\t\t\t\t\tsource: PROTOCOL_SOURCE,\n\t\t\t\t\ttype: 'init',\n\t\t\t\t\tversion: PROTOCOL_VERSION,\n\t\t\t\t\tsessionToken: opts.sessionToken,\n\t\t\t\t\tapiBase,\n\t\t\t\t\torganizationId: opts.organizationId,\n\t\t\t\t\ttypeId: opts.typeId,\n\t\t\t\t\tkind: opts.kind,\n\t\t\t\t\tdisplayName: opts.displayName,\n\t\t\t\t\tname: opts.name,\n\t\t\t\t\tdescription: opts.description,\n\t\t\t\t\tisNameEditable: opts.isNameEditable,\n\t\t\t\t\tisDescriptionEditable: opts.isDescriptionEditable,\n\t\t\t\t\texistingId: opts.existingId,\n\t\t\t\t\tsynthetic: opts.synthetic,\n\t\t\t\t\tappearance: opts.appearance,\n\t\t\t\t\tstylesheets: opts.stylesheets\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase 'resize': {\n\t\t\t\t// Clamp to a sane range so a renderer bug in the iframe\n\t\t\t\t// can't blow up the host layout with a multi-million-px\n\t\t\t\t// height. The origin check above already gates who can\n\t\t\t\t// send this — clamping is defense in depth. Number.isFinite\n\t\t\t\t// guards against non-numeric heights that would otherwise\n\t\t\t\t// produce \"NaNpx\" and leave the iframe collapsed.\n\t\t\t\tconst h = Number.isFinite(msg.height) ? msg.height : 0;\n\t\t\t\tiframe.style.height = `${Math.min(Math.max(h, 0), 20000)}px`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'saved':\n\t\t\t\topts.onSave?.(msg.connector);\n\t\t\t\tbreak;\n\t\t\tcase 'error':\n\t\t\t\topts.onError?.(msg.message);\n\t\t\t\tbreak;\n\t\t\tcase 'cancel':\n\t\t\t\topts.onCancel?.();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\twindow.addEventListener('message', onMessage);\n\tcontainer.appendChild(iframe);\n\n\treturn {\n\t\tdestroy(): void {\n\t\t\tdestroyed = true;\n\t\t\twindow.removeEventListener('message', onMessage);\n\t\t\tiframe.remove();\n\t\t}\n\t};\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/connect/protocol.ts","../../src/connect/lifecycle.ts","../../src/connect/index.ts"],"sourcesContent":["/**\n * postMessage contract between the host page (@monad-inc/embed/connect) and\n * the Monad-hosted iframe (@monad-inc/embed-frame).\n *\n * Why a protocol module: both sides must agree on message shapes, and a\n * drifting contract is a silent failure (a message just gets ignored).\n * @monad-inc/embed/connect owns it because it's the public-facing SDK; the\n * iframe app imports these types from here.\n *\n * Security model:\n * - Origin is the real boundary. Every listener validates\n * `event.origin` against an expected value before trusting a\n * message. The iframe knows its parent's origin (passed as a URL\n * param); the host knows the frame's origin (it chose it).\n * - `event.source` is also checked host-side so a sibling iframe on\n * the same origin can't impersonate ours.\n * - The `source: 'monad-embed'` tag is NOT security — it's just a\n * cheap filter so unrelated postMessage traffic (analytics SDKs,\n * devtools, wallet extensions) is ignored without parsing.\n * - The session token travels in the `init` message body, never in\n * the iframe URL, so it stays out of browser history and Referer.\n */\n\nexport const PROTOCOL_SOURCE = 'monad-embed' as const;\nexport const PROTOCOL_VERSION = 1 as const;\n\nexport type ComponentKind = 'input' | 'output';\n\n/**\n * Appearance tokens the host may pass to theme the iframe. The iframe\n * applies each as a `--monad-{token}` CSS variable. The host cannot\n * style across the iframe boundary directly (same-origin policy), so\n * this is the only theming channel — same idea as Stripe's `appearance`.\n */\nexport interface Appearance {\n\tcolorPrimary?: string;\n\tcolorText?: string;\n\tcolorBackground?: string;\n\tcolorBorder?: string;\n\tfontFamily?: string;\n\tborderRadius?: string;\n}\n\n/**\n * Copy overrides for a single field of a connector's config form.\n *\n * Only copy can be overridden. A field's type, requiredness, allowed\n * values, and default are fixed by the connector and cannot be changed\n * here.\n *\n * `placeholder` is a hint, not a default: a field the user never touches\n * still submits an empty value.\n *\n * Keys mirror the config shape returned for the connector type, so they\n * are snake_case — unlike the camelCase options on\n * `ConnectorFrameOptions`. Field keys are the connector's own keys; the\n * Datadog output's source field is `ddsource`, not `source`. Keys that\n * don't match the connector are ignored.\n */\nexport interface FieldMetaOverride {\n\t/** Field label. An empty string is ignored — unlabelled fields are a bug. */\n\tname?: string;\n\t/** Helper text shown with the control. Empty string blanks it. */\n\tdescription?: string;\n\t/** Ephemeral hint text inside the control. Empty string blanks it. */\n\tplaceholder?: string;\n\t/** Overrides for a nested object's child fields, keyed by child wire key. */\n\tchildren?: Record<string, FieldMetaOverride>;\n\t/** Overrides for a discriminated-union (one_of) field. */\n\tdiscriminator?: DiscriminatorMetaOverride;\n}\n\n/**\n * Copy overrides for a one_of field's variant selector. The set of\n * variants, their order, and which one is selected by default are fixed\n * by the connector.\n */\nexport interface DiscriminatorMetaOverride {\n\t/** Label on the variant selector (defaults to \"Type\"). */\n\tname?: string;\n\t/** Helper text for the variant selector. */\n\tdescription?: string;\n\t/** Per-variant overrides, keyed by discriminator value. */\n\tone_of?: Record<string, FieldMetaOverride>;\n}\n\n/**\n * Config-form copy overrides for one connector, mirroring the two\n * sections returned for the connector type. Overrides never add fields —\n * keys that don't match the connector are ignored.\n */\nexport interface ConfigMetaOverrides {\n\tsettings?: Record<string, FieldMetaOverride>;\n\tsecrets?: Record<string, FieldMetaOverride>;\n}\n\n/* ===== iframe -> host ===== */\n\nexport interface ReadyMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'ready';\n\tversion: number;\n}\n\nexport interface ResizeMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'resize';\n\t/** Content height in CSS pixels — the host sizes the <iframe> to this. */\n\theight: number;\n}\n\nexport interface SavedMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'saved';\n\t/** Safe reference to the created/updated connector — no secret material. */\n\tconnector: { id: string; name?: string };\n}\n\nexport interface ErrorMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'error';\n\tmessage: string;\n}\n\nexport interface CancelMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'cancel';\n}\n\nexport type FrameMessage =\n\t| ReadyMessage\n\t| ResizeMessage\n\t| SavedMessage\n\t| ErrorMessage\n\t| CancelMessage;\n\n/* ===== host -> iframe ===== */\n\nexport interface InitMessage {\n\tsource: typeof PROTOCOL_SOURCE;\n\ttype: 'init';\n\tversion: number;\n\t/** Short-lived embed session token minted by the host's backend. */\n\tsessionToken: string;\n\t/** Monad API base, e.g. \"https://api.monad.com\" or dev \"http://localhost/api\". */\n\tapiBase: string;\n\t/**\n\t * Sub-org id the session token is scoped to. The iframe uses this to\n\t * build `/v1/<org>/*` and `/v2/<org>/*` URLs. Must match the token's\n\t * `org` claim — server-side `org_access.Middleware` will 403 if not.\n\t */\n\torganizationId: string;\n\t/** Connector type id, e.g. \"runzero-findings\". */\n\ttypeId: string;\n\tkind: ComponentKind;\n\t/** Display name shown in the frame header. */\n\tdisplayName?: string;\n\t/**\n\t * Name for the created connector. On create, defaults to the connector\n\t * type's name plus a short unique suffix. On edit, omit to keep the\n\t * existing name.\n\t */\n\tname?: string;\n\t/**\n\t * Description for the created connector. On create, defaults to the\n\t * connector type's description. On edit, omit to keep the existing\n\t * description.\n\t */\n\tdescription?: string;\n\t/**\n\t * Show an editable name input inside the iframe, prefilled with the\n\t * resolved name, so the end user can name the connector themselves.\n\t * A non-empty name is required when shown. Defaults to false.\n\t */\n\tisNameEditable?: boolean;\n\t/**\n\t * Show an editable description input inside the iframe, prefilled with\n\t * the resolved description. Defaults to false.\n\t */\n\tisDescriptionEditable?: boolean;\n\t/** Existing connector id — present for edit mode, absent for create. */\n\texistingId?: string;\n\t/**\n\t * Whether the synthetic-data toggle is exposed in the form. Defaults\n\t * to `false` (toggle hidden). Production embed customers keep this\n\t * off — synthetic mode is a Monad-internal testing affordance.\n\t */\n\tsynthetic?: boolean;\n\t/** Theming tokens applied as CSS variables inside the iframe. */\n\tappearance?: Appearance;\n\t/**\n\t * Stylesheet URLs the host wants to apply to the iframe page. The\n\t * iframe loads these as `<link rel=\"stylesheet\">` tags before\n\t * rendering the form.\n\t *\n\t * All-or-nothing: if this array is present and non-empty, the\n\t * iframe loads ONLY these stylesheets and does NOT load its\n\t * built-in Monad UI defaults. If it's absent or empty, the iframe\n\t * loads the built-in defaults and nothing else. Mixing host CSS\n\t * with the defaults is intentionally not supported — partial\n\t * styling tends to produce confusing half-themed forms.\n\t *\n\t * URLs MUST be reachable from the iframe origin (typically by\n\t * being served from the host page's own origin, which the browser\n\t * permits for stylesheet `<link>` loads even cross-origin).\n\t */\n\tstylesheets?: string[];\n\t/**\n\t * Per-field copy overrides merged onto the connector's config form.\n\t * Copy only — see FieldMetaOverride.\n\t */\n\tconfigMetaOverrides?: ConfigMetaOverrides;\n}\n\nexport type HostMessage = InitMessage;\n","/**\n * Connector-lifecycle helpers — the \"wiring harness\" a host (Okta, in\n * the demo's fiction) drops into its own backend to power Disable /\n * Enable / Delete buttons in its own UI.\n *\n * Unlike createConnectorFrame (browser-only), these are isomorphic:\n * they just sequence Monad's standard `/v2/{org}/*` + `/v1/{org}/*`\n * API calls. They run wherever the host holds its API key — almost\n * always its backend.\n *\n * The host supplies a `MonadRequest` thunk that performs an\n * authenticated request and returns parsed JSON. The harness owns the\n * non-obvious parts: which endpoint + verb each action maps to, the\n * delete ordering (pipeline before the input/output it references),\n * and resolving an input id to its pipeline without the host having to\n * store that mapping.\n */\n\n/**\n * Authenticated request against the Monad API, returning parsed JSON\n * (or undefined for empty responses). The host implements this with\n * its long-lived API key and base URL. Path is the full API path,\n * e.g. `/v2/{org}/pipelines/{id}`.\n */\nexport type MonadRequest = (path: string, init?: RequestInit) => Promise<any>;\n\n/* ===== resolve ===== */\n\n/**\n * Where an integration's resources live and their current state.\n * Returned by findIntegrationPipeline so the host can act on an\n * integration knowing only the input id.\n */\nexport interface ResolvedIntegration {\n\torg: string;\n\tpipelineId: string;\n\t/** Whether the pipeline is currently enabled. */\n\tenabled: boolean;\n\tinputId: string;\n\t/** Output id of the pipeline's destination node, if it has one. */\n\toutputId?: string;\n}\n\ninterface PipelineNode {\n\tcomponent_id?: string;\n\tcomponent_type?: string;\n}\n\n/**\n * Find the pipeline an input feeds. Lets the host act on an integration\n * (disable/enable/delete) knowing only the input id — no need to\n * persist an input→pipeline map of its own.\n *\n * The pipelines list is slim (no node wiring), so this fetches each\n * pipeline's detail and scans for the input node. Fine for the modest\n * pipeline counts a single tenant sub-org has; a host managing many\n * pipelines per sub-org should instead persist the `pipelineId`\n * buildDevNullPipeline returns and skip the lookup.\n *\n * Returns null when the input has no pipeline.\n */\nexport async function findIntegrationPipeline(opts: {\n\trequest: MonadRequest;\n\torg: string;\n\tinputId: string;\n}): Promise<ResolvedIntegration | null> {\n\tconst { request, org, inputId } = opts;\n\tconst listed = await request(`/v2/${org}/pipelines/`);\n\tconst pipelines: any[] = Array.isArray(listed)\n\t\t? listed\n\t\t: (listed?.pipelines ?? listed?.data ?? []);\n\n\tfor (const summary of pipelines) {\n\t\tif (!summary?.id) continue;\n\t\tlet detail: any;\n\t\ttry {\n\t\t\tdetail = await request(`/v2/${org}/pipelines/${summary.id}`);\n\t\t} catch {\n\t\t\tcontinue; // skip a pipeline we can't read rather than failing\n\t\t}\n\t\tconst pipeline = detail?.config ?? detail;\n\t\tconst nodes: PipelineNode[] = pipeline?.nodes ?? [];\n\t\tconst inputNode = nodes.find((n) => n.component_type === 'input' && n.component_id === inputId);\n\t\tif (!inputNode) continue;\n\t\tconst outputNode = nodes.find((n) => n.component_type === 'output');\n\t\treturn {\n\t\t\torg,\n\t\t\tpipelineId: summary.id,\n\t\t\tenabled: Boolean(pipeline.enabled),\n\t\t\tinputId,\n\t\t\toutputId: outputNode?.component_id\n\t\t};\n\t}\n\treturn null;\n}\n\n/* ===== enable / disable ===== */\n\nexport interface SetEnabledOptions {\n\trequest: MonadRequest;\n\torg: string;\n\tpipelineId: string;\n\t/** true → resume processing, false → stop (config is kept either way). */\n\tenabled: boolean;\n}\n\n/**\n * Enable or disable an integration's pipeline. Disabling stops data\n * flowing but destroys nothing — the input, output, and pipeline\n * config all survive, so a later enable resumes it as it was.\n *\n * The pipeline PATCH endpoint replaces the whole config rather than\n * accepting a partial, so this reads the current pipeline and sends it\n * back with only the `enabled` flag flipped.\n *\n * Race warning: this is GET-then-PATCH on the same resource, so a\n * concurrent edit between the two requests gets clobbered. Fine for\n * human-driven toggles. If you call this from automation, fan-out, or\n * a retry loop, ask the API team for a server-side `{ enabled }`\n * partial PATCH instead — the round-trip we do here is only a\n * workaround for the current full-config replace semantics.\n */\nexport async function setIntegrationEnabled(opts: SetEnabledOptions): Promise<void> {\n\tconst { request, org, pipelineId, enabled } = opts;\n\tconst detail = await request(`/v2/${org}/pipelines/${pipelineId}`);\n\tconst pipeline = detail?.config ?? detail;\n\tawait request(`/v2/${org}/pipelines/${pipelineId}`, {\n\t\tmethod: 'PATCH',\n\t\tbody: JSON.stringify({\n\t\t\tname: pipeline.name,\n\t\t\tdescription: pipeline.description ?? '',\n\t\t\tenabled,\n\t\t\tnodes: (pipeline.nodes ?? []).map((n: any) => ({\n\t\t\t\tid: n.id,\n\t\t\t\tslug: n.slug,\n\t\t\t\tcomponent_id: n.component_id,\n\t\t\t\tcomponent_type: n.component_type,\n\t\t\t\tenabled: n.enabled ?? true\n\t\t\t})),\n\t\t\tedges: (pipeline.edges ?? []).map((e: any) => ({\n\t\t\t\tname: e.name,\n\t\t\t\tdescription: e.description ?? '',\n\t\t\t\tfrom_node_instance_id: e.from_node_instance_id,\n\t\t\t\tto_node_instance_id: e.to_node_instance_id,\n\t\t\t\tdisabled: e.disabled ?? false,\n\t\t\t\tconditions: e.conditions\n\t\t\t}))\n\t\t})\n\t});\n}\n\n/** Convenience: disable the pipeline (stop without deleting). */\nexport function disableIntegration(opts: Omit<SetEnabledOptions, 'enabled'>): Promise<void> {\n\treturn setIntegrationEnabled({ ...opts, enabled: false });\n}\n\n/** Convenience: re-enable a disabled pipeline. */\nexport function enableIntegration(opts: Omit<SetEnabledOptions, 'enabled'>): Promise<void> {\n\treturn setIntegrationEnabled({ ...opts, enabled: true });\n}\n\n/* ===== delete ===== */\n\n/**\n * Which resources a delete removes. An embed integration is a pipeline\n * + an input + (in the dev/null flow) an output — but hosts provision\n * and own tenant resources differently. Some keep a shared output,\n * some keep the input so it can be re-wired later, some only ever tear\n * down the pipeline. Choose the policy that matches how you provision;\n * a flag left undefined defaults to `true` (delete it).\n *\n * Three ready-made policies are exported — CLEANUP_FULL,\n * CLEANUP_KEEP_OUTPUT, CLEANUP_PIPELINE_ONLY — or pass your own.\n */\nexport interface CleanupPolicy {\n\t/** Delete the pipeline. Default: true. */\n\tpipeline?: boolean;\n\t/** Delete the input connector. Default: true. */\n\tinput?: boolean;\n\t/** Delete the output connector. Default: true. */\n\toutput?: boolean;\n}\n\n/** Tear down everything — pipeline, input, and output. */\nexport const CLEANUP_FULL: CleanupPolicy = { pipeline: true, input: true, output: true };\n\n/** Remove the pipeline and input but keep the output (e.g. a shared sink). */\nexport const CLEANUP_KEEP_OUTPUT: CleanupPolicy = {\n\tpipeline: true,\n\tinput: true,\n\toutput: false\n};\n\n/** Remove only the pipeline; keep the input so it can be re-wired. */\nexport const CLEANUP_PIPELINE_ONLY: CleanupPolicy = {\n\tpipeline: true,\n\tinput: false,\n\toutput: false\n};\n\nexport interface DeleteIntegrationOptions {\n\trequest: MonadRequest;\n\torg: string;\n\t/** Pipeline to remove. Omit when the input has no pipeline. */\n\tpipelineId?: string;\n\t/** Input to remove. Omit to leave it. */\n\tinputId?: string;\n\t/** Output to remove (the auto-created dev/null sink). Omit to leave it. */\n\toutputId?: string;\n\t/**\n\t * What to actually delete. Defaults to everything supplied\n\t * (CLEANUP_FULL). Set a flag false to keep that resource even when\n\t * its id is provided.\n\t */\n\tcleanup?: CleanupPolicy;\n}\n\n/**\n * Delete an integration per a cleanup policy. Order matters: the\n * pipeline references the input and output, so it goes first. Only\n * resources whose id is supplied AND whose policy flag is true are\n * removed — so nothing the host wants to keep is touched, and nothing\n * it wants gone is left orphaned.\n */\nexport async function deleteIntegration(opts: DeleteIntegrationOptions): Promise<void> {\n\tconst { request, org, pipelineId, inputId, outputId } = opts;\n\tconst policy: Required<CleanupPolicy> = {\n\t\tpipeline: true,\n\t\tinput: true,\n\t\toutput: true,\n\t\t...opts.cleanup\n\t};\n\tif (pipelineId && policy.pipeline) {\n\t\tawait request(`/v2/${org}/pipelines/${pipelineId}`, { method: 'DELETE' });\n\t}\n\tif (inputId && policy.input) {\n\t\tawait request(`/v1/${org}/inputs/${inputId}`, { method: 'DELETE' });\n\t}\n\tif (outputId && policy.output) {\n\t\tawait request(`/v1/${org}/outputs/${outputId}`, { method: 'DELETE' });\n\t}\n}\n\n/* ===== build ===== */\n\nexport interface BuildDevNullPipelineOptions {\n\trequest: MonadRequest;\n\torg: string;\n\tinputId: string;\n\t/** Used to name the auto-created output and pipeline. */\n\tinputName: string;\n\t/** Status polls before giving up (default 15, ~2s apart). */\n\tpollAttempts?: number;\n}\n\nexport interface BuiltPipeline {\n\tpipelineId: string;\n\toutputId: string;\n\tstatus: string;\n\t/** True once the pipeline reports Running. */\n\tactive: boolean;\n}\n\n/**\n * Stand up a dev/null pipeline for a freshly-configured input: create a\n * dev/null output, create an enabled pipeline wiring input → output,\n * then poll until it reports Running. The POC's \"what happens after an\n * input is configured\" path, packaged as a reusable harness call.\n */\nexport async function buildDevNullPipeline(\n\topts: BuildDevNullPipelineOptions\n): Promise<BuiltPipeline> {\n\tconst { request, org, inputId, inputName, pollAttempts = 15 } = opts;\n\tconst label = inputName || 'Input';\n\n\tconst output = await request(`/v2/${org}/outputs`, {\n\t\tmethod: 'POST',\n\t\tbody: JSON.stringify({\n\t\t\toutput_type: 'dev-null',\n\t\t\tname: `${label} → /dev/null`,\n\t\t\tdescription: 'Auto-created sink for embed pipeline',\n\t\t\tpromise_id: '',\n\t\t\tconfig: { settings: {}, secrets: {} }\n\t\t})\n\t});\n\n\tconst pipeline = await request(`/v2/${org}/pipelines/`, {\n\t\tmethod: 'POST',\n\t\tbody: JSON.stringify({\n\t\t\tname: `${label} pipeline`,\n\t\t\tdescription: 'Auto-created when the input was configured via embed',\n\t\t\tenabled: true,\n\t\t\tnodes: [\n\t\t\t\t{ slug: 'in', component_id: inputId, component_type: 'input', enabled: true },\n\t\t\t\t{ slug: 'out', component_id: output.id, component_type: 'output', enabled: true }\n\t\t\t],\n\t\t\tedges: [\n\t\t\t\t{\n\t\t\t\t\tfrom_node_instance_id: 'in',\n\t\t\t\t\tto_node_instance_id: 'out',\n\t\t\t\t\tdescription: 'all records',\n\t\t\t\t\tconditions: { operator: 'always' }\n\t\t\t\t}\n\t\t\t]\n\t\t})\n\t});\n\n\tlet status = 'Pending';\n\tfor (let attempt = 0; attempt < pollAttempts; attempt++) {\n\t\tconst s = await request(`/v2/${org}/pipelines/${pipeline.id}/status`);\n\t\tstatus = s?.status ?? 'Unknown';\n\t\tif (status === 'Running' || status === 'Erroring') break;\n\t\tawait new Promise((r) => setTimeout(r, 2000));\n\t}\n\n\treturn {\n\t\tpipelineId: pipeline.id,\n\t\toutputId: output.id,\n\t\tstatus,\n\t\tactive: status === 'Running'\n\t};\n}\n","/**\n * @monad-inc/embed/connect — host-side SDK for embedding Monad's\n * connector-config UI as a secure iframe.\n *\n * The customer's page calls `createConnectorFrame()`; this injects an\n * <iframe> pointing at a Monad-hosted page (@monad-inc/embed-frame). The\n * connector form — and every secret the user types into it — runs\n * inside that iframe on Monad's origin. Same-origin policy means the\n * customer's own JavaScript can never read those secrets. The customer\n * gets back only a connector id (a safe reference), the same way a\n * Stripe integration only ever sees a `pm_…` token.\n *\n * The host is still responsible for one thing: minting a short-lived\n * embed session token from its backend (POST /v1/embed/sessions, with\n * the long-lived API key) and handing it to `createConnectorFrame`.\n * That token travels into the iframe over postMessage, never the URL.\n */\nimport {\n\tPROTOCOL_SOURCE,\n\tPROTOCOL_VERSION,\n\ttype Appearance,\n\ttype ComponentKind,\n\ttype ConfigMetaOverrides,\n\ttype FrameMessage,\n\ttype HostMessage\n} from './protocol';\n\nexport {\n\tPROTOCOL_SOURCE,\n\tPROTOCOL_VERSION,\n\ttype Appearance,\n\ttype ComponentKind,\n\ttype ConfigMetaOverrides,\n\ttype DiscriminatorMetaOverride,\n\ttype FieldMetaOverride,\n\ttype FrameMessage,\n\ttype HostMessage,\n\ttype InitMessage\n} from './protocol';\n\nexport {\n\tfindIntegrationPipeline,\n\tsetIntegrationEnabled,\n\tdisableIntegration,\n\tenableIntegration,\n\tdeleteIntegration,\n\tbuildDevNullPipeline,\n\tCLEANUP_FULL,\n\tCLEANUP_KEEP_OUTPUT,\n\tCLEANUP_PIPELINE_ONLY,\n\ttype MonadRequest,\n\ttype ResolvedIntegration,\n\ttype SetEnabledOptions,\n\ttype DeleteIntegrationOptions,\n\ttype CleanupPolicy,\n\ttype BuildDevNullPipelineOptions,\n\ttype BuiltPipeline\n} from './lifecycle';\n\nexport interface ConnectorFrameOptions {\n\t/** DOM node (or selector) the iframe is appended to. */\n\tcontainer: HTMLElement | string;\n\t/**\n\t * URL of the Monad embed-frame page. Defaults to\n\t * \"https://app.monad.com/embed\" (production). Override for staging\n\t * (\"https://app.monad.security/embed\") or local testing\n\t * (\"http://localhost:5173/embed\"). MUST be a different origin than\n\t * the host page — that cross-origin boundary is what isolates the\n\t * secrets. A path component is allowed (so the embed page can live\n\t * at a sub-route of the main app); the postMessage security checks\n\t * compare against the URL's origin, not its full href.\n\t */\n\tframeOrigin?: string;\n\t/**\n\t * Monad API base the iframe should call. Defaults to\n\t * \"https://app.monad.com/api\" (production). Override for non-prod\n\t * environments. Set this together with `frameOrigin` — mixing\n\t * environments will not work.\n\t */\n\tapiBase?: string;\n\t/** Connector type id, e.g. \"runzero-findings\". */\n\ttypeId: string;\n\tkind: ComponentKind;\n\t/** Short-lived embed session token (host backend mints it). */\n\tsessionToken: string;\n\t/**\n\t * Sub-org id the session is scoped to. The host backend already knows\n\t * this — it's the same value passed to `/v1/embed/sessions` when minting\n\t * the token. Forwarded into the iframe so it can build `/v1/<org>/*` and\n\t * `/v2/<org>/*` URLs.\n\t */\n\torganizationId: string;\n\t/** Display name shown in the frame header. */\n\tdisplayName?: string;\n\t/**\n\t * Name for the created connector. On create, defaults to the connector\n\t * type's name plus a short unique suffix. On edit, omit to keep the\n\t * existing name.\n\t */\n\tname?: string;\n\t/**\n\t * Description for the created connector. On create, defaults to the\n\t * connector type's description. On edit, omit to keep the existing\n\t * description.\n\t */\n\tdescription?: string;\n\t/**\n\t * Show an editable name input inside the iframe (prefilled with the\n\t * resolved name) so the end user can name the connector themselves.\n\t * A non-empty name is required when shown. Defaults to false.\n\t */\n\tisNameEditable?: boolean;\n\t/**\n\t * Show an editable description input inside the iframe (prefilled with\n\t * the resolved description). Defaults to false.\n\t */\n\tisDescriptionEditable?: boolean;\n\t/** Existing connector id — pass for edit mode, omit to create. */\n\texistingId?: string;\n\t/**\n\t * Whether the synthetic-data toggle is exposed in the form. Defaults\n\t * to `false` — production embed flows keep it hidden.\n\t */\n\tsynthetic?: boolean;\n\t/** Theming tokens applied as CSS variables inside the iframe. */\n\tappearance?: Appearance;\n\t/**\n\t * Stylesheet URLs to apply inside the iframe. All-or-nothing: if\n\t * any URL is provided, the iframe loads ONLY these and skips its\n\t * built-in Monad UI defaults; if omitted/empty, the iframe loads\n\t * the defaults and nothing else. URLs must be reachable from the\n\t * iframe — typically served by the host page's own origin.\n\t */\n\tstylesheets?: string[];\n\t/**\n\t * Per-field copy overrides for the connector's config form: label,\n\t * description, placeholder. Copy only — nothing that changes a\n\t * field's type, requiredness, or submitted value.\n\t *\n\t * configMetaOverrides: {\n\t * settings: {\n\t * ddsource: { placeholder: 'northstar' },\n\t * service: { placeholder: 'northstar-web' },\n\t * },\n\t * }\n\t *\n\t * Keys are the connector's own wire keys, so check the connector-type\n\t * response rather than guessing — the Datadog output's source field is\n\t * `ddsource`, not `source`. Unmatched keys are ignored.\n\t *\n\t * `placeholder` is a hint, not a default: a field the user never\n\t * touches still submits `\"\"`. Nested fields are reached with\n\t * `children`, union variants with `discriminator.one_of.<variant>`.\n\t */\n\tconfigMetaOverrides?: ConfigMetaOverrides;\n\t/** Fired when the user saves successfully. */\n\tonSave?: (connector: { id: string; name?: string }) => void;\n\t/** Fired when the user cancels. */\n\tonCancel?: () => void;\n\t/** Fired on a save/test/load failure inside the iframe. */\n\tonError?: (message: string) => void;\n}\n\nexport interface ConnectorFrame {\n\t/** Remove the iframe and stop listening for its messages. */\n\tdestroy(): void;\n}\n\n/**\n * Production defaults. Customers integrating against prod don't pass\n * `frameOrigin`/`apiBase` at all; non-prod environments override both\n * together. Treat these as a public API contract — changing a hostname\n * is a breaking change for anyone on the default.\n */\nconst DEFAULT_FRAME_ORIGIN = 'https://app.monad.com/embed';\nconst DEFAULT_API_BASE = 'https://app.monad.com/api';\n\n/**\n * Inject the Monad connector iframe into `container` and wire the\n * postMessage channel. Returns a handle whose `destroy()` tears it all\n * down.\n *\n * Handshake:\n * 1. iframe loads, posts `ready`;\n * 2. host replies with `init` (session token, type, appearance);\n * 3. iframe mounts the form and streams `resize` as content grows;\n * 4. on save, iframe posts `saved {connector}`; on cancel, `cancel`.\n */\nexport function createConnectorFrame(opts: ConnectorFrameOptions): ConnectorFrame {\n\tconst container =\n\t\ttypeof opts.container === 'string'\n\t\t\t? document.querySelector<HTMLElement>(opts.container)\n\t\t\t: opts.container;\n\tif (!container) {\n\t\tthrow new Error('[monad-inc/connect] container element not found');\n\t}\n\n\tconst frameInput = opts.frameOrigin ?? DEFAULT_FRAME_ORIGIN;\n\tconst apiBase = opts.apiBase ?? DEFAULT_API_BASE;\n\n\t// Split the input URL into two pieces:\n\t// * `frameUrl` keeps the path (e.g. /embed) — used as iframe.src.\n\t// * `frameOrigin` is the bare origin — used for the postMessage\n\t// security checks below, which must compare against the value\n\t// the browser surfaces on MessageEvent.origin (always a bare\n\t// origin, never a path).\n\t// Previously these were collapsed into one `new URL(...).href`,\n\t// which silently broke any frameOrigin with a path — the equality\n\t// check `event.origin !== frameOrigin` would always fail and `init`\n\t// never made it back to the iframe.\n\tconst frameUrl = new URL(frameInput);\n\tconst frameOrigin = frameUrl.origin;\n\tconst framePath = frameUrl.pathname.replace(/\\/$/, '');\n\n\tconst iframe = document.createElement('iframe');\n\tiframe.className = 'monad-connect-frame';\n\tiframe.title = 'Monad connector configuration';\n\tiframe.style.width = '100%';\n\tiframe.style.border = '0';\n\tiframe.style.display = 'block';\n\t// The iframe needs its own scripts and same-origin access to the\n\t// Monad API; it must NOT get same-origin access to the host. A\n\t// cross-origin iframe already can't reach the host — no sandbox\n\t// attribute is needed for isolation, and adding one would block the\n\t// form's own scripts. Forms/popups stay enabled for OAuth-style\n\t// connectors that may open a window.\n\tiframe.allow = 'clipboard-write';\n\t// parentOrigin lets the iframe scope its postMessage target + sender\n\t// check. It is NOT a secret and NOT a capability — lying about it\n\t// only breaks the liar's own embed (see protocol.ts).\n\tiframe.src = `${frameOrigin}${framePath}/?parentOrigin=${encodeURIComponent(window.location.origin)}`;\n\n\tlet destroyed = false;\n\n\tfunction postToFrame(msg: HostMessage): void {\n\t\tiframe.contentWindow?.postMessage(msg, frameOrigin);\n\t}\n\n\tfunction onMessage(event: MessageEvent): void {\n\t\tif (destroyed) return;\n\t\t// --- security boundary: validate before trusting anything ---\n\t\tif (event.origin !== frameOrigin) return;\n\t\tif (event.source !== iframe.contentWindow) return;\n\t\tconst msg = event.data as FrameMessage | undefined;\n\t\tif (!msg || msg.source !== PROTOCOL_SOURCE) return;\n\n\t\tswitch (msg.type) {\n\t\t\tcase 'ready':\n\t\t\t\tpostToFrame({\n\t\t\t\t\tsource: PROTOCOL_SOURCE,\n\t\t\t\t\ttype: 'init',\n\t\t\t\t\tversion: PROTOCOL_VERSION,\n\t\t\t\t\tsessionToken: opts.sessionToken,\n\t\t\t\t\tapiBase,\n\t\t\t\t\torganizationId: opts.organizationId,\n\t\t\t\t\ttypeId: opts.typeId,\n\t\t\t\t\tkind: opts.kind,\n\t\t\t\t\tdisplayName: opts.displayName,\n\t\t\t\t\tname: opts.name,\n\t\t\t\t\tdescription: opts.description,\n\t\t\t\t\tisNameEditable: opts.isNameEditable,\n\t\t\t\t\tisDescriptionEditable: opts.isDescriptionEditable,\n\t\t\t\t\texistingId: opts.existingId,\n\t\t\t\t\tsynthetic: opts.synthetic,\n\t\t\t\t\tappearance: opts.appearance,\n\t\t\t\t\tstylesheets: opts.stylesheets,\n\t\t\t\t\tconfigMetaOverrides: opts.configMetaOverrides\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\tcase 'resize': {\n\t\t\t\t// Clamp to a sane range so a renderer bug in the iframe\n\t\t\t\t// can't blow up the host layout with a multi-million-px\n\t\t\t\t// height. The origin check above already gates who can\n\t\t\t\t// send this — clamping is defense in depth. Number.isFinite\n\t\t\t\t// guards against non-numeric heights that would otherwise\n\t\t\t\t// produce \"NaNpx\" and leave the iframe collapsed.\n\t\t\t\tconst h = Number.isFinite(msg.height) ? msg.height : 0;\n\t\t\t\tiframe.style.height = `${Math.min(Math.max(h, 0), 20000)}px`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'saved':\n\t\t\t\topts.onSave?.(msg.connector);\n\t\t\t\tbreak;\n\t\t\tcase 'error':\n\t\t\t\topts.onError?.(msg.message);\n\t\t\t\tbreak;\n\t\t\tcase 'cancel':\n\t\t\t\topts.onCancel?.();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\twindow.addEventListener('message', onMessage);\n\tcontainer.appendChild(iframe);\n\n\treturn {\n\t\tdestroy(): void {\n\t\t\tdestroyed = true;\n\t\t\twindow.removeEventListener('message', onMessage);\n\t\t\tiframe.remove();\n\t\t}\n\t};\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monad-inc/embed",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "SDK for embedding Monad's UI in other apps via a secure cross-origin iframe. The connect module (createConnectorFrame()) renders the connector-config form inside a Monad-hosted iframe, so the host's JavaScript never sees credential material — only a connector id reference comes back. Zero runtime dependencies.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Monad Inc.",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsup",
|
|
62
62
|
"build:watch": "tsup --watch",
|
|
63
|
-
"typecheck": "tsc --noEmit",
|
|
63
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
64
64
|
"test": "vitest run",
|
|
65
65
|
"test:watch": "vitest"
|
|
66
66
|
}
|