@aiworkbench/vibe-bridge 0.0.3 → 0.0.5
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/components/VibeHost.d.ts.map +1 -1
- package/dist/core/loader.d.ts +33 -1
- package/dist/core/loader.d.ts.map +1 -1
- package/dist/core/types.d.ts +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/hooks/useVibeLoader.d.ts +1 -1
- package/dist/hooks/useVibeLoader.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -6
- package/package.json +3 -4
- package/src/adapters/api.ts +0 -24
- package/src/adapters/auth.ts +0 -28
- package/src/adapters/events.ts +0 -16
- package/src/adapters/navigation.ts +0 -21
- package/src/adapters/storage.ts +0 -27
- package/src/adapters/theme.ts +0 -24
- package/src/adapters/toast.ts +0 -34
- package/src/components/VibeErrorBoundary.tsx +0 -36
- package/src/components/VibeHost.tsx +0 -104
- package/src/core/event-bus.ts +0 -60
- package/src/core/loader.ts +0 -57
- package/src/core/permissions.ts +0 -41
- package/src/core/types.ts +0 -54
- package/src/hooks/useVibeBridge.ts +0 -49
- package/src/hooks/useVibeEvents.ts +0 -13
- package/src/hooks/useVibeLoader.ts +0 -42
- package/src/index.ts +0 -33
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeHost.d.ts","sourceRoot":"","sources":["../../src/components/VibeHost.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"VibeHost.d.ts","sourceRoot":"","sources":["../../src/components/VibeHost.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAkFnD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,2CAiB5C"}
|
package/dist/core/loader.d.ts
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Register shared dependencies that mini-app bundles may import as bare
|
|
3
|
+
* specifiers. Call this once at app startup, *before* any VibeHost renders.
|
|
4
|
+
*
|
|
5
|
+
* Example:
|
|
6
|
+
* setSharedDependencies({
|
|
7
|
+
* "react": "https://esm.sh/react@19",
|
|
8
|
+
* "react/jsx-runtime":"https://esm.sh/react@19/jsx-runtime",
|
|
9
|
+
* "react-dom": "https://esm.sh/react-dom@19",
|
|
10
|
+
* "react-dom/client": "https://esm.sh/react-dom@19/client",
|
|
11
|
+
* });
|
|
12
|
+
*/
|
|
13
|
+
export declare function setSharedDependencies(deps: Record<string, string>): void;
|
|
14
|
+
/**
|
|
15
|
+
* Configure the set of trusted origins for mini-app bundles.
|
|
16
|
+
* Call this once at app startup before any VibeHost renders.
|
|
17
|
+
*
|
|
18
|
+
* Example:
|
|
19
|
+
* setAllowedOrigins([
|
|
20
|
+
* "https://vibedevstore.blob.core.windows.net",
|
|
21
|
+
* "https://vibestagingstore.blob.core.windows.net",
|
|
22
|
+
* ]);
|
|
23
|
+
*/
|
|
24
|
+
export declare function setAllowedOrigins(origins: string[]): void;
|
|
25
|
+
export interface LoadScriptOptions {
|
|
26
|
+
/** SRI integrity hash (e.g. "sha384-..."). If provided, the browser will
|
|
27
|
+
* refuse to execute the script if the content doesn't match. */
|
|
28
|
+
integrity?: string;
|
|
29
|
+
/** CORS setting for the script. Required for SRI on cross-origin scripts.
|
|
30
|
+
* Defaults to "anonymous" when integrity is provided. */
|
|
31
|
+
crossOrigin?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function loadScript(src: string, options?: LoadScriptOptions): Promise<void>;
|
|
2
34
|
export declare function waitForElement(tagName: string): Promise<void>;
|
|
3
35
|
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/core/loader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/core/loader.ts"],"names":[],"mappings":"AAoBA;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CASxE;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAQzD;AAwBD,MAAM,WAAW,iBAAiB;IAChC;qEACiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;8DAC0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA8Cf;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK7D"}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ export interface VibeHostProps {
|
|
|
5
5
|
manifest: VibeManifest;
|
|
6
6
|
/** URL to the built JS bundle for the mini-app. */
|
|
7
7
|
src: string;
|
|
8
|
+
/** SRI integrity hash (e.g. "sha384-...") from the registry entry.
|
|
9
|
+
* When provided, the browser refuses to execute the script if the
|
|
10
|
+
* content doesn't match — preventing tampered bundles from running. */
|
|
11
|
+
integrity?: string;
|
|
8
12
|
/** Shown while the script loads and custom element registers. */
|
|
9
13
|
fallback?: ReactNode;
|
|
10
14
|
/** Shown when the script fails to load or the element fails to mount. */
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY,EACb,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,QAAQ,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ,iEAAiE;IACjE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,yEAAyE;IACzE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC;IAC5C,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrC,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,sCAAsC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,SAAS,CAAC;IACf,UAAU,EAAE,gBAAgB,CAAC;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,SAAS,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACvC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY,EACb,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtD,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,QAAQ,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ;;4EAEwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,yEAAyE;IACzE,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC;IAC5C,iDAAiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrC,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,sCAAsC;IACtC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,GAAG,EAAE,SAAS,CAAC;IACf,UAAU,EAAE,gBAAgB,CAAC;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,SAAS,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACvC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { UseVibeLoaderResult } from "../core/types";
|
|
2
|
-
export declare function useVibeLoader(src: string, tagName: string): UseVibeLoaderResult;
|
|
2
|
+
export declare function useVibeLoader(src: string, tagName: string, integrity?: string): UseVibeLoaderResult;
|
|
3
3
|
//# sourceMappingURL=useVibeLoader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useVibeLoader.d.ts","sourceRoot":"","sources":["../../src/hooks/useVibeLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgB,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEvE,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"useVibeLoader.d.ts","sourceRoot":"","sources":["../../src/hooks/useVibeLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgB,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEvE,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,MAAM,GACjB,mBAAmB,CAkCrB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { createStorageAdapter } from "./adapters/storage";
|
|
|
12
12
|
export { createEventsAdapter } from "./adapters/events";
|
|
13
13
|
export { filterByPermissions } from "./core/permissions";
|
|
14
14
|
export { getEventBus, emitEvent } from "./core/event-bus";
|
|
15
|
+
export { setAllowedOrigins, setSharedDependencies } from "./core/loader";
|
|
15
16
|
export type { VibeHostProps, AdapterOverrides, LoaderStatus, UseVibeLoaderResult, } from "./core/types";
|
|
16
17
|
export type { AuthAdapterDeps } from "./adapters/auth";
|
|
17
18
|
export type { ApiAdapterDeps } from "./adapters/api";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAGnE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGzE,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,GACpB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,31 @@ import { useState, useEffect } from "react";
|
|
|
7
7
|
// src/core/loader.ts
|
|
8
8
|
var loadedScripts = new Set;
|
|
9
9
|
var ALLOWED_PROTOCOLS = new Set(["https:", "http:"]);
|
|
10
|
+
var allowedOrigins = null;
|
|
11
|
+
var sharedDeps = null;
|
|
12
|
+
var importMapInjected = false;
|
|
13
|
+
function setSharedDependencies(deps) {
|
|
14
|
+
if (importMapInjected) {
|
|
15
|
+
console.warn("[vibe-bridge] setSharedDependencies() called after an import map " + "was already injected. The new entries will be ignored.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
sharedDeps = { ...deps };
|
|
19
|
+
}
|
|
20
|
+
function ensureImportMap() {
|
|
21
|
+
if (importMapInjected || !sharedDeps)
|
|
22
|
+
return;
|
|
23
|
+
const script = document.createElement("script");
|
|
24
|
+
script.type = "importmap";
|
|
25
|
+
script.textContent = JSON.stringify({ imports: sharedDeps });
|
|
26
|
+
document.head.appendChild(script);
|
|
27
|
+
importMapInjected = true;
|
|
28
|
+
}
|
|
29
|
+
function setAllowedOrigins(origins) {
|
|
30
|
+
allowedOrigins = new Set(origins.map((o) => {
|
|
31
|
+
const url = new URL(o);
|
|
32
|
+
return url.origin;
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
10
35
|
function validateScriptSrc(src) {
|
|
11
36
|
let url;
|
|
12
37
|
try {
|
|
@@ -17,9 +42,12 @@ function validateScriptSrc(src) {
|
|
|
17
42
|
if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
|
|
18
43
|
return `Refused to load vibe app script with disallowed protocol "${url.protocol}": ${src}`;
|
|
19
44
|
}
|
|
45
|
+
if (allowedOrigins && !allowedOrigins.has(url.origin)) {
|
|
46
|
+
return `Refused to load vibe app script from untrusted origin "${url.origin}": ${src}. ` + `Allowed origins: ${[...allowedOrigins].join(", ")}`;
|
|
47
|
+
}
|
|
20
48
|
return null;
|
|
21
49
|
}
|
|
22
|
-
function loadScript(src) {
|
|
50
|
+
function loadScript(src, options) {
|
|
23
51
|
const validationError = validateScriptSrc(src);
|
|
24
52
|
if (validationError) {
|
|
25
53
|
return Promise.reject(new Error(validationError));
|
|
@@ -37,13 +65,19 @@ function loadScript(src) {
|
|
|
37
65
|
const script = document.createElement("script");
|
|
38
66
|
script.type = "module";
|
|
39
67
|
script.src = src;
|
|
68
|
+
if (options?.integrity) {
|
|
69
|
+
script.integrity = options.integrity;
|
|
70
|
+
script.crossOrigin = options.crossOrigin ?? "anonymous";
|
|
71
|
+
}
|
|
40
72
|
script.onload = () => {
|
|
41
73
|
loadedScripts.add(src);
|
|
42
74
|
resolve();
|
|
43
75
|
};
|
|
44
76
|
script.onerror = () => {
|
|
45
|
-
|
|
77
|
+
const integrityHint = options?.integrity ? " This may be an SRI integrity mismatch — the bundle content doesn't match the expected hash." : "";
|
|
78
|
+
reject(new Error(`Failed to load vibe app script: ${src}.${integrityHint}`));
|
|
46
79
|
};
|
|
80
|
+
ensureImportMap();
|
|
47
81
|
document.head.appendChild(script);
|
|
48
82
|
});
|
|
49
83
|
}
|
|
@@ -57,14 +91,14 @@ function waitForElement(tagName) {
|
|
|
57
91
|
}
|
|
58
92
|
|
|
59
93
|
// src/hooks/useVibeLoader.ts
|
|
60
|
-
function useVibeLoader(src, tagName) {
|
|
94
|
+
function useVibeLoader(src, tagName, integrity) {
|
|
61
95
|
const [status, setStatus] = useState("loading");
|
|
62
96
|
const [error, setError] = useState(null);
|
|
63
97
|
useEffect(() => {
|
|
64
98
|
let cancelled = false;
|
|
65
99
|
setStatus("loading");
|
|
66
100
|
setError(null);
|
|
67
|
-
loadScript(src).then(() => waitForElement(tagName)).then(() => {
|
|
101
|
+
loadScript(src, integrity ? { integrity } : undefined).then(() => waitForElement(tagName)).then(() => {
|
|
68
102
|
if (!cancelled) {
|
|
69
103
|
setStatus("ready");
|
|
70
104
|
}
|
|
@@ -78,7 +112,7 @@ function useVibeLoader(src, tagName) {
|
|
|
78
112
|
return () => {
|
|
79
113
|
cancelled = true;
|
|
80
114
|
};
|
|
81
|
-
}, [src, tagName]);
|
|
115
|
+
}, [src, tagName, integrity]);
|
|
82
116
|
return { status, error };
|
|
83
117
|
}
|
|
84
118
|
|
|
@@ -339,6 +373,7 @@ import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
|
|
|
339
373
|
function VibeHostInner({
|
|
340
374
|
manifest,
|
|
341
375
|
src,
|
|
376
|
+
integrity,
|
|
342
377
|
fallback,
|
|
343
378
|
errorFallback,
|
|
344
379
|
adapters,
|
|
@@ -350,7 +385,7 @@ function VibeHostInner({
|
|
|
350
385
|
const containerRef = useRef(null);
|
|
351
386
|
const elementRef = useRef(null);
|
|
352
387
|
const readyFiredRef = useRef(false);
|
|
353
|
-
const { status, error: loadError } = useVibeLoader(src, manifest.id);
|
|
388
|
+
const { status, error: loadError } = useVibeLoader(src, manifest.id, integrity);
|
|
354
389
|
const bridge = useVibeBridge({
|
|
355
390
|
appId: manifest.id,
|
|
356
391
|
permissions: manifest.permissions,
|
|
@@ -435,6 +470,8 @@ export {
|
|
|
435
470
|
useVibeLoader,
|
|
436
471
|
useVibeEvents,
|
|
437
472
|
useVibeBridge,
|
|
473
|
+
setSharedDependencies,
|
|
474
|
+
setAllowedOrigins,
|
|
438
475
|
getEventBus,
|
|
439
476
|
filterByPermissions,
|
|
440
477
|
emitEvent,
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiworkbench/vibe-bridge",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"publishConfig": { "access": "public" },
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"bun": "./src/index.ts",
|
|
11
10
|
"import": "./dist/index.js",
|
|
12
11
|
"types": "./dist/index.d.ts"
|
|
13
12
|
}
|
|
@@ -19,9 +18,9 @@
|
|
|
19
18
|
"clean": "rm -rf dist",
|
|
20
19
|
"prepublishOnly": "bun run build"
|
|
21
20
|
},
|
|
22
|
-
"files": ["dist"
|
|
21
|
+
"files": ["dist"],
|
|
23
22
|
"dependencies": {
|
|
24
|
-
"@aiworkbench/vibe-types": "^0.0.
|
|
23
|
+
"@aiworkbench/vibe-types": "^0.0.4"
|
|
25
24
|
},
|
|
26
25
|
"peerDependencies": {
|
|
27
26
|
"react": "^19.0.0",
|
package/src/adapters/api.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { ApiBridge } from "@aiworkbench/vibe-types";
|
|
2
|
-
|
|
3
|
-
export interface ApiAdapterDeps {
|
|
4
|
-
getToken: () => Promise<string>;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function createApiAdapter(deps: ApiAdapterDeps): ApiBridge {
|
|
8
|
-
return {
|
|
9
|
-
async fetch(endpoint: string, options?: RequestInit): Promise<Response> {
|
|
10
|
-
const url = endpoint.startsWith("/api/")
|
|
11
|
-
? endpoint
|
|
12
|
-
: `/api/${endpoint.replace(/^\//, "")}`;
|
|
13
|
-
|
|
14
|
-
const token = await deps.getToken();
|
|
15
|
-
const headers = new Headers(options?.headers);
|
|
16
|
-
headers.set("Authorization", `Bearer ${token}`);
|
|
17
|
-
|
|
18
|
-
return globalThis.fetch(url, {
|
|
19
|
-
...options,
|
|
20
|
-
headers,
|
|
21
|
-
});
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
}
|
package/src/adapters/auth.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { AuthBridge, BridgeUser } from "@aiworkbench/vibe-types";
|
|
2
|
-
|
|
3
|
-
export interface AuthAdapterDeps {
|
|
4
|
-
session: {
|
|
5
|
-
user?: { name?: string | null; email?: string | null } | null;
|
|
6
|
-
idToken?: string;
|
|
7
|
-
} | null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function createAuthAdapter(deps: AuthAdapterDeps): AuthBridge {
|
|
11
|
-
return {
|
|
12
|
-
getUser(): BridgeUser {
|
|
13
|
-
const user = deps.session?.user;
|
|
14
|
-
return {
|
|
15
|
-
id: user?.email ?? "unknown",
|
|
16
|
-
name: user?.name ?? "Unknown User",
|
|
17
|
-
email: user?.email ?? undefined,
|
|
18
|
-
};
|
|
19
|
-
},
|
|
20
|
-
async getToken(): Promise<string> {
|
|
21
|
-
const token = deps.session?.idToken;
|
|
22
|
-
if (!token) {
|
|
23
|
-
throw new Error("No authentication token available.");
|
|
24
|
-
}
|
|
25
|
-
return token;
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
}
|
package/src/adapters/events.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { EventsBridge, EventPayload } from "@aiworkbench/vibe-types";
|
|
2
|
-
import { emitEvent, onEvent, onceEvent } from "../core/event-bus";
|
|
3
|
-
|
|
4
|
-
export function createEventsAdapter(appId: string): EventsBridge {
|
|
5
|
-
return {
|
|
6
|
-
emit(event: string, payload?: EventPayload): void {
|
|
7
|
-
emitEvent(event, payload ?? {}, appId);
|
|
8
|
-
},
|
|
9
|
-
on(event: string, handler: (payload: EventPayload) => void): () => void {
|
|
10
|
-
return onEvent(event, (detail) => handler(detail.payload));
|
|
11
|
-
},
|
|
12
|
-
once(event: string, handler: (payload: EventPayload) => void): () => void {
|
|
13
|
-
return onceEvent(event, (detail) => handler(detail.payload));
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { NavigationBridge } from "@aiworkbench/vibe-types";
|
|
2
|
-
|
|
3
|
-
export interface NavigationAdapterDeps {
|
|
4
|
-
push: (path: string) => void;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function createNavigationAdapter(
|
|
8
|
-
deps: NavigationAdapterDeps,
|
|
9
|
-
): NavigationBridge {
|
|
10
|
-
return {
|
|
11
|
-
navigate(path: string): void {
|
|
12
|
-
// Block external URLs and javascript: protocol
|
|
13
|
-
if (/^https?:\/\//i.test(path) || /^javascript:/i.test(path)) {
|
|
14
|
-
throw new Error(
|
|
15
|
-
`Navigation to external URLs is not allowed: ${path}`,
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
deps.push(path);
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
}
|
package/src/adapters/storage.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { StorageBridge } from "@aiworkbench/vibe-types";
|
|
2
|
-
|
|
3
|
-
export function createStorageAdapter(appId: string): StorageBridge {
|
|
4
|
-
const prefix = `vibe:${appId}:`;
|
|
5
|
-
|
|
6
|
-
return {
|
|
7
|
-
async get(key: string): Promise<string | null> {
|
|
8
|
-
return localStorage.getItem(`${prefix}${key}`);
|
|
9
|
-
},
|
|
10
|
-
async set(key: string, value: string): Promise<void> {
|
|
11
|
-
localStorage.setItem(`${prefix}${key}`, value);
|
|
12
|
-
},
|
|
13
|
-
async remove(key: string): Promise<void> {
|
|
14
|
-
localStorage.removeItem(`${prefix}${key}`);
|
|
15
|
-
},
|
|
16
|
-
async keys(): Promise<string[]> {
|
|
17
|
-
const result: string[] = [];
|
|
18
|
-
for (let i = 0; i < localStorage.length; i++) {
|
|
19
|
-
const k = localStorage.key(i);
|
|
20
|
-
if (k?.startsWith(prefix)) {
|
|
21
|
-
result.push(k.slice(prefix.length));
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return result;
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
}
|
package/src/adapters/theme.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { ThemeBridge } from "@aiworkbench/vibe-types";
|
|
2
|
-
|
|
3
|
-
export function createThemeAdapter(): ThemeBridge {
|
|
4
|
-
return {
|
|
5
|
-
current(): "light" | "dark" {
|
|
6
|
-
// Check <html> class for Tailwind dark mode convention
|
|
7
|
-
const html = document.documentElement;
|
|
8
|
-
if (html.classList.contains("dark")) return "dark";
|
|
9
|
-
if (html.classList.contains("light")) return "light";
|
|
10
|
-
|
|
11
|
-
// Check data-theme attribute
|
|
12
|
-
const dataTheme = html.getAttribute("data-theme");
|
|
13
|
-
if (dataTheme === "dark") return "dark";
|
|
14
|
-
if (dataTheme === "light") return "light";
|
|
15
|
-
|
|
16
|
-
// Fallback to prefers-color-scheme media query
|
|
17
|
-
if (globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches) {
|
|
18
|
-
return "dark";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return "light";
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
}
|
package/src/adapters/toast.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { ToastBridge } from "@aiworkbench/vibe-types";
|
|
2
|
-
|
|
3
|
-
export interface ToastAdapterDeps {
|
|
4
|
-
toast: {
|
|
5
|
-
(message: string): void;
|
|
6
|
-
success: (message: string) => void;
|
|
7
|
-
error: (message: string) => void;
|
|
8
|
-
info: (message: string) => void;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function createToastAdapter(deps: ToastAdapterDeps): ToastBridge {
|
|
13
|
-
return {
|
|
14
|
-
show(
|
|
15
|
-
message: string,
|
|
16
|
-
options?: { type?: "info" | "success" | "error" },
|
|
17
|
-
): void {
|
|
18
|
-
const type = options?.type ?? "info";
|
|
19
|
-
switch (type) {
|
|
20
|
-
case "success":
|
|
21
|
-
deps.toast.success(message);
|
|
22
|
-
break;
|
|
23
|
-
case "error":
|
|
24
|
-
deps.toast.error(message);
|
|
25
|
-
break;
|
|
26
|
-
case "info":
|
|
27
|
-
deps.toast.info(message);
|
|
28
|
-
break;
|
|
29
|
-
default:
|
|
30
|
-
deps.toast(message);
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { Component, type ReactNode, type ErrorInfo } from "react";
|
|
2
|
-
|
|
3
|
-
interface VibeErrorBoundaryProps {
|
|
4
|
-
fallback: (error: Error) => ReactNode;
|
|
5
|
-
onError?: (error: Error) => void;
|
|
6
|
-
children: ReactNode;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface VibeErrorBoundaryState {
|
|
10
|
-
error: Error | null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class VibeErrorBoundary extends Component<
|
|
14
|
-
VibeErrorBoundaryProps,
|
|
15
|
-
VibeErrorBoundaryState
|
|
16
|
-
> {
|
|
17
|
-
constructor(props: VibeErrorBoundaryProps) {
|
|
18
|
-
super(props);
|
|
19
|
-
this.state = { error: null };
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
static getDerivedStateFromError(error: Error): VibeErrorBoundaryState {
|
|
23
|
-
return { error };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
componentDidCatch(error: Error, _info: ErrorInfo): void {
|
|
27
|
-
this.props.onError?.(error);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
render(): ReactNode {
|
|
31
|
-
if (this.state.error) {
|
|
32
|
-
return this.props.fallback(this.state.error);
|
|
33
|
-
}
|
|
34
|
-
return this.props.children;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { useRef, useEffect, useCallback } from "react";
|
|
2
|
-
import type { Bridge } from "@aiworkbench/vibe-types";
|
|
3
|
-
import { useVibeLoader } from "../hooks/useVibeLoader";
|
|
4
|
-
import { useVibeBridge } from "../hooks/useVibeBridge";
|
|
5
|
-
import { VibeErrorBoundary } from "./VibeErrorBoundary";
|
|
6
|
-
import type { VibeHostProps } from "../core/types";
|
|
7
|
-
|
|
8
|
-
interface VibeElementInstance extends HTMLElement {
|
|
9
|
-
bridge: Bridge;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function VibeHostInner({
|
|
13
|
-
manifest,
|
|
14
|
-
src,
|
|
15
|
-
fallback,
|
|
16
|
-
errorFallback,
|
|
17
|
-
adapters,
|
|
18
|
-
onReady,
|
|
19
|
-
onError,
|
|
20
|
-
className,
|
|
21
|
-
style,
|
|
22
|
-
}: VibeHostProps) {
|
|
23
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
24
|
-
const elementRef = useRef<VibeElementInstance | null>(null);
|
|
25
|
-
const readyFiredRef = useRef(false);
|
|
26
|
-
|
|
27
|
-
const { status, error: loadError } = useVibeLoader(src, manifest.id);
|
|
28
|
-
|
|
29
|
-
const bridge = useVibeBridge({
|
|
30
|
-
appId: manifest.id,
|
|
31
|
-
permissions: manifest.permissions,
|
|
32
|
-
overrides: adapters,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Handle load errors
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (loadError) {
|
|
38
|
-
onError?.(loadError);
|
|
39
|
-
}
|
|
40
|
-
}, [loadError, onError]);
|
|
41
|
-
|
|
42
|
-
// Mount the custom element once it's defined
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
const container = containerRef.current;
|
|
45
|
-
if (status !== "ready" || !container) return;
|
|
46
|
-
|
|
47
|
-
// Create and mount the custom element
|
|
48
|
-
const el = document.createElement(manifest.id) as VibeElementInstance;
|
|
49
|
-
el.bridge = bridge;
|
|
50
|
-
container.appendChild(el);
|
|
51
|
-
elementRef.current = el;
|
|
52
|
-
|
|
53
|
-
if (!readyFiredRef.current) {
|
|
54
|
-
readyFiredRef.current = true;
|
|
55
|
-
onReady?.();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return () => {
|
|
59
|
-
if (container.contains(el)) {
|
|
60
|
-
container.removeChild(el);
|
|
61
|
-
}
|
|
62
|
-
elementRef.current = null;
|
|
63
|
-
};
|
|
64
|
-
}, [status, manifest.id]);
|
|
65
|
-
|
|
66
|
-
// Re-inject bridge when it changes (session/theme updates)
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
if (elementRef.current) {
|
|
69
|
-
elementRef.current.bridge = bridge;
|
|
70
|
-
}
|
|
71
|
-
}, [bridge]);
|
|
72
|
-
|
|
73
|
-
if (status === "error" && loadError) {
|
|
74
|
-
if (errorFallback) {
|
|
75
|
-
return <>{errorFallback(loadError)}</>;
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (status === "loading") {
|
|
81
|
-
return <>{fallback ?? null}</>;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return <div ref={containerRef} className={className} style={style} />;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function VibeHost(props: VibeHostProps) {
|
|
88
|
-
const defaultErrorFallback = useCallback(
|
|
89
|
-
(error: Error) => {
|
|
90
|
-
props.onError?.(error);
|
|
91
|
-
if (props.errorFallback) {
|
|
92
|
-
return <>{props.errorFallback(error)}</>;
|
|
93
|
-
}
|
|
94
|
-
return null;
|
|
95
|
-
},
|
|
96
|
-
[props.onError, props.errorFallback],
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<VibeErrorBoundary fallback={defaultErrorFallback} onError={props.onError}>
|
|
101
|
-
<VibeHostInner {...props} />
|
|
102
|
-
</VibeErrorBoundary>
|
|
103
|
-
);
|
|
104
|
-
}
|
package/src/core/event-bus.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
const VIBE_EVENT_PREFIX = "vibe:";
|
|
2
|
-
|
|
3
|
-
export interface VibeEventDetail {
|
|
4
|
-
payload: Record<string, unknown>;
|
|
5
|
-
sourceAppId: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
let sharedBus: EventTarget | null = null;
|
|
9
|
-
|
|
10
|
-
export function getEventBus(): EventTarget {
|
|
11
|
-
if (!sharedBus) {
|
|
12
|
-
sharedBus = new EventTarget();
|
|
13
|
-
}
|
|
14
|
-
return sharedBus;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function prefixedEventName(event: string): string {
|
|
18
|
-
if (event.startsWith(VIBE_EVENT_PREFIX)) {
|
|
19
|
-
return event;
|
|
20
|
-
}
|
|
21
|
-
return `${VIBE_EVENT_PREFIX}${event}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function emitEvent(
|
|
25
|
-
event: string,
|
|
26
|
-
payload: Record<string, unknown>,
|
|
27
|
-
sourceAppId: string,
|
|
28
|
-
): void {
|
|
29
|
-
const bus = getEventBus();
|
|
30
|
-
const detail: VibeEventDetail = { payload, sourceAppId };
|
|
31
|
-
bus.dispatchEvent(
|
|
32
|
-
new CustomEvent(prefixedEventName(event), { detail }),
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function onEvent(
|
|
37
|
-
event: string,
|
|
38
|
-
handler: (detail: VibeEventDetail) => void,
|
|
39
|
-
): () => void {
|
|
40
|
-
const bus = getEventBus();
|
|
41
|
-
const prefixed = prefixedEventName(event);
|
|
42
|
-
const listener = (e: Event) => {
|
|
43
|
-
handler((e as CustomEvent<VibeEventDetail>).detail);
|
|
44
|
-
};
|
|
45
|
-
bus.addEventListener(prefixed, listener);
|
|
46
|
-
return () => bus.removeEventListener(prefixed, listener);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function onceEvent(
|
|
50
|
-
event: string,
|
|
51
|
-
handler: (detail: VibeEventDetail) => void,
|
|
52
|
-
): () => void {
|
|
53
|
-
const bus = getEventBus();
|
|
54
|
-
const prefixed = prefixedEventName(event);
|
|
55
|
-
const listener = (e: Event) => {
|
|
56
|
-
handler((e as CustomEvent<VibeEventDetail>).detail);
|
|
57
|
-
};
|
|
58
|
-
bus.addEventListener(prefixed, listener, { once: true });
|
|
59
|
-
return () => bus.removeEventListener(prefixed, listener);
|
|
60
|
-
}
|
package/src/core/loader.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
const loadedScripts = new Set<string>();
|
|
2
|
-
|
|
3
|
-
const ALLOWED_PROTOCOLS = new Set(["https:", "http:"]);
|
|
4
|
-
|
|
5
|
-
function validateScriptSrc(src: string): string | null {
|
|
6
|
-
let url: URL;
|
|
7
|
-
try {
|
|
8
|
-
url = new URL(src, window.location.href);
|
|
9
|
-
} catch {
|
|
10
|
-
return `Invalid vibe app script URL: ${src}`;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
|
|
14
|
-
return `Refused to load vibe app script with disallowed protocol "${url.protocol}": ${src}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function loadScript(src: string): Promise<void> {
|
|
21
|
-
const validationError = validateScriptSrc(src);
|
|
22
|
-
if (validationError) {
|
|
23
|
-
return Promise.reject(new Error(validationError));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (loadedScripts.has(src)) {
|
|
27
|
-
return Promise.resolve();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
const existing = document.querySelector(`script[src="${CSS.escape(src)}"]`);
|
|
32
|
-
if (existing) {
|
|
33
|
-
loadedScripts.add(src);
|
|
34
|
-
resolve();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const script = document.createElement("script");
|
|
39
|
-
script.type = "module";
|
|
40
|
-
script.src = src;
|
|
41
|
-
script.onload = () => {
|
|
42
|
-
loadedScripts.add(src);
|
|
43
|
-
resolve();
|
|
44
|
-
};
|
|
45
|
-
script.onerror = () => {
|
|
46
|
-
reject(new Error(`Failed to load vibe app script: ${src}`));
|
|
47
|
-
};
|
|
48
|
-
document.head.appendChild(script);
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function waitForElement(tagName: string): Promise<void> {
|
|
53
|
-
if (customElements.get(tagName)) {
|
|
54
|
-
return Promise.resolve();
|
|
55
|
-
}
|
|
56
|
-
return customElements.whenDefined(tagName).then(() => undefined);
|
|
57
|
-
}
|
package/src/core/permissions.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { Bridge, BridgeCapability } from "@aiworkbench/vibe-types";
|
|
2
|
-
|
|
3
|
-
function createDeniedProxy(capability: string): unknown {
|
|
4
|
-
return new Proxy(
|
|
5
|
-
{},
|
|
6
|
-
{
|
|
7
|
-
get(_target, prop) {
|
|
8
|
-
if (typeof prop === "symbol") return undefined;
|
|
9
|
-
throw new Error(
|
|
10
|
-
`Access denied: ${capability}.${prop} is not permitted. Add '${capability}' to your manifest.json permissions.`,
|
|
11
|
-
);
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function filterByPermissions(
|
|
18
|
-
bridge: Bridge,
|
|
19
|
-
permissions: BridgeCapability[],
|
|
20
|
-
): Bridge {
|
|
21
|
-
const allCapabilities: BridgeCapability[] = [
|
|
22
|
-
"auth",
|
|
23
|
-
"api",
|
|
24
|
-
"navigation",
|
|
25
|
-
"theme",
|
|
26
|
-
"toast",
|
|
27
|
-
"storage",
|
|
28
|
-
"events",
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
const filtered = {} as Record<string, unknown>;
|
|
32
|
-
for (const cap of allCapabilities) {
|
|
33
|
-
if (permissions.includes(cap)) {
|
|
34
|
-
filtered[cap] = bridge[cap];
|
|
35
|
-
} else {
|
|
36
|
-
filtered[cap] = createDeniedProxy(cap);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return filtered as unknown as Bridge;
|
|
41
|
-
}
|
package/src/core/types.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Bridge,
|
|
3
|
-
VibeManifest,
|
|
4
|
-
AuthBridge,
|
|
5
|
-
ApiBridge,
|
|
6
|
-
NavigationBridge,
|
|
7
|
-
ThemeBridge,
|
|
8
|
-
ToastBridge,
|
|
9
|
-
StorageBridge,
|
|
10
|
-
EventsBridge,
|
|
11
|
-
} from "@aiworkbench/vibe-types";
|
|
12
|
-
import type { ReactNode, CSSProperties } from "react";
|
|
13
|
-
|
|
14
|
-
export interface VibeHostProps {
|
|
15
|
-
/** The mini-app manifest describing its id, permissions, etc. */
|
|
16
|
-
manifest: VibeManifest;
|
|
17
|
-
/** URL to the built JS bundle for the mini-app. */
|
|
18
|
-
src: string;
|
|
19
|
-
/** Shown while the script loads and custom element registers. */
|
|
20
|
-
fallback?: ReactNode;
|
|
21
|
-
/** Shown when the script fails to load or the element fails to mount. */
|
|
22
|
-
errorFallback?: (error: Error) => ReactNode;
|
|
23
|
-
/** Optional overrides per capability adapter. */
|
|
24
|
-
adapters?: Partial<AdapterOverrides>;
|
|
25
|
-
/** Fires when the custom element is mounted and bridge is injected. */
|
|
26
|
-
onReady?: () => void;
|
|
27
|
-
/** Fires on load or mount failure. */
|
|
28
|
-
onError?: (error: Error) => void;
|
|
29
|
-
className?: string;
|
|
30
|
-
style?: CSSProperties;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface AdapterOverrides {
|
|
34
|
-
auth: AuthBridge;
|
|
35
|
-
api: ApiBridge;
|
|
36
|
-
navigation: NavigationBridge;
|
|
37
|
-
theme: ThemeBridge;
|
|
38
|
-
toast: ToastBridge;
|
|
39
|
-
storage: StorageBridge;
|
|
40
|
-
events: EventsBridge;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type LoaderStatus = "loading" | "ready" | "error";
|
|
44
|
-
|
|
45
|
-
export interface UseVibeLoaderResult {
|
|
46
|
-
status: LoaderStatus;
|
|
47
|
-
error: Error | null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface UseVibeBridgeOptions {
|
|
51
|
-
appId: string;
|
|
52
|
-
permissions: Bridge extends infer B ? (keyof B)[] : never;
|
|
53
|
-
overrides?: Partial<AdapterOverrides>;
|
|
54
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
import type { Bridge, BridgeCapability } from "@aiworkbench/vibe-types";
|
|
3
|
-
import { useSession } from "next-auth/react";
|
|
4
|
-
import { useRouter } from "next/navigation";
|
|
5
|
-
import { toast } from "sonner";
|
|
6
|
-
import { createAuthAdapter } from "../adapters/auth";
|
|
7
|
-
import { createApiAdapter } from "../adapters/api";
|
|
8
|
-
import { createNavigationAdapter } from "../adapters/navigation";
|
|
9
|
-
import { createThemeAdapter } from "../adapters/theme";
|
|
10
|
-
import { createToastAdapter } from "../adapters/toast";
|
|
11
|
-
import { createStorageAdapter } from "../adapters/storage";
|
|
12
|
-
import { createEventsAdapter } from "../adapters/events";
|
|
13
|
-
import { filterByPermissions } from "../core/permissions";
|
|
14
|
-
import type { AdapterOverrides } from "../core/types";
|
|
15
|
-
|
|
16
|
-
export interface UseVibeBridgeOptions {
|
|
17
|
-
appId: string;
|
|
18
|
-
permissions: BridgeCapability[];
|
|
19
|
-
overrides?: Partial<AdapterOverrides>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function useVibeBridge(options: UseVibeBridgeOptions): Bridge {
|
|
23
|
-
const { appId, permissions, overrides } = options;
|
|
24
|
-
const { data: session } = useSession();
|
|
25
|
-
const router = useRouter();
|
|
26
|
-
|
|
27
|
-
return useMemo(() => {
|
|
28
|
-
const authAdapter =
|
|
29
|
-
overrides?.auth ?? createAuthAdapter({ session: session ?? null });
|
|
30
|
-
|
|
31
|
-
const fullBridge: Bridge = {
|
|
32
|
-
auth: authAdapter,
|
|
33
|
-
api:
|
|
34
|
-
overrides?.api ??
|
|
35
|
-
createApiAdapter({ getToken: () => authAdapter.getToken() }),
|
|
36
|
-
navigation:
|
|
37
|
-
overrides?.navigation ??
|
|
38
|
-
createNavigationAdapter({ push: (path) => router.push(path) }),
|
|
39
|
-
theme: overrides?.theme ?? createThemeAdapter(),
|
|
40
|
-
toast:
|
|
41
|
-
overrides?.toast ??
|
|
42
|
-
createToastAdapter({ toast }),
|
|
43
|
-
storage: overrides?.storage ?? createStorageAdapter(appId),
|
|
44
|
-
events: overrides?.events ?? createEventsAdapter(appId),
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
return filterByPermissions(fullBridge, permissions);
|
|
48
|
-
}, [session, router, appId, permissions, overrides]);
|
|
49
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
2
|
-
import { onEvent, type VibeEventDetail } from "../core/event-bus";
|
|
3
|
-
|
|
4
|
-
export function useVibeEvents(
|
|
5
|
-
event: string,
|
|
6
|
-
handler: (payload: Record<string, unknown>, sourceAppId: string) => void,
|
|
7
|
-
): void {
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
return onEvent(event, (detail: VibeEventDetail) => {
|
|
10
|
-
handler(detail.payload, detail.sourceAppId);
|
|
11
|
-
});
|
|
12
|
-
}, [event, handler]);
|
|
13
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import { loadScript, waitForElement } from "../core/loader";
|
|
3
|
-
import type { LoaderStatus, UseVibeLoaderResult } from "../core/types";
|
|
4
|
-
|
|
5
|
-
export function useVibeLoader(
|
|
6
|
-
src: string,
|
|
7
|
-
tagName: string,
|
|
8
|
-
): UseVibeLoaderResult {
|
|
9
|
-
const [status, setStatus] = useState<LoaderStatus>("loading");
|
|
10
|
-
const [error, setError] = useState<Error | null>(null);
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
let cancelled = false;
|
|
14
|
-
|
|
15
|
-
setStatus("loading");
|
|
16
|
-
setError(null);
|
|
17
|
-
|
|
18
|
-
loadScript(src)
|
|
19
|
-
.then(() => waitForElement(tagName))
|
|
20
|
-
.then(() => {
|
|
21
|
-
if (!cancelled) {
|
|
22
|
-
setStatus("ready");
|
|
23
|
-
}
|
|
24
|
-
})
|
|
25
|
-
.catch((err: unknown) => {
|
|
26
|
-
if (!cancelled) {
|
|
27
|
-
const e =
|
|
28
|
-
err instanceof Error
|
|
29
|
-
? err
|
|
30
|
-
: new Error(`Failed to load vibe app: ${tagName}`);
|
|
31
|
-
setError(e);
|
|
32
|
-
setStatus("error");
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return () => {
|
|
37
|
-
cancelled = true;
|
|
38
|
-
};
|
|
39
|
-
}, [src, tagName]);
|
|
40
|
-
|
|
41
|
-
return { status, error };
|
|
42
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// Components
|
|
2
|
-
export { VibeHost } from "./components/VibeHost";
|
|
3
|
-
export { VibeErrorBoundary } from "./components/VibeErrorBoundary";
|
|
4
|
-
|
|
5
|
-
// Hooks
|
|
6
|
-
export { useVibeBridge } from "./hooks/useVibeBridge";
|
|
7
|
-
export { useVibeLoader } from "./hooks/useVibeLoader";
|
|
8
|
-
export { useVibeEvents } from "./hooks/useVibeEvents";
|
|
9
|
-
|
|
10
|
-
// Adapter factories
|
|
11
|
-
export { createAuthAdapter } from "./adapters/auth";
|
|
12
|
-
export { createApiAdapter } from "./adapters/api";
|
|
13
|
-
export { createNavigationAdapter } from "./adapters/navigation";
|
|
14
|
-
export { createThemeAdapter } from "./adapters/theme";
|
|
15
|
-
export { createToastAdapter } from "./adapters/toast";
|
|
16
|
-
export { createStorageAdapter } from "./adapters/storage";
|
|
17
|
-
export { createEventsAdapter } from "./adapters/events";
|
|
18
|
-
|
|
19
|
-
// Core utilities
|
|
20
|
-
export { filterByPermissions } from "./core/permissions";
|
|
21
|
-
export { getEventBus, emitEvent } from "./core/event-bus";
|
|
22
|
-
|
|
23
|
-
// Types
|
|
24
|
-
export type {
|
|
25
|
-
VibeHostProps,
|
|
26
|
-
AdapterOverrides,
|
|
27
|
-
LoaderStatus,
|
|
28
|
-
UseVibeLoaderResult,
|
|
29
|
-
} from "./core/types";
|
|
30
|
-
export type { AuthAdapterDeps } from "./adapters/auth";
|
|
31
|
-
export type { ApiAdapterDeps } from "./adapters/api";
|
|
32
|
-
export type { NavigationAdapterDeps } from "./adapters/navigation";
|
|
33
|
-
export type { ToastAdapterDeps } from "./adapters/toast";
|