@zolomedia/bifrost-client 1.7.74

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.
Files changed (140) hide show
  1. package/L1_Foundation/L1_Foundation.js +13 -0
  2. package/L1_Foundation/bootstrap/bootstrap.js +11 -0
  3. package/L1_Foundation/bootstrap/bootstrap_hooks.js +123 -0
  4. package/L1_Foundation/bootstrap/bootstrap_index.js +15 -0
  5. package/L1_Foundation/bootstrap/bootstrap_logger.js +135 -0
  6. package/L1_Foundation/bootstrap/cdn_loader.js +217 -0
  7. package/L1_Foundation/bootstrap/module_registry.js +102 -0
  8. package/L1_Foundation/bootstrap/prism_loader.js +164 -0
  9. package/L1_Foundation/config/client_config.js +110 -0
  10. package/L1_Foundation/config/config.js +7 -0
  11. package/L1_Foundation/connection/connection.js +8 -0
  12. package/L1_Foundation/connection/websocket_connection.js +122 -0
  13. package/L1_Foundation/constants/bifrost_constants.js +284 -0
  14. package/L1_Foundation/constants/constants.js +7 -0
  15. package/L1_Foundation/logger/logger.js +10 -0
  16. package/L2_Handling/L2_Handling.js +15 -0
  17. package/L2_Handling/cache/cache.js +22 -0
  18. package/L2_Handling/cache/cache_constants.js +69 -0
  19. package/L2_Handling/cache/orchestration/cache_manager.js +299 -0
  20. package/L2_Handling/cache/orchestration/cache_orchestrator.js +260 -0
  21. package/L2_Handling/cache/orchestration/orchestration.js +12 -0
  22. package/L2_Handling/cache/storage/session_manager.js +289 -0
  23. package/L2_Handling/cache/storage/storage.js +10 -0
  24. package/L2_Handling/cache/storage/storage_manager.js +590 -0
  25. package/L2_Handling/display/composite/composite.js +13 -0
  26. package/L2_Handling/display/composite/dashboard_renderer.js +221 -0
  27. package/L2_Handling/display/composite/swiper_renderer.js +564 -0
  28. package/L2_Handling/display/composite/terminal_renderer.js +922 -0
  29. package/L2_Handling/display/composite/wizard_conditional_renderer.js +274 -0
  30. package/L2_Handling/display/display.js +30 -0
  31. package/L2_Handling/display/feedback/feedback.js +11 -0
  32. package/L2_Handling/display/feedback/progressbar_renderer.js +418 -0
  33. package/L2_Handling/display/feedback/spinner_renderer.js +246 -0
  34. package/L2_Handling/display/inputs/button_renderer.js +634 -0
  35. package/L2_Handling/display/inputs/form_renderer.js +583 -0
  36. package/L2_Handling/display/inputs/input_renderer.js +658 -0
  37. package/L2_Handling/display/inputs/inputs.js +12 -0
  38. package/L2_Handling/display/navigation/menu_renderer.js +206 -0
  39. package/L2_Handling/display/navigation/navigation.js +11 -0
  40. package/L2_Handling/display/navigation/navigation_renderer.js +703 -0
  41. package/L2_Handling/display/orchestration/orchestration.js +11 -0
  42. package/L2_Handling/display/orchestration/renderer.js +430 -0
  43. package/L2_Handling/display/orchestration/zdisplay_orchestrator.js +1759 -0
  44. package/L2_Handling/display/outputs/alert_renderer.js +161 -0
  45. package/L2_Handling/display/outputs/audio_renderer.js +94 -0
  46. package/L2_Handling/display/outputs/card_renderer.js +229 -0
  47. package/L2_Handling/display/outputs/code_renderer.js +66 -0
  48. package/L2_Handling/display/outputs/dl_renderer.js +131 -0
  49. package/L2_Handling/display/outputs/header_renderer.js +162 -0
  50. package/L2_Handling/display/outputs/icon_renderer.js +107 -0
  51. package/L2_Handling/display/outputs/image_renderer.js +145 -0
  52. package/L2_Handling/display/outputs/list_renderer.js +190 -0
  53. package/L2_Handling/display/outputs/outputs.js +19 -0
  54. package/L2_Handling/display/outputs/table_renderer.js +765 -0
  55. package/L2_Handling/display/outputs/text_renderer.js +818 -0
  56. package/L2_Handling/display/outputs/typography_renderer.js +293 -0
  57. package/L2_Handling/display/outputs/video_renderer.js +116 -0
  58. package/L2_Handling/display/primitives/document_structure_primitives.js +319 -0
  59. package/L2_Handling/display/primitives/form_primitives.js +526 -0
  60. package/L2_Handling/display/primitives/generic_containers.js +109 -0
  61. package/L2_Handling/display/primitives/interactive_primitives.js +305 -0
  62. package/L2_Handling/display/primitives/link_primitives.js +552 -0
  63. package/L2_Handling/display/primitives/lists_primitives.js +262 -0
  64. package/L2_Handling/display/primitives/media_primitives.js +383 -0
  65. package/L2_Handling/display/primitives/primitives.js +19 -0
  66. package/L2_Handling/display/primitives/semantic_element_primitive.js +226 -0
  67. package/L2_Handling/display/primitives/table_primitives.js +528 -0
  68. package/L2_Handling/display/primitives/typography_primitives.js +175 -0
  69. package/L2_Handling/display/specialized/input_request_renderer.js +467 -0
  70. package/L2_Handling/display/specialized/specialized.js +10 -0
  71. package/L2_Handling/hooks/hooks.js +9 -0
  72. package/L2_Handling/hooks/menu_integration.js +57 -0
  73. package/L2_Handling/hooks/widget_hook_manager.js +292 -0
  74. package/L2_Handling/message/message.js +8 -0
  75. package/L2_Handling/message/message_handler.js +701 -0
  76. package/L2_Handling/navigation/navigation.js +8 -0
  77. package/L2_Handling/navigation/navigation_manager.js +403 -0
  78. package/L2_Handling/zhooks/features/cache_live.js +287 -0
  79. package/L2_Handling/zhooks/features/crumbs_live.js +292 -0
  80. package/L2_Handling/zhooks/zhooks_manager.js +65 -0
  81. package/L2_Handling/zvaf/zvaf.js +8 -0
  82. package/L2_Handling/zvaf/zvaf_manager.js +334 -0
  83. package/L3_Abstraction/L3_Abstraction.js +12 -0
  84. package/L3_Abstraction/orchestrator/container_unwrapper.js +101 -0
  85. package/L3_Abstraction/orchestrator/group_renderer.js +698 -0
  86. package/L3_Abstraction/orchestrator/input_event_handler.js +797 -0
  87. package/L3_Abstraction/orchestrator/metadata_processor.js +249 -0
  88. package/L3_Abstraction/orchestrator/navbar_builder.js +201 -0
  89. package/L3_Abstraction/orchestrator/orchestrator.js +13 -0
  90. package/L3_Abstraction/orchestrator/wizard_gate_handler.js +360 -0
  91. package/L3_Abstraction/renderer/renderer.js +1 -0
  92. package/L3_Abstraction/session/session.js +1 -0
  93. package/L4_Orchestration/L4_Orchestration.js +11 -0
  94. package/L4_Orchestration/client/client.js +1 -0
  95. package/L4_Orchestration/facade/facade.js +9 -0
  96. package/L4_Orchestration/facade/manager_registry.js +118 -0
  97. package/L4_Orchestration/facade/renderer_registry.js +274 -0
  98. package/L4_Orchestration/lifecycle/asset_loader.js +255 -0
  99. package/L4_Orchestration/lifecycle/initializer.js +135 -0
  100. package/L4_Orchestration/lifecycle/lifecycle.js +8 -0
  101. package/L4_Orchestration/rendering/facade.js +94 -0
  102. package/L4_Orchestration/rendering/rendering.js +7 -0
  103. package/LICENSE +21 -0
  104. package/README.md +82 -0
  105. package/bifrost_client.js +204 -0
  106. package/bifrost_core.js +1686 -0
  107. package/docs/ARCHITECTURE.md +111 -0
  108. package/docs/PROTOCOL.md +106 -0
  109. package/docs/RENDERERS.md +101 -0
  110. package/docs/SECURITY.md +92 -0
  111. package/package.json +24 -0
  112. package/syntax/prism-zconfig.js +41 -0
  113. package/syntax/prism-zenv.js +69 -0
  114. package/syntax/prism-zolo-theme.css +288 -0
  115. package/syntax/prism-zolo.js +380 -0
  116. package/syntax/prism-zschema.js +38 -0
  117. package/syntax/prism-zspark.js +25 -0
  118. package/syntax/prism-zui.js +68 -0
  119. package/zSys/accessibility/accessibility.js +10 -0
  120. package/zSys/accessibility/emoji_accessibility.js +173 -0
  121. package/zSys/dom/block_utils.js +122 -0
  122. package/zSys/dom/container_utils.js +370 -0
  123. package/zSys/dom/dom.js +13 -0
  124. package/zSys/dom/dom_utils.js +328 -0
  125. package/zSys/dom/encoding_utils.js +117 -0
  126. package/zSys/dom/style_utils.js +71 -0
  127. package/zSys/errors/error_display.js +299 -0
  128. package/zSys/errors/errors.js +10 -0
  129. package/zSys/theme/color_utils.js +274 -0
  130. package/zSys/theme/dark_mode_utils.js +272 -0
  131. package/zSys/theme/size_utils.js +256 -0
  132. package/zSys/theme/spacing_utils.js +405 -0
  133. package/zSys/theme/theme.js +14 -0
  134. package/zSys/theme/zbase.css +1735 -0
  135. package/zSys/theme/zbase_inject.js +161 -0
  136. package/zSys/theme/ztheme_utils.js +305 -0
  137. package/zSys/validation/error_boundary.js +201 -0
  138. package/zSys/validation/validation.js +11 -0
  139. package/zSys/validation/validation_utils.js +238 -0
  140. package/zSys/zSys.js +14 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * L4_Orchestration/rendering/facade.js
