@nominalso/vibe-bridge 0.5.0 → 0.6.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/AGENTS.md +13 -4
- package/README.md +45 -7
- package/dist/chunk-AH2BCQVB.js +405 -0
- package/dist/chunk-AH2BCQVB.js.map +1 -0
- package/dist/index.browser.cjs +1211 -0
- package/dist/index.browser.cjs.map +1 -0
- package/dist/index.browser.js +1172 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.cjs +198 -46
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +94 -3782
- package/dist/index.d.ts +94 -3782
- package/dist/index.js +176 -403
- package/dist/index.js.map +1 -0
- package/dist/index.server.cjs +448 -0
- package/dist/index.server.cjs.map +1 -0
- package/dist/index.server.d.cts +51 -0
- package/dist/index.server.d.ts +51 -0
- package/dist/index.server.js +84 -0
- package/dist/index.server.js.map +1 -0
- package/dist/version-MpAmSu0t.d.cts +3819 -0
- package/dist/version-MpAmSu0t.d.ts +3819 -0
- package/docs/getting-started.md +3 -1
- package/llms.txt +3 -1
- package/package.json +27 -30
package/dist/index.cjs
CHANGED
|
@@ -28,17 +28,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
|
|
30
30
|
// src/index.ts
|
|
31
|
-
var
|
|
32
|
-
__export(
|
|
33
|
-
BRIDGE_VERSION: () =>
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
BRIDGE_VERSION: () => BRIDGE_VERSION,
|
|
34
34
|
BridgeError: () => BridgeError,
|
|
35
35
|
HttpBridgeError: () => HttpBridgeError,
|
|
36
36
|
VibeAppBridge: () => VibeAppBridge
|
|
37
37
|
});
|
|
38
|
-
module.exports = __toCommonJS(
|
|
39
|
-
|
|
40
|
-
// package.json
|
|
41
|
-
var version = "0.5.0";
|
|
38
|
+
module.exports = __toCommonJS(src_exports);
|
|
42
39
|
|
|
43
40
|
// ../protocol-types/dist/index.js
|
|
44
41
|
function normalizeSubroute(subroute) {
|
|
@@ -69,20 +66,46 @@ function isBridgeMessage(data) {
|
|
|
69
66
|
if (CORRELATED_KINDS_SET.has(d.kind) && typeof d.requestId !== "string") return false;
|
|
70
67
|
return true;
|
|
71
68
|
}
|
|
69
|
+
var BRIDGE_ERROR_BRAND = /* @__PURE__ */ Symbol.for("@nominalso/vibe-bridge:BridgeError");
|
|
70
|
+
var HTTP_BRIDGE_ERROR_BRAND = /* @__PURE__ */ Symbol.for("@nominalso/vibe-bridge:HttpBridgeError");
|
|
72
71
|
var BridgeError = class extends Error {
|
|
73
72
|
constructor(code, message) {
|
|
74
73
|
super(message);
|
|
75
74
|
this.code = code;
|
|
76
75
|
this.name = "BridgeError";
|
|
77
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Cross-realm-safe type guard. Prefer this over `instanceof BridgeError` when
|
|
79
|
+
* an error may have been thrown by a differently-built copy of the SDK (e.g.
|
|
80
|
+
* server bundle vs client bundle) — it matches on a process-global brand
|
|
81
|
+
* rather than class identity.
|
|
82
|
+
*/
|
|
83
|
+
static isBridgeError(err) {
|
|
84
|
+
return typeof err === "object" && err !== null && err[BRIDGE_ERROR_BRAND] === true;
|
|
85
|
+
}
|
|
78
86
|
};
|
|
87
|
+
Object.defineProperty(BridgeError.prototype, BRIDGE_ERROR_BRAND, {
|
|
88
|
+
value: true,
|
|
89
|
+
enumerable: false
|
|
90
|
+
});
|
|
79
91
|
var HttpBridgeError = class extends BridgeError {
|
|
80
92
|
constructor(status, message) {
|
|
81
93
|
super("REQUEST_FAILED", message);
|
|
82
94
|
this.status = status;
|
|
83
95
|
this.name = "HttpBridgeError";
|
|
84
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Cross-realm-safe type guard (see {@link BridgeError.isBridgeError}). Returns
|
|
99
|
+
* `true` only for `HttpBridgeError` instances, not plain `BridgeError`s.
|
|
100
|
+
*/
|
|
101
|
+
static isHttpBridgeError(err) {
|
|
102
|
+
return typeof err === "object" && err !== null && err[HTTP_BRIDGE_ERROR_BRAND] === true;
|
|
103
|
+
}
|
|
85
104
|
};
|
|
105
|
+
Object.defineProperty(HttpBridgeError.prototype, HTTP_BRIDGE_ERROR_BRAND, {
|
|
106
|
+
value: true,
|
|
107
|
+
enumerable: false
|
|
108
|
+
});
|
|
86
109
|
function isOriginPattern(entry) {
|
|
87
110
|
return entry.includes("*");
|
|
88
111
|
}
|
|
@@ -111,8 +134,11 @@ function isOriginAllowed(origin, allowlist, { allowPatterns }) {
|
|
|
111
134
|
return false;
|
|
112
135
|
}
|
|
113
136
|
|
|
114
|
-
//
|
|
115
|
-
var
|
|
137
|
+
// package.json
|
|
138
|
+
var version = "0.6.0";
|
|
139
|
+
|
|
140
|
+
// src/version.ts
|
|
141
|
+
var BRIDGE_VERSION = version;
|
|
116
142
|
|
|
117
143
|
// src/data-methods.ts
|
|
118
144
|
var BridgeDataMethods = class {
|
|
@@ -428,13 +454,23 @@ function resolveRequestTimeout(type, requestTimeout) {
|
|
|
428
454
|
|
|
429
455
|
// src/VibeAppBridge.ts
|
|
430
456
|
function resolvePosthog(mod) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return
|
|
457
|
+
const hasInit = (o) => typeof o?.init === "function";
|
|
458
|
+
const d1 = mod?.default;
|
|
459
|
+
if (hasInit(d1)) return d1;
|
|
460
|
+
const d2 = d1?.default;
|
|
461
|
+
if (hasInit(d2)) return d2;
|
|
462
|
+
if (hasInit(mod)) return mod;
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
function serverEnvError(method) {
|
|
466
|
+
return new BridgeError(
|
|
467
|
+
"SERVER_ENVIRONMENT",
|
|
468
|
+
`VibeAppBridge.${method} was called outside a browser (no \`window\`). The bridge only runs in the browser \u2014 call it from a client component or effect, not during SSR, in a React Server Component, or in a Worker.`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
function assertBrowser(method) {
|
|
472
|
+
if (typeof window === "undefined") throw serverEnvError(method);
|
|
436
473
|
}
|
|
437
|
-
var posthog = resolvePosthog(import_posthog_js.default);
|
|
438
474
|
var PARENT_ORIGIN_OVERRIDE_KEY = "__NOM_VIBE_PARENT_ORIGIN__";
|
|
439
475
|
var PREVIEW_HOST_RE = /(?:^|\.)(?:vercel\.app|lovable\.app|lovable\.dev|lovableproject\.com)$|^(?:localhost|127\.0\.0\.1)$/;
|
|
440
476
|
function isPreviewSelfHost(hostname) {
|
|
@@ -494,9 +530,22 @@ function resolveOriginPolicy(configured) {
|
|
|
494
530
|
return { targetOrigin, isTrusted };
|
|
495
531
|
}
|
|
496
532
|
var VibeAppBridge = class extends BridgeDataMethods {
|
|
497
|
-
|
|
533
|
+
/**
|
|
534
|
+
* Stores options only — **pure and side-effect-free**. No `window`,
|
|
535
|
+
* `document`, `history`, `postMessage`, `crypto`, or `posthog-js` access
|
|
536
|
+
* happens here, so constructing a bridge is safe during SSR / in a Server
|
|
537
|
+
* Component / in a Worker. Browser wiring is deferred to {@link connect} /
|
|
538
|
+
* {@link attach}.
|
|
539
|
+
*/
|
|
540
|
+
constructor(options = {}) {
|
|
498
541
|
super();
|
|
542
|
+
/** Concrete origin to `postMessage` to, or `null` until resolved at {@link attach}. */
|
|
543
|
+
this.targetOrigin = null;
|
|
544
|
+
/** Validates an inbound `event.origin`; rejects everything until {@link attach}. */
|
|
545
|
+
this.isTrustedOrigin = () => false;
|
|
499
546
|
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
547
|
+
/** Whether {@link attach} has installed the message listener + resolved origin. */
|
|
548
|
+
this.attached = false;
|
|
500
549
|
this.context = null;
|
|
501
550
|
/**
|
|
502
551
|
* Set once {@link initPostHog} has successfully called `posthog.init()`.
|
|
@@ -504,6 +553,16 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
504
553
|
* recording is actually live.
|
|
505
554
|
*/
|
|
506
555
|
this.posthogInitialized = false;
|
|
556
|
+
/** The lazily-imported `posthog-js` singleton, once recording is initialized. */
|
|
557
|
+
this.posthogInstance = null;
|
|
558
|
+
/**
|
|
559
|
+
* Set for the duration of an in-flight {@link initPostHog} (which is async — it
|
|
560
|
+
* awaits `import('posthog-js')`). Serializes initialization so two overlapping
|
|
561
|
+
* `POSTHOG_PUSH`es can't both call `posthog.init()`.
|
|
562
|
+
*/
|
|
563
|
+
this.posthogIniting = false;
|
|
564
|
+
/** So the "posthog-js not installed" warning is logged at most once. */
|
|
565
|
+
this.posthogUnavailableWarned = false;
|
|
507
566
|
this.connectPromise = null;
|
|
508
567
|
this.connectPollInterval = null;
|
|
509
568
|
this.connectTimeout = null;
|
|
@@ -544,8 +603,12 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
544
603
|
},
|
|
545
604
|
// Persistent (not nulled after connect): the host delivers PostHog once at
|
|
546
605
|
// connect via its own push, and initPostHog is idempotent, so this can run
|
|
547
|
-
// whenever the message arrives, independent of how connect() resolved.
|
|
548
|
-
|
|
606
|
+
// whenever the message arrives, independent of how connect() resolved. Fire
|
|
607
|
+
// and forget — initPostHog is async (lazy-imports posthog-js) and swallows
|
|
608
|
+
// its own errors.
|
|
609
|
+
POSTHOG_PUSH: (payload) => {
|
|
610
|
+
void this.initPostHog(payload);
|
|
611
|
+
},
|
|
549
612
|
// Persistent: the host pushes auth changes (logout, identity/tenant
|
|
550
613
|
// switch) at any time after connect.
|
|
551
614
|
AUTH_CHANGED: (payload) => this.authChangeCallback?.(payload),
|
|
@@ -575,42 +638,72 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
575
638
|
this.ui = {
|
|
576
639
|
/** Collapse or expand the host's side navigation. */
|
|
577
640
|
setSideNav: (payload) => {
|
|
641
|
+
assertBrowser("ui.setSideNav()");
|
|
578
642
|
this.sendCommand("SET_SIDE_NAV", payload);
|
|
579
643
|
},
|
|
580
644
|
/** Switch the host to a different subsidiary. */
|
|
581
645
|
switchSubsidiary: (subsidiaryId) => {
|
|
646
|
+
assertBrowser("ui.switchSubsidiary()");
|
|
582
647
|
this.sendCommand("SWITCH_SUBSIDIARY", { subsidiaryId });
|
|
583
648
|
}
|
|
584
649
|
};
|
|
585
|
-
|
|
586
|
-
this.
|
|
587
|
-
this.isTrustedOrigin = policy.isTrusted;
|
|
588
|
-
this.requestTimeout = requestTimeout;
|
|
650
|
+
this.parentOriginOption = options.parentOrigin;
|
|
651
|
+
this.requestTimeout = options.requestTimeout;
|
|
589
652
|
this.boundHandleMessage = this.handleMessage.bind(this);
|
|
590
|
-
window.addEventListener("message", this.boundHandleMessage);
|
|
591
653
|
}
|
|
592
654
|
dispatchPush(msg) {
|
|
593
655
|
const handler = this.pushHandlers[msg.type];
|
|
594
656
|
handler(msg.payload);
|
|
595
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Installs the browser wiring: resolves the parent-origin policy and starts
|
|
660
|
+
* listening for host `postMessage`s. **Idempotent** and safe to call before
|
|
661
|
+
* {@link connect} if you want the bridge receiving pushes early; {@link
|
|
662
|
+
* connect} calls it for you, so most apps never call this directly.
|
|
663
|
+
*
|
|
664
|
+
* @throws `BridgeError` code `'SERVER_ENVIRONMENT'` if called where there is no
|
|
665
|
+
* `window`.
|
|
666
|
+
*/
|
|
667
|
+
attach() {
|
|
668
|
+
assertBrowser("attach()");
|
|
669
|
+
if (this.attached) return;
|
|
670
|
+
const policy = resolveOriginPolicy(this.parentOriginOption);
|
|
671
|
+
this.targetOrigin = policy.targetOrigin;
|
|
672
|
+
this.isTrustedOrigin = policy.isTrusted;
|
|
673
|
+
window.addEventListener("message", this.boundHandleMessage);
|
|
674
|
+
this.attached = true;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Lazily installs the browser wiring on first transport use. Keeps the
|
|
678
|
+
* constructor pure while ensuring that any method reaching the wire (a data
|
|
679
|
+
* `request`, a fire-and-forget command) works without an explicit prior
|
|
680
|
+
* `attach()`/`connect()` — attach stays idempotent, so `connect()` is
|
|
681
|
+
* unaffected.
|
|
682
|
+
*/
|
|
683
|
+
ensureAttached() {
|
|
684
|
+
if (!this.attached) this.attach();
|
|
685
|
+
}
|
|
596
686
|
/**
|
|
597
687
|
* Connects to the host and returns the tenant/user {@link ContextPayload}.
|
|
598
688
|
* **Call this once on app init and await it before any other method** — data
|
|
599
689
|
* methods, `upload()`, and subroute reporting all require an established
|
|
600
690
|
* connection. Concurrent calls return the same promise.
|
|
601
691
|
*
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
* `
|
|
692
|
+
* First {@link attach | attaches} the browser wiring (message listener +
|
|
693
|
+
* origin resolution), then sends a `CONNECT` handshake, re-sending every 500ms
|
|
694
|
+
* until the host replies by pushing the context (so it works even if the host
|
|
695
|
+
* mounts after the iframe loads). Context is only ever delivered by push.
|
|
696
|
+
* Rejects with `Bridge connect timed out` after 10 seconds if no context
|
|
697
|
+
* arrives (usually a `parentOrigin` mismatch or the host hasn't mounted).
|
|
607
698
|
*
|
|
608
699
|
* After connecting, if the context includes an initial subroute (e.g. from
|
|
609
700
|
* a deep link), the bridge navigates to it and starts auto-tracking
|
|
610
701
|
* internal navigation via the history API.
|
|
611
702
|
*
|
|
612
703
|
* @returns The tenant/user/subsidiary context for this app session.
|
|
613
|
-
* @throws
|
|
704
|
+
* @throws Rejects with `BridgeError` code `'SERVER_ENVIRONMENT'` if called
|
|
705
|
+
* where there is no `window`, `'PARENT_ORIGIN_UNRESOLVED'` if no embedding
|
|
706
|
+
* origin could be resolved, or `'TIMEOUT'` if no context arrives in 10s.
|
|
614
707
|
* @example
|
|
615
708
|
* ```ts
|
|
616
709
|
* const ctx = await bridge.connect()
|
|
@@ -618,8 +711,10 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
618
711
|
* ```
|
|
619
712
|
*/
|
|
620
713
|
connect() {
|
|
714
|
+
if (typeof window === "undefined") return Promise.reject(serverEnvError("connect()"));
|
|
621
715
|
if (this.context) return Promise.resolve(this.context);
|
|
622
716
|
if (this.connectPromise) return this.connectPromise;
|
|
717
|
+
this.attach();
|
|
623
718
|
if (this.targetOrigin === null) {
|
|
624
719
|
return Promise.reject(
|
|
625
720
|
new BridgeError(
|
|
@@ -653,7 +748,7 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
653
748
|
this.onContextReceived = null;
|
|
654
749
|
this.rejectConnect = null;
|
|
655
750
|
console.log(
|
|
656
|
-
`[vibe-bridge] Connected \u2014 bridge: ${
|
|
751
|
+
`[vibe-bridge] Connected \u2014 bridge: ${BRIDGE_VERSION}, host: ${ctx.hostVersion}, tenant: ${ctx.tenant}, user: ${ctx.user.displayName}`
|
|
657
752
|
);
|
|
658
753
|
const initialSubroute = ctx.subroute ? normalizeSubroute(ctx.subroute) : null;
|
|
659
754
|
if (initialSubroute && initialSubroute !== "/") {
|
|
@@ -670,7 +765,7 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
670
765
|
resolve(ctx);
|
|
671
766
|
};
|
|
672
767
|
const sendConnect = () => {
|
|
673
|
-
this.sendCommand("CONNECT", { bridgeVersion:
|
|
768
|
+
this.sendCommand("CONNECT", { bridgeVersion: BRIDGE_VERSION });
|
|
674
769
|
};
|
|
675
770
|
sendConnect();
|
|
676
771
|
this.connectPollInterval = setInterval(sendConnect, CONNECT_POLL_MS);
|
|
@@ -683,6 +778,7 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
683
778
|
* Use this for hash-based routers or non-standard navigation patterns.
|
|
684
779
|
*/
|
|
685
780
|
reportSubroute(subroute, options) {
|
|
781
|
+
assertBrowser("reportSubroute()");
|
|
686
782
|
const normalized = normalizeSubroute(subroute);
|
|
687
783
|
if (normalized === null) return;
|
|
688
784
|
this.lastReportedSubroute = normalized;
|
|
@@ -693,6 +789,8 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
693
789
|
* If no callback is registered, the SDK uses `history.pushState` + a
|
|
694
790
|
* `popstate` event, which works for most SPA routers automatically.
|
|
695
791
|
* Returns an unsubscribe function.
|
|
792
|
+
*
|
|
793
|
+
* Pure — only stores the callback, so it is safe to call before {@link connect}.
|
|
696
794
|
*/
|
|
697
795
|
onSubrouteRequest(callback) {
|
|
698
796
|
this.subrouteCallback = callback;
|
|
@@ -707,6 +805,8 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
707
805
|
* returns. Returns an unsubscribe function. Single subscriber: a second call
|
|
708
806
|
* replaces the first. Wire it **before {@link connect}** (the bridge warns at
|
|
709
807
|
* connect if it isn't).
|
|
808
|
+
*
|
|
809
|
+
* Pure — only stores the callback, so it is safe to call before {@link connect}.
|
|
710
810
|
*/
|
|
711
811
|
onContextChange(callback) {
|
|
712
812
|
this.contextChangeCallback = callback;
|
|
@@ -723,7 +823,7 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
723
823
|
*
|
|
724
824
|
* **Wire this before {@link connect}** — without it the embedded app won't
|
|
725
825
|
* sign out when the user logs out of Nominal (the bridge warns at connect if
|
|
726
|
-
* it isn't wired).
|
|
826
|
+
* it isn't wired). Pure — only stores the callback.
|
|
727
827
|
*/
|
|
728
828
|
onAuthChange(callback) {
|
|
729
829
|
this.authChangeCallback = callback;
|
|
@@ -769,6 +869,7 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
769
869
|
* ```
|
|
770
870
|
*/
|
|
771
871
|
navigate(path, action = "push") {
|
|
872
|
+
assertBrowser("navigate()");
|
|
772
873
|
this.sendCommand("NAVIGATE", { path, action });
|
|
773
874
|
}
|
|
774
875
|
/**
|
|
@@ -782,10 +883,12 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
782
883
|
* ```
|
|
783
884
|
*/
|
|
784
885
|
navigateTo(route, routeParams, action = "push") {
|
|
886
|
+
assertBrowser("navigateTo()");
|
|
785
887
|
this.sendCommand("NAVIGATE", { route, routeParams, action });
|
|
786
888
|
}
|
|
787
889
|
/** Refreshes the host's current route (re-fetches its server data). Fire-and-forget. */
|
|
788
890
|
refresh() {
|
|
891
|
+
assertBrowser("refresh()");
|
|
789
892
|
this.sendCommand("NAVIGATE", { action: "refresh" });
|
|
790
893
|
}
|
|
791
894
|
/**
|
|
@@ -807,8 +910,9 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
807
910
|
*
|
|
808
911
|
* **No-op until {@link connect} has resolved with PostHog enabled by the
|
|
809
912
|
* host** — if the host didn't send a `posthog` block (older host, or recording
|
|
810
|
-
* disabled for this env/app),
|
|
811
|
-
*
|
|
913
|
+
* disabled for this env/app), or `posthog-js` isn't installed (it is an
|
|
914
|
+
* optional dependency), nothing is sent. App authors don't import `posthog-js`
|
|
915
|
+
* themselves; this is the supported event surface.
|
|
812
916
|
*
|
|
813
917
|
* The event is sent **directly** from the iframe's own PostHog instance (only
|
|
814
918
|
* the session *recording* is forwarded to the parent), so it carries the
|
|
@@ -825,20 +929,33 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
825
929
|
* ```
|
|
826
930
|
*/
|
|
827
931
|
track(event, properties) {
|
|
828
|
-
|
|
829
|
-
|
|
932
|
+
assertBrowser("track()");
|
|
933
|
+
if (!this.posthogInitialized || !this.posthogInstance) return;
|
|
934
|
+
this.posthogInstance.capture(event, properties);
|
|
830
935
|
}
|
|
936
|
+
/**
|
|
937
|
+
* Tears down the bridge: stops the connect handshake, removes the message
|
|
938
|
+
* listener, restores patched history methods, and rejects any in-flight calls
|
|
939
|
+
* with a `'DESTROYED'` error. **Idempotent and safe on the server** — calling
|
|
940
|
+
* it when {@link connect}/{@link attach} never ran (or where there is no
|
|
941
|
+
* `window`) is a no-op, so it is safe as a React effect cleanup.
|
|
942
|
+
*/
|
|
831
943
|
destroy() {
|
|
832
944
|
this.teardownNavigationTracking();
|
|
833
945
|
this.rejectConnect?.(new BridgeError("DESTROYED", "Bridge destroyed"));
|
|
834
946
|
this.stopConnectPolling();
|
|
835
|
-
window
|
|
947
|
+
if (this.attached && typeof window !== "undefined") {
|
|
948
|
+
window.removeEventListener("message", this.boundHandleMessage);
|
|
949
|
+
}
|
|
950
|
+
this.attached = false;
|
|
836
951
|
for (const pending of this.pendingRequests.values()) {
|
|
837
952
|
pending.reject(new BridgeError("DESTROYED", "Bridge destroyed"));
|
|
838
953
|
}
|
|
839
954
|
this.pendingRequests.clear();
|
|
840
955
|
this.context = null;
|
|
841
956
|
this.posthogInitialized = false;
|
|
957
|
+
this.posthogInstance = null;
|
|
958
|
+
this.posthogIniting = false;
|
|
842
959
|
this.connectPromise = null;
|
|
843
960
|
this.onContextReceived = null;
|
|
844
961
|
this.rejectConnect = null;
|
|
@@ -857,23 +974,34 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
857
974
|
this.connectTimeout = null;
|
|
858
975
|
}
|
|
859
976
|
}
|
|
977
|
+
warnPosthogUnavailable() {
|
|
978
|
+
if (this.posthogUnavailableWarned) return;
|
|
979
|
+
this.posthogUnavailableWarned = true;
|
|
980
|
+
console.warn(
|
|
981
|
+
"[vibe-bridge] The host enabled session recording but `posthog-js` is not installed (it is an optional dependency). Recording and track() are disabled. Run `npm i posthog-js` to enable them."
|
|
982
|
+
);
|
|
983
|
+
}
|
|
860
984
|
/**
|
|
861
985
|
* Initializes PostHog session recording + analytics from the host-supplied
|
|
862
986
|
* config carried by a one-time `POSTHOG_PUSH`, **only when the host opted in**
|
|
863
987
|
* (`config.enabled`).
|
|
864
988
|
*
|
|
989
|
+
* `posthog-js` is an **optional dependency**, loaded lazily via `await
|
|
990
|
+
* import('posthog-js')` the first time recording is enabled — so it stays out
|
|
991
|
+
* of the import graph (and out of any server bundle) for apps that never
|
|
992
|
+
* record. If it isn't installed, this warns once and no-ops (never throws).
|
|
993
|
+
*
|
|
865
994
|
* The host is the single source of truth: when it sends no `POSTHOG_PUSH`
|
|
866
995
|
* (recording disabled for this env/app) or `enabled: false`, this does
|
|
867
|
-
* nothing
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
* in try/catch so a duplicate push or a PostHog failure is harmless.
|
|
996
|
+
* nothing. Cross-origin replay needs `recordCrossOriginIframes` on both sides
|
|
997
|
+
* so the parent receives the iframe's rrweb frames into one stitched recording,
|
|
998
|
+
* and `identify()` ties the iframe's recording + events to the same person as
|
|
999
|
+
* the host. Idempotent (the `posthogInitialized` guard) and wrapped in
|
|
1000
|
+
* try/catch so a duplicate push or a PostHog failure is harmless.
|
|
873
1001
|
*/
|
|
874
|
-
initPostHog(config) {
|
|
1002
|
+
async initPostHog(config) {
|
|
875
1003
|
if (!config.enabled) return;
|
|
876
|
-
if (this.posthogInitialized) return;
|
|
1004
|
+
if (this.posthogInitialized || this.posthogIniting) return;
|
|
877
1005
|
if (typeof window === "undefined") return;
|
|
878
1006
|
if (!config.token || !config.apiHost || !config.distinctId) {
|
|
879
1007
|
console.warn(
|
|
@@ -881,7 +1009,20 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
881
1009
|
);
|
|
882
1010
|
return;
|
|
883
1011
|
}
|
|
1012
|
+
this.posthogIniting = true;
|
|
884
1013
|
try {
|
|
1014
|
+
let posthog;
|
|
1015
|
+
try {
|
|
1016
|
+
posthog = resolvePosthog(await import("posthog-js"));
|
|
1017
|
+
} catch {
|
|
1018
|
+
this.warnPosthogUnavailable();
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
if (!posthog) {
|
|
1022
|
+
this.warnPosthogUnavailable();
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (!this.attached) return;
|
|
885
1026
|
posthog.init(config.token, {
|
|
886
1027
|
api_host: config.apiHost,
|
|
887
1028
|
// absolute ingestion host, NOT a relative /ingest proxy
|
|
@@ -904,16 +1045,19 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
904
1045
|
}
|
|
905
1046
|
});
|
|
906
1047
|
posthog.identify(config.distinctId);
|
|
1048
|
+
this.posthogInstance = posthog;
|
|
907
1049
|
this.posthogInitialized = true;
|
|
908
1050
|
console.log("[vibe-bridge] PostHog session recording initialized");
|
|
909
1051
|
} catch (err) {
|
|
910
1052
|
console.warn("[vibe-bridge] PostHog init failed \u2014 continuing without recording.", err);
|
|
1053
|
+
} finally {
|
|
1054
|
+
this.posthogIniting = false;
|
|
911
1055
|
}
|
|
912
1056
|
}
|
|
913
1057
|
/**
|
|
914
1058
|
* Monkey-patches `history.pushState` and `history.replaceState` to
|
|
915
1059
|
* auto-detect SPA navigation and report it to the host. Called after
|
|
916
|
-
* connect() resolves.
|
|
1060
|
+
* connect() resolves (so `window`/`history` are guaranteed present).
|
|
917
1061
|
*/
|
|
918
1062
|
setupNavigationTracking() {
|
|
919
1063
|
if (this.origPushState) return;
|
|
@@ -964,6 +1108,7 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
964
1108
|
this.sendCommand("REPORT_SUBROUTE", { subroute, replace });
|
|
965
1109
|
}
|
|
966
1110
|
sendCommand(type, payload) {
|
|
1111
|
+
this.ensureAttached();
|
|
967
1112
|
this.postToParent({
|
|
968
1113
|
__protocol: PROTOCOL_ID,
|
|
969
1114
|
__version: PROTOCOL_VERSION,
|
|
@@ -979,12 +1124,18 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
979
1124
|
* `onProgress` callback. `type` autocompletes to every operation name and
|
|
980
1125
|
* narrows `payload`/return to that operation's types.
|
|
981
1126
|
*
|
|
1127
|
+
* @throws Rejects with `BridgeError` code `'SERVER_ENVIRONMENT'` if called
|
|
1128
|
+
* where there is no `window`.
|
|
982
1129
|
* @example
|
|
983
1130
|
* ```ts
|
|
984
1131
|
* const accounts = await bridge.request('GET_ACCOUNTS', { query: { account_ids: ['acc-1'] } })
|
|
985
1132
|
* ```
|
|
986
1133
|
*/
|
|
987
1134
|
request(type, payload, onProgress) {
|
|
1135
|
+
if (typeof window === "undefined") {
|
|
1136
|
+
return Promise.reject(serverEnvError(`request(${String(type)})`));
|
|
1137
|
+
}
|
|
1138
|
+
this.ensureAttached();
|
|
988
1139
|
return new Promise((resolve, reject) => {
|
|
989
1140
|
const requestId = crypto.randomUUID();
|
|
990
1141
|
const timer = setTimeout(
|
|
@@ -1056,3 +1207,4 @@ var VibeAppBridge = class extends BridgeDataMethods {
|
|
|
1056
1207
|
HttpBridgeError,
|
|
1057
1208
|
VibeAppBridge
|
|
1058
1209
|
});
|
|
1210
|
+
//# sourceMappingURL=index.cjs.map
|