@nominalso/vibe-host 0.0.1 → 0.1.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/README.md +75 -11
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +42 -1
- package/dist/index.d.ts +42 -1
- package/dist/index.js +1 -1
- package/package.json +1 -11
package/README.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# @nominalso/vibe-host
|
|
2
2
|
|
|
3
|
-
Host-side SDK for embedding **Nominal Vibe Apps**. Used by the Nominal app (nom-ui) to receive requests from an
|
|
4
|
-
|
|
5
|
-
**
|
|
6
|
-
pointing at the Nominal API, and the SDK wires up and dispatches all operations for you.
|
|
3
|
+
Host-side SDK for embedding **Nominal Vibe Apps**. Used by the Nominal app (nom-ui) to receive requests from an embedded Vibe App over a typed `postMessage` protocol and dispatch them to Nominal APIs. The host ships a **complete, type-checked handler map** covering every protocol operation — you supply a `clientConfig` pointing at the Nominal API, and the SDK wires up and dispatches all operations for you.
|
|
4
|
+
|
|
5
|
+
> **For AI agents:** you do **not** pass a `handlers` map. Provide `clientConfig` (where the Nominal API lives) and the SDK builds every handler. The iframe-side counterpart is `@nominalso/vibe-bridge`.
|
|
7
6
|
|
|
8
7
|
## Install
|
|
9
8
|
|
|
@@ -11,28 +10,93 @@ pointing at the Nominal API, and the SDK wires up and dispatches all operations
|
|
|
11
10
|
npm install @nominalso/vibe-host
|
|
12
11
|
```
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
Externalizes only `@hey-api/client-fetch`; TypeScript types are self-contained.
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
15
16
|
|
|
16
17
|
```tsx
|
|
17
18
|
import { VibeAppHost } from '@nominalso/vibe-host'
|
|
18
19
|
|
|
19
20
|
const host = new VibeAppHost({
|
|
21
|
+
// Iframe origin(s) allowed to talk to this host — must match exactly.
|
|
20
22
|
trustedOrigins: ['https://my-vibe-app.lovable.app'],
|
|
23
|
+
// This Vibe App's base path in nom-ui (used to sync the browser URL).
|
|
21
24
|
appBasePath: `/${tenant}/${subsidiary}/apps/${slug}`,
|
|
25
|
+
// Context pushed to the iframe on connect.
|
|
22
26
|
getContext: () => ({ tenant, subsidiaryId, subsidiaries, user, lastClosedPeriodSlug }),
|
|
23
27
|
// Points the built-in handler map at the Nominal API (e.g. nom-ui's proxy route).
|
|
24
|
-
// The SDK provides handlers for every protocol operation out of the box.
|
|
25
28
|
clientConfig: { baseUrl: '/api/proxy', stripApiPrefix: true },
|
|
26
29
|
})
|
|
27
30
|
|
|
28
|
-
// Start listening
|
|
31
|
+
// Start listening BEFORE the iframe loads, then push context on its `load` event.
|
|
29
32
|
const unmount = host.mount()
|
|
30
|
-
// host.pushContextTo(iframe.contentWindow)
|
|
33
|
+
// once the iframe has loaded: host.pushContextTo(iframe.contentWindow)
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
## API
|
|
37
|
+
|
|
38
|
+
### `new VibeAppHost(options)`
|
|
39
|
+
|
|
40
|
+
| Option | Type | Description |
|
|
41
|
+
| ---------------- | ----------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `trustedOrigins` | `string[]` | Iframe origins allowed to talk to this host. Messages from other origins are ignored. |
|
|
43
|
+
| `getContext` | `() => HostContext` | Returns the current context to send to the iframe (`hostVersion` is injected automatically). |
|
|
44
|
+
| `appBasePath` | `string` | Base URL path of this Vibe App in nom-ui, e.g. `/acme/1/apps/fixed-assets`. |
|
|
45
|
+
| `clientConfig` | `VibeApiClientOptions?` | Points the built-in handler map at the Nominal API. Defaults to `{ baseUrl: '', stripApiPrefix: false }`. |
|
|
46
|
+
|
|
47
|
+
`VibeApiClientOptions` is `{ baseUrl: string; stripApiPrefix?: boolean }`. Set `stripApiPrefix: true` when routing through a proxy whose convention expects paths without the `/api` prefix.
|
|
48
|
+
|
|
49
|
+
### `mount(iframeWindow?): () => void`
|
|
50
|
+
|
|
51
|
+
Starts listening for bridge messages and sets up browser back/forward sync. Returns a cleanup function. Pass `iframeWindow` to immediately push context; if omitted, the host answers `GET_CONTEXT` polls but won't proactively push until you call `pushContextTo`.
|
|
52
|
+
|
|
53
|
+
### `pushContextTo(iframeWindow): void`
|
|
54
|
+
|
|
55
|
+
Pushes the current context to the iframe and stores the window reference for future pushes (e.g. subroute sync). Call once the iframe has loaded if you called `mount()` with no argument.
|
|
56
|
+
|
|
57
|
+
### Exports
|
|
58
|
+
|
|
59
|
+
`VibeAppHost`, and the types `VibeAppHostOptions`, `HostContext`, `HostHandlers`, `RequestHandlers`, `ContextPayload`, `VibeApiClientOptions`.
|
|
60
|
+
|
|
61
|
+
## Mounting inside Next.js (nom-ui)
|
|
62
|
+
|
|
63
|
+
The iframe is server-rendered, so it may start loading before React hydrates, and cross-origin `contentDocument` is always `null`. The robust pattern: `mount()` (no arg) immediately to start listening, then `pushContextTo(iframe.contentWindow)` from the iframe's `load` event.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
'use client'
|
|
67
|
+
import { useEffect, useRef } from 'react'
|
|
68
|
+
import { VibeAppHost } from '@nominalso/vibe-host'
|
|
69
|
+
|
|
70
|
+
function VibeAppFrame({ host, src }: { host: VibeAppHost; src: string }) {
|
|
71
|
+
const iframeRef = useRef<HTMLIFrameElement>(null)
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const iframe = iframeRef.current
|
|
75
|
+
if (!iframe) return
|
|
76
|
+
const unmount = host.mount() // listen before the iframe loads
|
|
77
|
+
const onLoad = () => iframe.contentWindow && host.pushContextTo(iframe.contentWindow)
|
|
78
|
+
iframe.addEventListener('load', onLoad)
|
|
79
|
+
return () => {
|
|
80
|
+
iframe.removeEventListener('load', onLoad)
|
|
81
|
+
unmount()
|
|
82
|
+
}
|
|
83
|
+
}, [host])
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<iframe
|
|
87
|
+
ref={iframeRef}
|
|
88
|
+
src={src}
|
|
89
|
+
sandbox="allow-scripts allow-forms allow-same-origin allow-popups"
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Calling `contentWindow.postMessage` while the iframe is still at `about:blank` throws a `DOMException` — that's why context is pushed from the `load` event, not on effect setup.
|
|
96
|
+
|
|
97
|
+
## How it fits together
|
|
98
|
+
|
|
99
|
+
The iframe side is [`@nominalso/vibe-bridge`](https://www.npmjs.com/package/@nominalso/vibe-bridge). See the [repository](https://github.com/nominalso/vibe-apps-sdk#readme) for the full protocol and architecture.
|
|
36
100
|
|
|
37
101
|
## License
|
|
38
102
|
|
package/dist/index.cjs
CHANGED
package/dist/index.d.cts
CHANGED
|
@@ -3447,6 +3447,11 @@ interface VibeApiClientOptions {
|
|
|
3447
3447
|
type HostHandlers = Omit<RequestHandlers, 'GET_CONTEXT'>;
|
|
3448
3448
|
type HostContext = Omit<ContextPayload, 'hostVersion'>;
|
|
3449
3449
|
interface VibeAppHostOptions {
|
|
3450
|
+
/**
|
|
3451
|
+
* Iframe origins allowed to talk to this host. Messages from any other origin
|
|
3452
|
+
* are ignored, and context/pushes are only sent to these origins. Must match
|
|
3453
|
+
* the Vibe App's origin exactly (e.g. `'https://my-vibe-app.lovable.app'`).
|
|
3454
|
+
*/
|
|
3450
3455
|
trustedOrigins: string[];
|
|
3451
3456
|
/** Returns the current context to send to the iframe on connect. */
|
|
3452
3457
|
getContext: () => HostContext;
|
|
@@ -3455,8 +3460,44 @@ interface VibeAppHostOptions {
|
|
|
3455
3460
|
* Used to sync the browser URL with the iframe's internal subroute.
|
|
3456
3461
|
*/
|
|
3457
3462
|
appBasePath: string;
|
|
3463
|
+
/**
|
|
3464
|
+
* Points the built-in handler map at the Nominal API (e.g. nom-ui's proxy
|
|
3465
|
+
* route). The host provides handlers for every protocol operation out of the
|
|
3466
|
+
* box; this just tells them where to send requests. Defaults to
|
|
3467
|
+
* `{ baseUrl: '', stripApiPrefix: false }`.
|
|
3468
|
+
*/
|
|
3458
3469
|
clientConfig?: VibeApiClientOptions;
|
|
3459
3470
|
}
|
|
3471
|
+
/**
|
|
3472
|
+
* Host-side counterpart to `VibeAppBridge`. Construct it with the trusted
|
|
3473
|
+
* iframe origin(s), a `getContext` callback, the app's base path, and a
|
|
3474
|
+
* `clientConfig` pointing at the Nominal API; then call {@link VibeAppHost.mount}
|
|
3475
|
+
* to start listening. The handler map for all protocol operations is built
|
|
3476
|
+
* automatically — you do not pass handlers.
|
|
3477
|
+
*
|
|
3478
|
+
* @example
|
|
3479
|
+
* ```tsx
|
|
3480
|
+
* // In a React (nom-ui) component:
|
|
3481
|
+
* const host = new VibeAppHost({
|
|
3482
|
+
* trustedOrigins: ['https://my-vibe-app.lovable.app'],
|
|
3483
|
+
* appBasePath: `/${tenant}/${subsidiary}/apps/${slug}`,
|
|
3484
|
+
* getContext: () => ({ tenant, subsidiaryId, subsidiaries, user, lastClosedPeriodSlug }),
|
|
3485
|
+
* clientConfig: { baseUrl: '/api/proxy', stripApiPrefix: true },
|
|
3486
|
+
* })
|
|
3487
|
+
*
|
|
3488
|
+
* useEffect(() => {
|
|
3489
|
+
* const iframe = iframeRef.current
|
|
3490
|
+
* if (!iframe) return
|
|
3491
|
+
* const unmount = host.mount() // listen before the iframe loads
|
|
3492
|
+
* const onLoad = () => iframe.contentWindow && host.pushContextTo(iframe.contentWindow)
|
|
3493
|
+
* iframe.addEventListener('load', onLoad)
|
|
3494
|
+
* return () => {
|
|
3495
|
+
* iframe.removeEventListener('load', onLoad)
|
|
3496
|
+
* unmount()
|
|
3497
|
+
* }
|
|
3498
|
+
* }, [host])
|
|
3499
|
+
* ```
|
|
3500
|
+
*/
|
|
3460
3501
|
declare class VibeAppHost {
|
|
3461
3502
|
private readonly trustedOrigins;
|
|
3462
3503
|
private readonly handlers;
|
|
@@ -3494,4 +3535,4 @@ declare class VibeAppHost {
|
|
|
3494
3535
|
private respond;
|
|
3495
3536
|
}
|
|
3496
3537
|
|
|
3497
|
-
export { type HostContext, type HostHandlers, type RequestHandlers, VibeAppHost, type VibeAppHostOptions };
|
|
3538
|
+
export { type ContextPayload, type HostContext, type HostHandlers, type RequestHandlers, type VibeApiClientOptions, VibeAppHost, type VibeAppHostOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -3447,6 +3447,11 @@ interface VibeApiClientOptions {
|
|
|
3447
3447
|
type HostHandlers = Omit<RequestHandlers, 'GET_CONTEXT'>;
|
|
3448
3448
|
type HostContext = Omit<ContextPayload, 'hostVersion'>;
|
|
3449
3449
|
interface VibeAppHostOptions {
|
|
3450
|
+
/**
|
|
3451
|
+
* Iframe origins allowed to talk to this host. Messages from any other origin
|
|
3452
|
+
* are ignored, and context/pushes are only sent to these origins. Must match
|
|
3453
|
+
* the Vibe App's origin exactly (e.g. `'https://my-vibe-app.lovable.app'`).
|
|
3454
|
+
*/
|
|
3450
3455
|
trustedOrigins: string[];
|
|
3451
3456
|
/** Returns the current context to send to the iframe on connect. */
|
|
3452
3457
|
getContext: () => HostContext;
|
|
@@ -3455,8 +3460,44 @@ interface VibeAppHostOptions {
|
|
|
3455
3460
|
* Used to sync the browser URL with the iframe's internal subroute.
|
|
3456
3461
|
*/
|
|
3457
3462
|
appBasePath: string;
|
|
3463
|
+
/**
|
|
3464
|
+
* Points the built-in handler map at the Nominal API (e.g. nom-ui's proxy
|
|
3465
|
+
* route). The host provides handlers for every protocol operation out of the
|
|
3466
|
+
* box; this just tells them where to send requests. Defaults to
|
|
3467
|
+
* `{ baseUrl: '', stripApiPrefix: false }`.
|
|
3468
|
+
*/
|
|
3458
3469
|
clientConfig?: VibeApiClientOptions;
|
|
3459
3470
|
}
|
|
3471
|
+
/**
|
|
3472
|
+
* Host-side counterpart to `VibeAppBridge`. Construct it with the trusted
|
|
3473
|
+
* iframe origin(s), a `getContext` callback, the app's base path, and a
|
|
3474
|
+
* `clientConfig` pointing at the Nominal API; then call {@link VibeAppHost.mount}
|
|
3475
|
+
* to start listening. The handler map for all protocol operations is built
|
|
3476
|
+
* automatically — you do not pass handlers.
|
|
3477
|
+
*
|
|
3478
|
+
* @example
|
|
3479
|
+
* ```tsx
|
|
3480
|
+
* // In a React (nom-ui) component:
|
|
3481
|
+
* const host = new VibeAppHost({
|
|
3482
|
+
* trustedOrigins: ['https://my-vibe-app.lovable.app'],
|
|
3483
|
+
* appBasePath: `/${tenant}/${subsidiary}/apps/${slug}`,
|
|
3484
|
+
* getContext: () => ({ tenant, subsidiaryId, subsidiaries, user, lastClosedPeriodSlug }),
|
|
3485
|
+
* clientConfig: { baseUrl: '/api/proxy', stripApiPrefix: true },
|
|
3486
|
+
* })
|
|
3487
|
+
*
|
|
3488
|
+
* useEffect(() => {
|
|
3489
|
+
* const iframe = iframeRef.current
|
|
3490
|
+
* if (!iframe) return
|
|
3491
|
+
* const unmount = host.mount() // listen before the iframe loads
|
|
3492
|
+
* const onLoad = () => iframe.contentWindow && host.pushContextTo(iframe.contentWindow)
|
|
3493
|
+
* iframe.addEventListener('load', onLoad)
|
|
3494
|
+
* return () => {
|
|
3495
|
+
* iframe.removeEventListener('load', onLoad)
|
|
3496
|
+
* unmount()
|
|
3497
|
+
* }
|
|
3498
|
+
* }, [host])
|
|
3499
|
+
* ```
|
|
3500
|
+
*/
|
|
3460
3501
|
declare class VibeAppHost {
|
|
3461
3502
|
private readonly trustedOrigins;
|
|
3462
3503
|
private readonly handlers;
|
|
@@ -3494,4 +3535,4 @@ declare class VibeAppHost {
|
|
|
3494
3535
|
private respond;
|
|
3495
3536
|
}
|
|
3496
3537
|
|
|
3497
|
-
export { type HostContext, type HostHandlers, type RequestHandlers, VibeAppHost, type VibeAppHostOptions };
|
|
3538
|
+
export { type ContextPayload, type HostContext, type HostHandlers, type RequestHandlers, type VibeApiClientOptions, VibeAppHost, type VibeAppHostOptions };
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nominalso/vibe-host",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Host-side SDK for embedding Nominal Vibe Apps — receives bridge requests over a typed postMessage protocol and dispatches them to Nominal APIs (used by nom-ui).",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"nominal",
|
|
8
|
-
"vibe-apps",
|
|
9
|
-
"vibe-app",
|
|
10
|
-
"sdk",
|
|
11
|
-
"iframe",
|
|
12
|
-
"postmessage",
|
|
13
|
-
"host",
|
|
14
|
-
"embed"
|
|
15
|
-
],
|
|
16
6
|
"homepage": "https://github.com/nominalso/vibe-apps-sdk#readme",
|
|
17
7
|
"bugs": {
|
|
18
8
|
"url": "https://github.com/nominalso/vibe-apps-sdk/issues"
|