3
+ *
4
+ * Rendering Facade
5
+ *
6
+ * Thin delegation layer that ensures orchestrator is loaded and routes
7
+ * rendering requests. Extracted from bifrost_client.js to reduce facade bloat.
8
+ *
9
+ * All methods are simple delegations to ZDisplayOrchestrator.
10
+ *
11
+ * Extracted from bifrost_client.js (Phase 5.1)
12
+ */
13
+
14
+ export class RenderingFacade {
15
+ constructor(client) {
16
+ this.client = client;
17
+ }
18
+
19
+ /**
20
+ * Render a complete block of YAML data
21
+ * @param {Object} blockData - Block data to render
22
+ */
23
+ async renderBlock(blockData) {
24
+ await this.client._ensureZDisplayOrchestrator();
25
+ return this.client.zDisplayOrchestrator.renderBlock(blockData);
26
+ }
27
+
28
+ /**
29
+ * Progressive chunk rendering (Terminal First philosophy)
30
+ * Appends chunks from backend as they arrive, stops at failed gates
31
+ * @param {Object} message - Chunk message from backend
32
+ */
33
+ async renderChunkProgressive(message) {
34
+ // Clear navigation timeout when first chunk arrives
35
+ if (this.client._navigationTimeout) {
36
+ clearTimeout(this.client._navigationTimeout);
37
+ this.client._navigationTimeout = null;
38
+ }
39
+
40
+ await this.client._ensureZDisplayOrchestrator();
41
+ return this.client.zDisplayOrchestrator.renderChunkProgressive(message);
42
+ }
43
+
44
+ /**
45
+ * Recursively render YAML items (handles nested structures like implicit wizards)
46
+ * @param {Object} data - YAML data to render
47
+ * @param {HTMLElement} parentElement - Parent element to render into
48
+ */
49
+ async renderItems(data, parentElement) {
50
+ await this.client._ensureZDisplayOrchestrator();
51
+ return this.client.zDisplayOrchestrator.renderItems(data, parentElement);
52
+ }
53
+
54
+ /**
55
+ * Create container wrapper for a zKey with zTheme responsive classes
56
+ * Supports _zClass metadata for customization
57
+ * @param {string} zKey - The key name
58
+ * @param {Object} metadata - Metadata object (_zClass, _zStyle, _zHTML, zId)
59
+ */
60
+ async createContainer(zKey, metadata) {
61
+ await this.client._ensureZDisplayOrchestrator();
62
+ return this.client.zDisplayOrchestrator.createContainer(zKey, metadata);
63
+ }
64
+
65
+ /**
66
+ * Render navbar HTML (returns DOM element, doesn't inject into DOM)
67
+ * @param {Array} items - Navbar items (e.g., ['zVaF', 'zAbout', '^zLogin'])
68
+ * @returns {Promise<HTMLElement>} Navbar DOM element
69
+ */
70
+ async renderMetaNavBarHTML(items) {
71
+ await this.client._ensureZDisplayOrchestrator();
72
+ return this.client.zDisplayOrchestrator.renderMetaNavBarHTML(items);
73
+ }
74
+
75
+ /**
76
+ * Render navigation bar from metadata (~zNavBar* in content)
77
+ * @param {Array} items - Navbar items
78
+ * @param {HTMLElement} parentElement - Parent element to render into
79
+ */
80
+ async renderNavBar(items, parentElement) {
81
+ await this.client._ensureZDisplayOrchestrator();
82
+ return this.client.zDisplayOrchestrator.renderNavBar(items, parentElement);
83
+ }
84
+
85
+ /**
86
+ * Render a single zDisplay event as DOM element
87
+ * @param {Object} eventData - zDisplay event data
88
+ * @returns {Promise<HTMLElement>} Rendered element
89
+ */
90
+ async renderZDisplayEvent(eventData) {
91
+ await this.client._ensureZDisplayOrchestrator();
92
+ return this.client.zDisplayOrchestrator.renderZDisplayEvent(eventData);
93
+ }
94
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * L4_Orchestration/rendering/rendering.js
3
+ *
4
+ * Rendering Layer Barrel
5
+ */
6
+
7
+ export { RenderingFacade } from './facade.js';
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ZoloMedia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # zBifrost Client
2
+
3
+ The browser client for **zBifrost** — the WebSocket bridge between a zOS Python
4
+ server and the browser. It turns JSON events streamed from a zOS server into live
5
+ DOM, with no application code of its own.
6
+
7
+ ## What it is
8
+
9
+ A thin bootstrap (`bifrost_client.js`, ~190 lines) that:
10
+
11
+ 1. Reads server config injected into the page (`<script id="zui-config">`)
12
+ 2. Opens a WebSocket to the zOS server
13
+ 3. Receives `connection_info` carrying the core module URL
14
+ 4. Dynamically imports `bifrost_core.js` (server-controlled version)
15
+ 5. Hands off — all rendering is driven by what the server sends
16
+
17
+ **The server controls what the client loads. The client controls nothing about
18
+ your app.**
19
+
20
+ ## What it is not
21
+
22
+ There is no routing logic, no `.zolo` parsing, no RBAC, no business logic, and no
23
+ secrets. The JS receives JSON events from Python and creates DOM elements. That is
24
+ the entire job. See [`docs/SECURITY.md`](docs/SECURITY.md) for the trust boundary.
25
+
26
+ ## Structure
27
+
28
+ ```
29
+ bifrost_client.js — thin bootstrap (~190 lines), hardcoded in the page <head>
30
+ bifrost_core.js — full WebSocket client, loaded dynamically at runtime
31
+ L1_Foundation/ — WebSocket connection, constants, config, bootstrap, registries
32
+ L2_Handling/ — message handling, renderers, cache, navigation, zvaf, hooks
33
+ L3_Abstraction/ — orchestrators (navbar, wizard gate, session)
34
+ L4_Orchestration/ — rendering facade, renderer/manager registries, lifecycle
35
+ zSys/ — DOM utils, theme utils, accessibility, validation, encoding
36
+ syntax/ — Prism.js grammars for .zolo / zUI / zSchema / zSpark / zEnv
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ The page loads the bootstrap and instantiates it; the server injects the config:
42
+
43
+ ```html
44
+ <script src="https://cdn.jsdelivr.net/gh/ZoloAi/zbifrost-client@v1.7.62/bifrost_client.js"></script>
45
+ <script>
46
+ window.bifrostClient = new BifrostClient(null, { autoConnect: true });
47
+ </script>
48
+ ```
49
+
50
+ The server injects `<script id="zui-config" type="application/json">` with the
51
+ WebSocket config, `zBlock`, `zVaFile`, `zVaFolder`, and pre-built nav HTML, and
52
+ sends `connection_info.bifrost_core_url` over the socket to select the core build.
53
+
54
+ ### Pinning & CDN
55
+
56
+ Releases are git tags consumed via jsDelivr. Pin an exact tag in production:
57
+
58
+ ```html
59
+ <script src="https://cdn.jsdelivr.net/gh/ZoloAi/zbifrost-client@v1.7.62/bifrost_client.js"></script>
60
+ ```
61
+
62
+ The matching `bifrost_core.js` version is chosen **server-side** (see the zOS
63
+ bridge `connection_info.bifrost_core_url`) — bump both together on each release.
64
+ For a CDN other than jsDelivr, pass `coreOriginAllowlist` (see
65
+ [`docs/SECURITY.md`](docs/SECURITY.md#core-import-origin-pinning)).
66
+
67
+ ## Documentation
68
+
69
+ | Guide | What it covers |
70
+ |-------|----------------|
71
+ | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Bootstrap/core split, L1–L4 + zSys layers, registries, lazy loading |
72
+ | [docs/PROTOCOL.md](docs/PROTOCOL.md) | The wire protocol from the client's side: `connection_info`, `render_chunk` + opcode decoding, the `PROTOCOL_EVENTS` vocabulary, message flow |
73
+ | [docs/RENDERERS.md](docs/RENDERERS.md) | The renderer model, the registry, how to add a renderer, the HTML-escape SSOT |
74
+ | [docs/SECURITY.md](docs/SECURITY.md) | Trust boundary, XSS escaping SSOT, core-import origin pinning, session-cookie handling |
75
+
76
+ > The **server-side** zBifrost mechanism (render-opcode encoding, auth, chunking)
77
+ > is documented in the zOS/zGuard repos — it is intentionally not shipped here.
78
+ > This repo documents only the open, browser-side renderer.
79
+
80
+ ## License
81
+
82
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,204 @@
1
+ /**
2
+ * bifrost_client.js — Thin bootstrap for BifrostCore
3
+ *
4
+ * Phase 4: Reduced to ~180 lines.
5
+ * Responsibilities:
6
+ * 1. Read zui-config from DOM
7
+ * 2. Open a raw WebSocket to receive connection_info
8
+ * 3. connection_info.bifrost_core_url → dynamic import(url) of the real client
9
+ * 4. Instantiate BifrostCore and set window.bifrostClient
10
+ * 5. Forward registerHook() calls queued before core is ready
11
+ *
12
+ * Intelligence lives in bifrost_core.js (server-controlled URL = Phase 3B).
13
+ * zVaF.html does: new BifrostClient() — autoConnect defaults on; pass
14
+ * { autoConnect: false } to opt out (the exception, not the rule).
15
+ */
16
+
17
+ (function (root) {
18
+ 'use strict';
19
+
20
+ // ─── helpers ────────────────────────────────────────────────────────────────
21
+
22
+ function readZuiConfig() {
23
+ try {
24
+ const el = document.getElementById('zui-config');
25
+ return el ? JSON.parse(el.textContent) : {};
26
+ } catch (_) { return {}; }
27
+ }
28
+
29
+ function buildWsUrl(wsCfg) {
30
+ wsCfg = wsCfg || {};
31
+ const proto = wsCfg.ssl_enabled ? 'wss' : 'ws';
32
+ const host = wsCfg.host || '127.0.0.1';
33
+ const port = wsCfg.port || 8765;
34
+ return `${proto}://${host}:${port}`;
35
+ }
36
+
37
+ function makeLogger(cfg) {
38
+ const isProd = cfg.deployment === 'Production';
39
+ const prefix = '[BifrostBootstrap]';
40
+ return {
41
+ debug: isProd ? () => {} : (...a) => console.debug(prefix, ...a),
42
+ info: isProd ? () => {} : (...a) => console.info(prefix, ...a),
43
+ warn: (...a) => console.warn(prefix, ...a),
44
+ error: (...a) => console.error(prefix, ...a),
45
+ };
46
+ }
47
+
48
+ // ─── BifrostClient (bootstrap) ──────────────────────────────────────────────
49
+
50
+ class BifrostClient {
51
+ constructor(url, options = {}) {
52
+ // Ergonomic single-object form: new BifrostClient({ zHooks: {...} }).
53
+ // A non-string first arg is treated as the options bag.
54
+ if (url && typeof url === 'object') { options = url; url = null; }
55
+
56
+ this._cfg = readZuiConfig();
57
+ this._opts = options;
58
+ this._url = url || buildWsUrl(this._cfg.websocket);
59
+ this._core = null;
60
+ this._coreLoading = false;
61
+ this._pendingHooks = []; // hooks registered before core is ready
62
+ this.logger = makeLogger(this._cfg);
63
+
64
+ // Default-on: connect unless the caller explicitly opts out.
65
+ if (options.autoConnect !== false) {
66
+ this._bootstrap();
67
+ }
68
+ }
69
+
70
+ // ── bootstrap: minimal WS just to receive connection_info ─────────────────
71
+
72
+ _bootstrap() {
73
+ const ws = this._ws = new WebSocket(this._url);
74
+
75
+ ws.addEventListener('open', () => {
76
+ this.logger.info('Connected (bootstrap)');
77
+ // Do NOT send execute_walker here — BifrostCore will do it on its own connect.
78
+ // Sending here would cause a redundant server-side walker execution on an
79
+ // orphaned WS connection that we are about to close.
80
+ });
81
+
82
+ ws.addEventListener('message', ({ data }) => {
83
+ let msg;
84
+ try { msg = JSON.parse(data); } catch { return; }
85
+
86
+ if (msg.event === 'connection_info' && !this._core) {
87
+ this._loadCore(msg).catch(err => this.logger.error('Core load failed:', err));
88
+ }
89
+ // All other messages are ignored by the bootstrap; BifrostCore handles them.
90
+ });
91
+
92
+ ws.addEventListener('close', () => {
93
+ if (!this._core && !this._coreLoading) this.logger.warn('Bootstrap WS closed before core loaded');
94
+ });
95
+
96
+ ws.addEventListener('error', () => {
97
+ this.logger.error('Bootstrap WS error');
98
+ });
99
+ }
100
+
101
+ // ── core loading ──────────────────────────────────────────────────────────
102
+
103
+ async _loadCore(connectionInfo) {
104
+ this._coreLoading = true;
105
+ // 3B: server tells us the authoritative core URL; falls back to local.
106
+ // Relative ("/...") resolves against the page origin; absolute URLs are
107
+ // taken as-is (CDN deployments).
108
+ const rawUrl = connectionInfo.bifrost_core_url || '/bifrost/src/bifrost_core.js';
109
+ let coreUrl;
110
+ try {
111
+ coreUrl = new URL(rawUrl, window.location.origin).href;
112
+ } catch (e) {
113
+ this._coreLoading = false;
114
+ this.logger.error('Invalid bifrost_core_url, refusing to load core:', rawUrl);
115
+ return;
116
+ }
117
+
118
+ // Origin pinning: bifrost_core_url arrives over the WebSocket and is therefore
119
+ // attacker-influenceable if connection_info is spoofed. Only import from the
120
+ // page origin, the official jsDelivr CDN (where the canonical client ships),
121
+ // or an explicitly configured allowlist — never an arbitrary origin, which
122
+ // would load attacker code into the page context.
123
+ const allowedOrigins = [
124
+ window.location.origin,
125
+ 'https://cdn.jsdelivr.net',
126
+ ...(this._opts.coreOriginAllowlist || []),
127
+ ];
128
+ const coreOrigin = new URL(coreUrl).origin;
129
+ if (!allowedOrigins.includes(coreOrigin)) {
130
+ this._coreLoading = false;
131
+ this.logger.error(
132
+ `Refusing to load bifrost core from disallowed origin "${coreOrigin}". ` +
133
+ `Allowed: ${allowedOrigins.join(', ')}. ` +
134
+ `Set opts.coreOriginAllowlist to permit a trusted CDN.`
135
+ );
136
+ return;
137
+ }
138
+ this.logger.info('Loading core from', coreUrl);
139
+
140
+ // Close the bootstrap WS cleanly before the core opens its own
141
+ this._ws.close();
142
+
143
+ const mod = await import(coreUrl);
144
+
145
+ // Instantiate BifrostCore — it reads zui-config itself, connects, sends execute_walker
146
+ const core = new mod.BifrostCore(this._url, {
147
+ autoConnect: true,
148
+ zTheme: false, // ztheme.js already loaded by zVaF.html
149
+ ...this._opts,
150
+ });
151
+
152
+ this._core = core;
153
+ window.bifrostClient = core;
154
+
155
+ // Replay any hooks registered on the bootstrap before the core was ready
156
+ for (const { hookName, fn } of this._pendingHooks) {
157
+ core.registerHook(hookName, fn);
158
+ }
159
+ this._pendingHooks = [];
160
+
161
+ this.logger.info('Core attached — window.bifrostClient is now BifrostCore');
162
+ }
163
+
164
+ // ── forwarding API (called by zVaF.html / menu_integration before core ready) ──
165
+
166
+ registerHook(hookName, fn) {
167
+ if (this._core) {
168
+ this._core.registerHook(hookName, fn);
169
+ } else {
170
+ this._pendingHooks.push({ hookName, fn });
171
+ }
172
+ }
173
+
174
+ unregisterHook(hookName) {
175
+ if (this._core) this._core.unregisterHook(hookName);
176
+ }
177
+
178
+ send(data) {
179
+ if (this._core) return this._core.send(data);
180
+ this.logger.warn('send() called before core is ready');
181
+ }
182
+
183
+ read(resource, params) {
184
+ if (this._core) return this._core.read(resource, params);
185
+ return Promise.reject(new Error('BifrostClient: core not yet loaded'));
186
+ }
187
+
188
+ // Expose connect() for callers who do: client.connect() manually
189
+ async connect() {
190
+ // Bootstrap already opened a WS; if core is loaded, delegate
191
+ if (this._core) return this._core.connect();
192
+ // Otherwise wait for core to be ready (already bootstrapping)
193
+ return new Promise((resolve) => {
194
+ const poll = setInterval(() => {
195
+ if (this._core) { clearInterval(poll); resolve(); }
196
+ }, 50);
197
+ });
198
+ }
199
+ }
200
+
201
+ // Expose globally — zVaF.html does: window.bifrostClient = new BifrostClient(...)
202
+ root.BifrostClient = BifrostClient;
203
+
204
+ }(typeof self !== 'undefined' ? self : this));