@proteos/component-sdk 0.60.0 → 0.75.0
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/dist/{chunk-HODSL6ER.js → chunk-IYXDPNMJ.js} +2 -2
- package/dist/chunk-IYXDPNMJ.js.map +1 -0
- package/dist/{chunk-3VUWWDED.cjs → chunk-Q7PTW7DD.cjs} +2 -2
- package/dist/chunk-Q7PTW7DD.cjs.map +1 -0
- package/dist/index.cjs +78 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +92 -7
- package/dist/index.d.ts +92 -7
- package/dist/index.js +69 -15
- package/dist/index.js.map +1 -1
- package/dist/protocol.cjs +5 -5
- package/dist/protocol.d.cts +181 -3
- package/dist/protocol.d.ts +181 -3
- package/dist/protocol.js +1 -1
- package/package.json +2 -2
- package/src/context.tsx +116 -16
- package/src/hooks.test.tsx +116 -3
- package/src/hooks.ts +69 -4
- package/src/index.ts +13 -0
- package/src/protocol.ts +178 -0
- package/dist/chunk-3VUWWDED.cjs.map +0 -1
- package/dist/chunk-HODSL6ER.js.map +0 -1
package/src/protocol.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
// Every message carries `source: MESSAGE_SOURCE` so we can ignore the unrelated
|
|
9
9
|
// postMessage traffic that shares the window (OIDC silent-renew, Vite HMR, …).
|
|
10
10
|
|
|
11
|
+
import type { PageAction } from '@proteos/sdk'
|
|
12
|
+
|
|
11
13
|
export const MESSAGE_SOURCE = 'proteos-component-runtime' as const
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -41,10 +43,42 @@ export interface OverlayRect {
|
|
|
41
43
|
height: number
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
/** What the viewer is doing with the record on the host page.
|
|
47
|
+
*
|
|
48
|
+
* - `view` — a read-only record page, or a page without a record
|
|
49
|
+
* (platform, kiosk, public, previews).
|
|
50
|
+
* - `create` — the form for a record that is not saved yet (`record` holds
|
|
51
|
+
* the seed values only, no `id`).
|
|
52
|
+
* - `edit` — the form over an existing record (`record` is the SAVED row;
|
|
53
|
+
* in-flight edits are not reflected).
|
|
54
|
+
*
|
|
55
|
+
* Inside an action's / workflow's invoke dialog (a trigger page) `mode` is
|
|
56
|
+
* `create` while the person fills the params and `view` once the run is on
|
|
57
|
+
* its way; `phase` says where the run is.
|
|
58
|
+
*
|
|
59
|
+
* A snapshot like `record`: the host remounts the frame when the page
|
|
60
|
+
* switches between view and edit, so a new INIT carries the new mode. Also
|
|
61
|
+
* the Liquid scope key `mode` when props are resolved. */
|
|
62
|
+
export type PageMode = 'view' | 'create' | 'edit'
|
|
63
|
+
|
|
64
|
+
/** Where an invocation is in the invoke dialog's lifecycle — set only on a
|
|
65
|
+
* trigger page (the dialog of an action / workflow); absent everywhere else.
|
|
66
|
+
*
|
|
67
|
+
* - `input` — the form over the params (`record` = the seeded params).
|
|
68
|
+
* - `running` — the run is in flight (`record` = the submitted params;
|
|
69
|
+
* `params.execution` for a workflow).
|
|
70
|
+
* - `result` — the run ended (`record` = the params plus the action's
|
|
71
|
+
* returns; `params.result` / `params.execution`).
|
|
72
|
+
*
|
|
73
|
+
* Also the Liquid scope key `phase`. */
|
|
74
|
+
export type TriggerPhase = 'input' | 'running' | 'result'
|
|
75
|
+
|
|
44
76
|
export interface PageContext {
|
|
45
77
|
record?: Record<string, unknown>
|
|
46
78
|
entity?: { slug: string; [key: string]: unknown }
|
|
47
79
|
params?: Record<string, unknown>
|
|
80
|
+
mode: PageMode
|
|
81
|
+
phase?: TriggerPhase
|
|
48
82
|
/** The signed-in viewer (absent on public pages and offline previews). The
|
|
49
83
|
* host resolves `{{ current_user.* }}` and the bare `current_user` prop
|
|
50
84
|
* sentinel against it before posting props. */
|
|
@@ -63,6 +97,21 @@ export interface InitMessage {
|
|
|
63
97
|
authToken: string
|
|
64
98
|
/** API origin the bundle endpoint + SDK calls resolve against. */
|
|
65
99
|
apiBaseUrl: string
|
|
100
|
+
/** Content hash (hex SHA-256) of the component's deployed bundle, from the
|
|
101
|
+
* component's `bundle_checksum`. The runtime appends it as `?v=` to the
|
|
102
|
+
* bundle request so the server can mark the response immutable — the
|
|
103
|
+
* browser then serves every later mount from its cache without a round
|
|
104
|
+
* trip. Absent when the host does not know it yet (metadata still
|
|
105
|
+
* loading, or a row deployed before checksum stamping); the bundle then
|
|
106
|
+
* revalidates as before. */
|
|
107
|
+
bundleVersion?: string
|
|
108
|
+
/** Key under which the runtime shares its query cache with sibling and
|
|
109
|
+
* successor frames of the same component through the host (same
|
|
110
|
+
* origin): the host warms a new frame from the last state a frame with
|
|
111
|
+
* this key left behind. Covers the slug AND the resolved props, so two
|
|
112
|
+
* placements configured differently never share. Absent on hosts that
|
|
113
|
+
* do not keep a store (previews, public pages). */
|
|
114
|
+
cacheKey?: string
|
|
66
115
|
/** Resolved props for the component (Liquid templating happens host-side). */
|
|
67
116
|
props: Record<string, unknown>
|
|
68
117
|
pageCtx: PageContext
|
|
@@ -109,12 +158,51 @@ export interface ModalClosedMessage {
|
|
|
109
158
|
result?: unknown
|
|
110
159
|
}
|
|
111
160
|
|
|
161
|
+
/** How a RUN_PAGE_ACTION ended, by what the button did:
|
|
162
|
+
*
|
|
163
|
+
* - `done` — the run went through: an action's `returns` (null when it
|
|
164
|
+
* returned none), a workflow's accepted `executionId`, the
|
|
165
|
+
* record an intent created / updated.
|
|
166
|
+
* - `failed` — the invocation failed and the person closed the error.
|
|
167
|
+
* - `cancelled` — the person stepped out before anything ran or saved.
|
|
168
|
+
* - `handed_over` — an `agent` / `link` button: the chat or the tab is open;
|
|
169
|
+
* what happens there is not reported back.
|
|
170
|
+
* - `unavailable` — nothing was opened: `invalid` (the action does not parse),
|
|
171
|
+
* `hidden` (its `visible_when` does not match, an intent
|
|
172
|
+
* has no record to act on, a link no address to open),
|
|
173
|
+
* `busy` (another page action is still open), `unsupported`
|
|
174
|
+
* (a public page, a host without page actions — a preview —
|
|
175
|
+
* or an agent button where there is no agent window). */
|
|
176
|
+
export type PageActionOutcome =
|
|
177
|
+
| { status: 'done'; kind: 'action'; returns: Record<string, unknown> | null }
|
|
178
|
+
| { status: 'done'; kind: 'workflow'; executionId: string }
|
|
179
|
+
| {
|
|
180
|
+
status: 'done'
|
|
181
|
+
kind: 'intent'
|
|
182
|
+
mode: 'create' | 'update'
|
|
183
|
+
entitySlug: string
|
|
184
|
+
recordId: string
|
|
185
|
+
title: string
|
|
186
|
+
}
|
|
187
|
+
| { status: 'failed'; error: { code: string; message: string } }
|
|
188
|
+
| { status: 'cancelled' }
|
|
189
|
+
| { status: 'handed_over'; kind: 'agent' | 'link' }
|
|
190
|
+
| { status: 'unavailable'; reason: 'invalid' | 'hidden' | 'busy' | 'unsupported' }
|
|
191
|
+
|
|
192
|
+
/** Resolution of a RUN_PAGE_ACTION request, once the run's flow is over. */
|
|
193
|
+
export interface PageActionSettledMessage {
|
|
194
|
+
type: 'PAGE_ACTION_SETTLED'
|
|
195
|
+
requestId: string
|
|
196
|
+
outcome: PageActionOutcome
|
|
197
|
+
}
|
|
198
|
+
|
|
112
199
|
export type HostMessage =
|
|
113
200
|
| InitMessage
|
|
114
201
|
| AuthRefreshMessage
|
|
115
202
|
| DataInvalidateMessage
|
|
116
203
|
| OverlayRectMessage
|
|
117
204
|
| ModalClosedMessage
|
|
205
|
+
| PageActionSettledMessage
|
|
118
206
|
|
|
119
207
|
// ── Runtime → host ──────────────────────────────────────────────────────────
|
|
120
208
|
|
|
@@ -221,6 +309,92 @@ export interface ModalResultMessage {
|
|
|
221
309
|
result?: unknown
|
|
222
310
|
}
|
|
223
311
|
|
|
312
|
+
/** Ask the host to open its agent session window (the floating assistant).
|
|
313
|
+
*
|
|
314
|
+
* - `sessionId` set: the window resumes THAT session (its agent is read from
|
|
315
|
+
* the session). A `prompt` is sent into it.
|
|
316
|
+
* - no `sessionId`: a fresh session with `agentKey` (or the agent currently
|
|
317
|
+
* selected in the window); a `prompt` is sent as its first message. With
|
|
318
|
+
* neither field the window just opens as it is.
|
|
319
|
+
*
|
|
320
|
+
* Ignored on public pages (there is no signed-in user to run an agent as). */
|
|
321
|
+
export interface OpenAgentWindowMessage {
|
|
322
|
+
type: 'OPEN_AGENT_WINDOW'
|
|
323
|
+
sessionId?: string
|
|
324
|
+
agentKey?: string
|
|
325
|
+
prompt?: string
|
|
326
|
+
/** Model-only context sent with `prompt`; never rendered. */
|
|
327
|
+
context?: string
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Ask the host to minimize its agent session window. The active session and
|
|
331
|
+
* its stream stay alive — reopening resumes it. */
|
|
332
|
+
export interface CloseAgentWindowMessage {
|
|
333
|
+
type: 'CLOSE_AGENT_WINDOW'
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** A record write the component made through its sdk (`data.records.*`)
|
|
337
|
+
* succeeded. Posted by the runtime shell — not by component code — so the
|
|
338
|
+
* host can refresh what it shows of that entity and, when the component
|
|
339
|
+
* sits in a surface that reports edits (the Ava workspace), tell the agent.
|
|
340
|
+
* `patch` is what was submitted, `record` what the server returned. */
|
|
341
|
+
export interface RecordWrittenMessage {
|
|
342
|
+
type: 'RECORD_WRITTEN'
|
|
343
|
+
entitySlug: string
|
|
344
|
+
operation: 'create' | 'update' | 'delete' | 'batch_upsert'
|
|
345
|
+
recordId?: string
|
|
346
|
+
patch?: Record<string, unknown>
|
|
347
|
+
record?: Record<string, unknown>
|
|
348
|
+
/** Rows in a batch upsert. */
|
|
349
|
+
count?: number
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** A record a page action acts on, as the component names it. */
|
|
353
|
+
export interface PageActionRecord {
|
|
354
|
+
entitySlug: string
|
|
355
|
+
recordId: string
|
|
356
|
+
/** Shown when the person picks among several records for an intent. */
|
|
357
|
+
title?: string
|
|
358
|
+
/** The entity's display name, shown beside `title` in that pick. */
|
|
359
|
+
entityName?: string
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** What the component knows that the button needs — layered over what the
|
|
363
|
+
* host already knows about the page. */
|
|
364
|
+
export interface PageActionContext {
|
|
365
|
+
/** Merged over the page scope (`record`, `entity`, `params`, `mode`,
|
|
366
|
+
* `current_user`) the action's Liquid templates render against — `params`
|
|
367
|
+
* / `inputs` prefill, `prompt`, `context`, `entity`, `record`, `url`.
|
|
368
|
+
* Shallow: a key given here replaces the page's. `current_user` is always
|
|
369
|
+
* the host's. */
|
|
370
|
+
scope?: Record<string, unknown>
|
|
371
|
+
/** The records the button acts on. Default: the page record (none on a page
|
|
372
|
+
* without one).
|
|
373
|
+
*
|
|
374
|
+
* - `action` with entity scope — the one record it is invoked on.
|
|
375
|
+
* - `action` with entity_batch scope — every record, in ONE invocation.
|
|
376
|
+
* - `intent` `update_record` without a `record` — the records of the
|
|
377
|
+
* intent's entity it may update: one is updated, several are offered
|
|
378
|
+
* to pick from. */
|
|
379
|
+
records?: PageActionRecord[]
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** Ask the host to run a page action — the same button a page or list toolbar
|
|
383
|
+
* carries, defined inline by the component. The host dispatches it exactly
|
|
384
|
+
* as a toolbar press (invoke dialog, record proposal review, agent window,
|
|
385
|
+
* new tab) and answers PAGE_ACTION_SETTLED with the same `requestId` once
|
|
386
|
+
* the flow is over.
|
|
387
|
+
*
|
|
388
|
+
* One page action at a time: while one is open (from any component) the
|
|
389
|
+
* host answers further requests `unavailable: busy`. */
|
|
390
|
+
export interface RunPageActionMessage {
|
|
391
|
+
type: 'RUN_PAGE_ACTION'
|
|
392
|
+
/** Correlates the eventual PAGE_ACTION_SETTLED. Generated by the runtime. */
|
|
393
|
+
requestId: string
|
|
394
|
+
action: PageAction
|
|
395
|
+
context?: PageActionContext
|
|
396
|
+
}
|
|
397
|
+
|
|
224
398
|
export interface ErrorMessage {
|
|
225
399
|
type: 'ERROR'
|
|
226
400
|
message: string
|
|
@@ -237,6 +411,10 @@ export type RuntimeMessage =
|
|
|
237
411
|
| OpenModalMessage
|
|
238
412
|
| ModalResultMessage
|
|
239
413
|
| EscapePressedMessage
|
|
414
|
+
| OpenAgentWindowMessage
|
|
415
|
+
| CloseAgentWindowMessage
|
|
416
|
+
| RecordWrittenMessage
|
|
417
|
+
| RunPageActionMessage
|
|
240
418
|
| ErrorMessage
|
|
241
419
|
|
|
242
420
|
// Wire envelope: the message plus the source discriminator.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocol.ts"],"names":[],"mappings":";;;AAUO,IAAM,cAAA,GAAiB;AAuBvB,IAAM,yBAAA,GAA4B;AAmNlC,SAAS,cAAc,IAAA,EAA8C;AAC1E,EAAA,OACE,OAAO,IAAA,KAAS,QAAA,IAChB,IAAA,KAAS,IAAA,IACR,KAA8B,MAAA,KAAW,cAAA,IAC1C,OAAQ,IAAA,CAA4B,IAAA,KAAS,QAAA;AAEjD;AAEO,SAAS,iBAAiB,IAAA,EAAiD;AAChF,EAAA,OACE,OAAO,IAAA,KAAS,QAAA,IAChB,IAAA,KAAS,IAAA,IACR,KAA8B,MAAA,KAAW,cAAA,IAC1C,OAAQ,IAAA,CAA4B,IAAA,KAAS,QAAA;AAEjD","file":"chunk-3VUWWDED.cjs","sourcesContent":["// postMessage protocol between the host page and the component-runtime iframe.\n// This is the single source of truth for the wire contract: the host renderer\n// (clients/web .../component-frame.tsx), the runtime shell\n// (clients/web/component-runtime/shell.tsx), and the author-facing hooks\n// (this package's RuntimeProvider / useNavigate) all import these same types so\n// every end stays in lockstep.\n//\n// Every message carries `source: MESSAGE_SOURCE` so we can ignore the unrelated\n// postMessage traffic that shares the window (OIDC silent-renew, Vite HMR, …).\n\nexport const MESSAGE_SOURCE = 'proteos-component-runtime' as const\n\n/**\n * z-index the component iframe is promoted to while one of its own overlays\n * (modal, popover, menu, select, tooltip) is open. The iframe goes\n * `position: fixed; inset: 0` at this z so the overlay — which portals to the\n * IFRAME's body and would otherwise clip at the frame's box — can paint over\n * the rest of the page.\n *\n * Host z ladder this slots into:\n * 50 @proteos/ui Modal / Tooltip / ImageLightbox (default tier)\n * 1000+ full-page panel stack (scrim 1000, panels 1001+n)\n * 1090 peek scrim 1100 peek panel, ask-proteos window\n * 1150 ← THIS\n * 1200 menu popover, command palette, action dialog, component modal\n * 1300 select popover\n *\n * Above the peek/full-page surfaces a component can live on, below the global\n * host chrome. Note the cap: inside a peek (a `fixed` + z-index ancestor) the\n * promoted iframe paints within that 1100 stacking context — it covers the\n * peek's own content and everything under it, but host chrome at 1200+ still\n * wins. That is the intended layering, not a bug.\n */\nexport const COMPONENT_OVERLAY_Z_INDEX = 1150\n\n/** Viewport-relative box of the iframe's in-flow placeholder, in CSS px. */\nexport interface OverlayRect {\n top: number\n left: number\n width: number\n height: number\n}\n\nexport interface PageContext {\n record?: Record<string, unknown>\n entity?: { slug: string; [key: string]: unknown }\n params?: Record<string, unknown>\n /** The signed-in viewer (absent on public pages and offline previews). The\n * host resolves `{{ current_user.* }}` and the bare `current_user` prop\n * sentinel against it before posting props. */\n current_user?: { id: string; email: string; given_name: string; family_name: string }\n [key: string]: unknown\n}\n\n// ── Host → runtime ──────────────────────────────────────────────────────────\n\nexport interface InitMessage {\n type: 'INIT'\n /** Raw bearer token (the OIDC `id_token`). The runtime's ProteosClient\n * prepends `Bearer ` when building the Authorization header, so the host\n * sends the token verbatim — NOT a full `Bearer …` header value.\n * Empty string in public mode (`isPublic: true`). */\n authToken: string\n /** API origin the bundle endpoint + SDK calls resolve against. */\n apiBaseUrl: string\n /** Resolved props for the component (Liquid templating happens host-side). */\n props: Record<string, unknown>\n pageCtx: PageContext\n /** PUBLIC mode: the component renders on an unauthenticated public page.\n * The shell fetches the bundle from the public endpoint (no bearer) and\n * injects a restricted sdk — only `functions.actions.invokePublic` reaches\n * the platform. Requires `orgId`. */\n isPublic?: boolean\n /** Org id for public mode — public endpoints carry the org in the path\n * because there is no token to scope from. */\n orgId?: string\n /** Set when this frame is the CONTENT of a host component modal (opened by\n * another component via OPEN_MODAL). The component reads it through\n * `useModalHost()` and closes with a result by posting MODAL_RESULT with\n * this id. Absent on ordinary page-hosted frames. */\n modalRequestId?: string\n}\n\nexport interface AuthRefreshMessage {\n type: 'AUTH_REFRESH'\n token: string\n}\n\nexport interface DataInvalidateMessage {\n type: 'DATA_INVALIDATE'\n queryKey?: unknown[]\n}\n\n/** Where the iframe's in-flow box sits in the host viewport. Sent while the\n * frame is promoted (OVERLAY_STATE open) so the runtime can keep its content\n * parked at the original rect while the iframe itself covers the viewport —\n * and re-sent when the host window resizes under an open overlay. */\nexport interface OverlayRectMessage {\n type: 'OVERLAY_RECT'\n rect: OverlayRect\n}\n\n/** Resolution of an OPEN_MODAL request: the host modal closed. `result` is the\n * value the modal's component passed to MODAL_RESULT, and is absent when the\n * modal was dismissed (backdrop / Esc / ×). */\nexport interface ModalClosedMessage {\n type: 'MODAL_CLOSED'\n requestId: string\n result?: unknown\n}\n\nexport type HostMessage =\n | InitMessage\n | AuthRefreshMessage\n | DataInvalidateMessage\n | OverlayRectMessage\n | ModalClosedMessage\n\n// ── Runtime → host ──────────────────────────────────────────────────────────\n\nexport interface ReadyMessage {\n type: 'READY'\n}\n\nexport interface ResizeMessage {\n type: 'RESIZE'\n height: number\n}\n\nexport interface NavigateMessage {\n type: 'NAVIGATE'\n to: string\n}\n\n/** Open an in-app path in a peek (slide-over) panel instead of full-page\n * navigation. Same `/…` in-app path grammar as NAVIGATE; the host decides\n * what can peek (today: platform pages) and falls back to NAVIGATE otherwise. */\nexport interface OpenPeekMessage {\n type: 'OPEN_PEEK'\n to: string\n}\n\n/** Ask the host to open a prepared call: the host shows the conversation brief's\n * preview (its softphone owns the dial) and lets the user place the call.\n * Ignored on public pages and on hosts without a softphone. */\nexport interface OpenCallMessage {\n type: 'OPEN_CALL'\n /** The conversation brief to preview and dial from. */\n conversationBriefId: string\n}\n\n/** The runtime has (or no longer has) an overlay open and needs the host to\n * promote (or restore) the iframe. See {@link COMPONENT_OVERLAY_Z_INDEX}.\n *\n * Same-origin today, so the runtime applies the promotion styles to its own\n * `window.frameElement` synchronously (pre-paint, no flicker) and this message\n * is the host's cue to MIRROR that state into React — gate RESIZE, hold the\n * placeholder height, start posting OVERLAY_RECT. When components move to a\n * dedicated origin the runtime can no longer touch the frame and this message\n * becomes the actuation instead; the runtime hides its overlay until the\n * answering OVERLAY_RECT arrives. */\nexport interface OverlayStateMessage {\n type: 'OVERLAY_STATE'\n open: boolean\n}\n\n/** Width of a host component modal's panel: `sm` 384px, `md` 512px, `lg` 672px,\n * `xl` 896px. Below 640px the modal is a full-screen sheet whatever the\n * width. */\nexport type ModalWidth = 'sm' | 'md' | 'lg' | 'xl'\n\n/** Ask the host to open ANOTHER component in a central modal, above everything\n * this frame could reach on its own. The host answers with MODAL_CLOSED\n * carrying the same `requestId`.\n *\n * One modal per opener at a time: while a request from this frame is still\n * open, the host answers any further OPEN_MODAL from it as dismissed (a\n * double click must not stack two dialogs). The modal's own component may\n * open another — that request comes from a different frame. */\nexport interface OpenModalMessage {\n type: 'OPEN_MODAL'\n /** Correlates the eventual MODAL_CLOSED. Generated by the opener. */\n requestId: string\n /** Slug of the component to mount inside the modal. */\n componentSlug: string\n /** Props for that component. The host renders Liquid in every string value\n * against the OPENER's page scope (`record`, `entity`, `params`,\n * `current_user`) before handing them over — exactly as it does for a\n * page-placed component. So `{{ … }}` / `{% … %}` in a value is evaluated,\n * not passed through. */\n props?: Record<string, unknown>\n /** Optional modal heading. */\n title?: string\n /** Panel width. Defaults to `lg`. */\n width?: ModalWidth\n /** Height of the component's frame, in px. Set: the frame is that tall from\n * the first paint (capped at the viewport), scrolls its own content, and\n * sits edge to edge in the panel — the component owns its padding. Unset:\n * the frame grows with its content inside the host's padded body. */\n height?: number\n}\n\n/** Escape was pressed with focus inside the iframe, and nothing in the runtime\n * consumed it.\n *\n * Keyboard events don't cross the iframe boundary, and react-aria binds its\n * Escape handling to the overlay element in its own React tree — so a\n * host-rendered dialog CANNOT see Escape once the user has clicked into a\n * component. The runtime forwards it so the host can dismiss on the user's\n * behalf. Not sent while the runtime has its own overlay open: that overlay\n * owns Escape. */\nexport interface EscapePressedMessage {\n type: 'ESCAPE_PRESSED'\n}\n\n/** Posted by the component INSIDE a host modal to close it with a value. The\n * `requestId` is the one INIT carried as `modalRequestId`. */\nexport interface ModalResultMessage {\n type: 'MODAL_RESULT'\n requestId: string\n result?: unknown\n}\n\nexport interface ErrorMessage {\n type: 'ERROR'\n message: string\n stack?: string\n}\n\nexport type RuntimeMessage =\n | ReadyMessage\n | ResizeMessage\n | NavigateMessage\n | OpenPeekMessage\n | OpenCallMessage\n | OverlayStateMessage\n | OpenModalMessage\n | ModalResultMessage\n | EscapePressedMessage\n | ErrorMessage\n\n// Wire envelope: the message plus the source discriminator.\nexport type Envelope<T> = T & { source: typeof MESSAGE_SOURCE }\n\nexport function isHostMessage(data: unknown): data is Envelope<HostMessage> {\n return (\n typeof data === 'object' &&\n data !== null &&\n (data as { source?: unknown }).source === MESSAGE_SOURCE &&\n typeof (data as { type?: unknown }).type === 'string'\n )\n}\n\nexport function isRuntimeMessage(data: unknown): data is Envelope<RuntimeMessage> {\n return (\n typeof data === 'object' &&\n data !== null &&\n (data as { source?: unknown }).source === MESSAGE_SOURCE &&\n typeof (data as { type?: unknown }).type === 'string'\n )\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocol.ts"],"names":[],"mappings":";AAUO,IAAM,cAAA,GAAiB;AAuBvB,IAAM,yBAAA,GAA4B;AAmNlC,SAAS,cAAc,IAAA,EAA8C;AAC1E,EAAA,OACE,OAAO,IAAA,KAAS,QAAA,IAChB,IAAA,KAAS,IAAA,IACR,KAA8B,MAAA,KAAW,cAAA,IAC1C,OAAQ,IAAA,CAA4B,IAAA,KAAS,QAAA;AAEjD;AAEO,SAAS,iBAAiB,IAAA,EAAiD;AAChF,EAAA,OACE,OAAO,IAAA,KAAS,QAAA,IAChB,IAAA,KAAS,IAAA,IACR,KAA8B,MAAA,KAAW,cAAA,IAC1C,OAAQ,IAAA,CAA4B,IAAA,KAAS,QAAA;AAEjD","file":"chunk-HODSL6ER.js","sourcesContent":["// postMessage protocol between the host page and the component-runtime iframe.\n// This is the single source of truth for the wire contract: the host renderer\n// (clients/web .../component-frame.tsx), the runtime shell\n// (clients/web/component-runtime/shell.tsx), and the author-facing hooks\n// (this package's RuntimeProvider / useNavigate) all import these same types so\n// every end stays in lockstep.\n//\n// Every message carries `source: MESSAGE_SOURCE` so we can ignore the unrelated\n// postMessage traffic that shares the window (OIDC silent-renew, Vite HMR, …).\n\nexport const MESSAGE_SOURCE = 'proteos-component-runtime' as const\n\n/**\n * z-index the component iframe is promoted to while one of its own overlays\n * (modal, popover, menu, select, tooltip) is open. The iframe goes\n * `position: fixed; inset: 0` at this z so the overlay — which portals to the\n * IFRAME's body and would otherwise clip at the frame's box — can paint over\n * the rest of the page.\n *\n * Host z ladder this slots into:\n * 50 @proteos/ui Modal / Tooltip / ImageLightbox (default tier)\n * 1000+ full-page panel stack (scrim 1000, panels 1001+n)\n * 1090 peek scrim 1100 peek panel, ask-proteos window\n * 1150 ← THIS\n * 1200 menu popover, command palette, action dialog, component modal\n * 1300 select popover\n *\n * Above the peek/full-page surfaces a component can live on, below the global\n * host chrome. Note the cap: inside a peek (a `fixed` + z-index ancestor) the\n * promoted iframe paints within that 1100 stacking context — it covers the\n * peek's own content and everything under it, but host chrome at 1200+ still\n * wins. That is the intended layering, not a bug.\n */\nexport const COMPONENT_OVERLAY_Z_INDEX = 1150\n\n/** Viewport-relative box of the iframe's in-flow placeholder, in CSS px. */\nexport interface OverlayRect {\n top: number\n left: number\n width: number\n height: number\n}\n\nexport interface PageContext {\n record?: Record<string, unknown>\n entity?: { slug: string; [key: string]: unknown }\n params?: Record<string, unknown>\n /** The signed-in viewer (absent on public pages and offline previews). The\n * host resolves `{{ current_user.* }}` and the bare `current_user` prop\n * sentinel against it before posting props. */\n current_user?: { id: string; email: string; given_name: string; family_name: string }\n [key: string]: unknown\n}\n\n// ── Host → runtime ──────────────────────────────────────────────────────────\n\nexport interface InitMessage {\n type: 'INIT'\n /** Raw bearer token (the OIDC `id_token`). The runtime's ProteosClient\n * prepends `Bearer ` when building the Authorization header, so the host\n * sends the token verbatim — NOT a full `Bearer …` header value.\n * Empty string in public mode (`isPublic: true`). */\n authToken: string\n /** API origin the bundle endpoint + SDK calls resolve against. */\n apiBaseUrl: string\n /** Resolved props for the component (Liquid templating happens host-side). */\n props: Record<string, unknown>\n pageCtx: PageContext\n /** PUBLIC mode: the component renders on an unauthenticated public page.\n * The shell fetches the bundle from the public endpoint (no bearer) and\n * injects a restricted sdk — only `functions.actions.invokePublic` reaches\n * the platform. Requires `orgId`. */\n isPublic?: boolean\n /** Org id for public mode — public endpoints carry the org in the path\n * because there is no token to scope from. */\n orgId?: string\n /** Set when this frame is the CONTENT of a host component modal (opened by\n * another component via OPEN_MODAL). The component reads it through\n * `useModalHost()` and closes with a result by posting MODAL_RESULT with\n * this id. Absent on ordinary page-hosted frames. */\n modalRequestId?: string\n}\n\nexport interface AuthRefreshMessage {\n type: 'AUTH_REFRESH'\n token: string\n}\n\nexport interface DataInvalidateMessage {\n type: 'DATA_INVALIDATE'\n queryKey?: unknown[]\n}\n\n/** Where the iframe's in-flow box sits in the host viewport. Sent while the\n * frame is promoted (OVERLAY_STATE open) so the runtime can keep its content\n * parked at the original rect while the iframe itself covers the viewport —\n * and re-sent when the host window resizes under an open overlay. */\nexport interface OverlayRectMessage {\n type: 'OVERLAY_RECT'\n rect: OverlayRect\n}\n\n/** Resolution of an OPEN_MODAL request: the host modal closed. `result` is the\n * value the modal's component passed to MODAL_RESULT, and is absent when the\n * modal was dismissed (backdrop / Esc / ×). */\nexport interface ModalClosedMessage {\n type: 'MODAL_CLOSED'\n requestId: string\n result?: unknown\n}\n\nexport type HostMessage =\n | InitMessage\n | AuthRefreshMessage\n | DataInvalidateMessage\n | OverlayRectMessage\n | ModalClosedMessage\n\n// ── Runtime → host ──────────────────────────────────────────────────────────\n\nexport interface ReadyMessage {\n type: 'READY'\n}\n\nexport interface ResizeMessage {\n type: 'RESIZE'\n height: number\n}\n\nexport interface NavigateMessage {\n type: 'NAVIGATE'\n to: string\n}\n\n/** Open an in-app path in a peek (slide-over) panel instead of full-page\n * navigation. Same `/…` in-app path grammar as NAVIGATE; the host decides\n * what can peek (today: platform pages) and falls back to NAVIGATE otherwise. */\nexport interface OpenPeekMessage {\n type: 'OPEN_PEEK'\n to: string\n}\n\n/** Ask the host to open a prepared call: the host shows the conversation brief's\n * preview (its softphone owns the dial) and lets the user place the call.\n * Ignored on public pages and on hosts without a softphone. */\nexport interface OpenCallMessage {\n type: 'OPEN_CALL'\n /** The conversation brief to preview and dial from. */\n conversationBriefId: string\n}\n\n/** The runtime has (or no longer has) an overlay open and needs the host to\n * promote (or restore) the iframe. See {@link COMPONENT_OVERLAY_Z_INDEX}.\n *\n * Same-origin today, so the runtime applies the promotion styles to its own\n * `window.frameElement` synchronously (pre-paint, no flicker) and this message\n * is the host's cue to MIRROR that state into React — gate RESIZE, hold the\n * placeholder height, start posting OVERLAY_RECT. When components move to a\n * dedicated origin the runtime can no longer touch the frame and this message\n * becomes the actuation instead; the runtime hides its overlay until the\n * answering OVERLAY_RECT arrives. */\nexport interface OverlayStateMessage {\n type: 'OVERLAY_STATE'\n open: boolean\n}\n\n/** Width of a host component modal's panel: `sm` 384px, `md` 512px, `lg` 672px,\n * `xl` 896px. Below 640px the modal is a full-screen sheet whatever the\n * width. */\nexport type ModalWidth = 'sm' | 'md' | 'lg' | 'xl'\n\n/** Ask the host to open ANOTHER component in a central modal, above everything\n * this frame could reach on its own. The host answers with MODAL_CLOSED\n * carrying the same `requestId`.\n *\n * One modal per opener at a time: while a request from this frame is still\n * open, the host answers any further OPEN_MODAL from it as dismissed (a\n * double click must not stack two dialogs). The modal's own component may\n * open another — that request comes from a different frame. */\nexport interface OpenModalMessage {\n type: 'OPEN_MODAL'\n /** Correlates the eventual MODAL_CLOSED. Generated by the opener. */\n requestId: string\n /** Slug of the component to mount inside the modal. */\n componentSlug: string\n /** Props for that component. The host renders Liquid in every string value\n * against the OPENER's page scope (`record`, `entity`, `params`,\n * `current_user`) before handing them over — exactly as it does for a\n * page-placed component. So `{{ … }}` / `{% … %}` in a value is evaluated,\n * not passed through. */\n props?: Record<string, unknown>\n /** Optional modal heading. */\n title?: string\n /** Panel width. Defaults to `lg`. */\n width?: ModalWidth\n /** Height of the component's frame, in px. Set: the frame is that tall from\n * the first paint (capped at the viewport), scrolls its own content, and\n * sits edge to edge in the panel — the component owns its padding. Unset:\n * the frame grows with its content inside the host's padded body. */\n height?: number\n}\n\n/** Escape was pressed with focus inside the iframe, and nothing in the runtime\n * consumed it.\n *\n * Keyboard events don't cross the iframe boundary, and react-aria binds its\n * Escape handling to the overlay element in its own React tree — so a\n * host-rendered dialog CANNOT see Escape once the user has clicked into a\n * component. The runtime forwards it so the host can dismiss on the user's\n * behalf. Not sent while the runtime has its own overlay open: that overlay\n * owns Escape. */\nexport interface EscapePressedMessage {\n type: 'ESCAPE_PRESSED'\n}\n\n/** Posted by the component INSIDE a host modal to close it with a value. The\n * `requestId` is the one INIT carried as `modalRequestId`. */\nexport interface ModalResultMessage {\n type: 'MODAL_RESULT'\n requestId: string\n result?: unknown\n}\n\nexport interface ErrorMessage {\n type: 'ERROR'\n message: string\n stack?: string\n}\n\nexport type RuntimeMessage =\n | ReadyMessage\n | ResizeMessage\n | NavigateMessage\n | OpenPeekMessage\n | OpenCallMessage\n | OverlayStateMessage\n | OpenModalMessage\n | ModalResultMessage\n | EscapePressedMessage\n | ErrorMessage\n\n// Wire envelope: the message plus the source discriminator.\nexport type Envelope<T> = T & { source: typeof MESSAGE_SOURCE }\n\nexport function isHostMessage(data: unknown): data is Envelope<HostMessage> {\n return (\n typeof data === 'object' &&\n data !== null &&\n (data as { source?: unknown }).source === MESSAGE_SOURCE &&\n typeof (data as { type?: unknown }).type === 'string'\n )\n}\n\nexport function isRuntimeMessage(data: unknown): data is Envelope<RuntimeMessage> {\n return (\n typeof data === 'object' &&\n data !== null &&\n (data as { source?: unknown }).source === MESSAGE_SOURCE &&\n typeof (data as { type?: unknown }).type === 'string'\n )\n}\n"]}
|