@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.
- package/L1_Foundation/L1_Foundation.js +13 -0
- package/L1_Foundation/bootstrap/bootstrap.js +11 -0
- package/L1_Foundation/bootstrap/bootstrap_hooks.js +123 -0
- package/L1_Foundation/bootstrap/bootstrap_index.js +15 -0
- package/L1_Foundation/bootstrap/bootstrap_logger.js +135 -0
- package/L1_Foundation/bootstrap/cdn_loader.js +217 -0
- package/L1_Foundation/bootstrap/module_registry.js +102 -0
- package/L1_Foundation/bootstrap/prism_loader.js +164 -0
- package/L1_Foundation/config/client_config.js +110 -0
- package/L1_Foundation/config/config.js +7 -0
- package/L1_Foundation/connection/connection.js +8 -0
- package/L1_Foundation/connection/websocket_connection.js +122 -0
- package/L1_Foundation/constants/bifrost_constants.js +284 -0
- package/L1_Foundation/constants/constants.js +7 -0
- package/L1_Foundation/logger/logger.js +10 -0
- package/L2_Handling/L2_Handling.js +15 -0
- package/L2_Handling/cache/cache.js +22 -0
- package/L2_Handling/cache/cache_constants.js +69 -0
- package/L2_Handling/cache/orchestration/cache_manager.js +299 -0
- package/L2_Handling/cache/orchestration/cache_orchestrator.js +260 -0
- package/L2_Handling/cache/orchestration/orchestration.js +12 -0
- package/L2_Handling/cache/storage/session_manager.js +289 -0
- package/L2_Handling/cache/storage/storage.js +10 -0
- package/L2_Handling/cache/storage/storage_manager.js +590 -0
- package/L2_Handling/display/composite/composite.js +13 -0
- package/L2_Handling/display/composite/dashboard_renderer.js +221 -0
- package/L2_Handling/display/composite/swiper_renderer.js +564 -0
- package/L2_Handling/display/composite/terminal_renderer.js +922 -0
- package/L2_Handling/display/composite/wizard_conditional_renderer.js +274 -0
- package/L2_Handling/display/display.js +30 -0
- package/L2_Handling/display/feedback/feedback.js +11 -0
- package/L2_Handling/display/feedback/progressbar_renderer.js +418 -0
- package/L2_Handling/display/feedback/spinner_renderer.js +246 -0
- package/L2_Handling/display/inputs/button_renderer.js +634 -0
- package/L2_Handling/display/inputs/form_renderer.js +583 -0
- package/L2_Handling/display/inputs/input_renderer.js +658 -0
- package/L2_Handling/display/inputs/inputs.js +12 -0
- package/L2_Handling/display/navigation/menu_renderer.js +206 -0
- package/L2_Handling/display/navigation/navigation.js +11 -0
- package/L2_Handling/display/navigation/navigation_renderer.js +703 -0
- package/L2_Handling/display/orchestration/orchestration.js +11 -0
- package/L2_Handling/display/orchestration/renderer.js +430 -0
- package/L2_Handling/display/orchestration/zdisplay_orchestrator.js +1759 -0
- package/L2_Handling/display/outputs/alert_renderer.js +161 -0
- package/L2_Handling/display/outputs/audio_renderer.js +94 -0
- package/L2_Handling/display/outputs/card_renderer.js +229 -0
- package/L2_Handling/display/outputs/code_renderer.js +66 -0
- package/L2_Handling/display/outputs/dl_renderer.js +131 -0
- package/L2_Handling/display/outputs/header_renderer.js +162 -0
- package/L2_Handling/display/outputs/icon_renderer.js +107 -0
- package/L2_Handling/display/outputs/image_renderer.js +145 -0
- package/L2_Handling/display/outputs/list_renderer.js +190 -0
- package/L2_Handling/display/outputs/outputs.js +19 -0
- package/L2_Handling/display/outputs/table_renderer.js +765 -0
- package/L2_Handling/display/outputs/text_renderer.js +818 -0
- package/L2_Handling/display/outputs/typography_renderer.js +293 -0
- package/L2_Handling/display/outputs/video_renderer.js +116 -0
- package/L2_Handling/display/primitives/document_structure_primitives.js +319 -0
- package/L2_Handling/display/primitives/form_primitives.js +526 -0
- package/L2_Handling/display/primitives/generic_containers.js +109 -0
- package/L2_Handling/display/primitives/interactive_primitives.js +305 -0
- package/L2_Handling/display/primitives/link_primitives.js +552 -0
- package/L2_Handling/display/primitives/lists_primitives.js +262 -0
- package/L2_Handling/display/primitives/media_primitives.js +383 -0
- package/L2_Handling/display/primitives/primitives.js +19 -0
- package/L2_Handling/display/primitives/semantic_element_primitive.js +226 -0
- package/L2_Handling/display/primitives/table_primitives.js +528 -0
- package/L2_Handling/display/primitives/typography_primitives.js +175 -0
- package/L2_Handling/display/specialized/input_request_renderer.js +467 -0
- package/L2_Handling/display/specialized/specialized.js +10 -0
- package/L2_Handling/hooks/hooks.js +9 -0
- package/L2_Handling/hooks/menu_integration.js +57 -0
- package/L2_Handling/hooks/widget_hook_manager.js +292 -0
- package/L2_Handling/message/message.js +8 -0
- package/L2_Handling/message/message_handler.js +701 -0
- package/L2_Handling/navigation/navigation.js +8 -0
- package/L2_Handling/navigation/navigation_manager.js +403 -0
- package/L2_Handling/zhooks/features/cache_live.js +287 -0
- package/L2_Handling/zhooks/features/crumbs_live.js +292 -0
- package/L2_Handling/zhooks/zhooks_manager.js +65 -0
- package/L2_Handling/zvaf/zvaf.js +8 -0
- package/L2_Handling/zvaf/zvaf_manager.js +334 -0
- package/L3_Abstraction/L3_Abstraction.js +12 -0
- package/L3_Abstraction/orchestrator/container_unwrapper.js +101 -0
- package/L3_Abstraction/orchestrator/group_renderer.js +698 -0
- package/L3_Abstraction/orchestrator/input_event_handler.js +797 -0
- package/L3_Abstraction/orchestrator/metadata_processor.js +249 -0
- package/L3_Abstraction/orchestrator/navbar_builder.js +201 -0
- package/L3_Abstraction/orchestrator/orchestrator.js +13 -0
- package/L3_Abstraction/orchestrator/wizard_gate_handler.js +360 -0
- package/L3_Abstraction/renderer/renderer.js +1 -0
- package/L3_Abstraction/session/session.js +1 -0
- package/L4_Orchestration/L4_Orchestration.js +11 -0
- package/L4_Orchestration/client/client.js +1 -0
- package/L4_Orchestration/facade/facade.js +9 -0
- package/L4_Orchestration/facade/manager_registry.js +118 -0
- package/L4_Orchestration/facade/renderer_registry.js +274 -0
- package/L4_Orchestration/lifecycle/asset_loader.js +255 -0
- package/L4_Orchestration/lifecycle/initializer.js +135 -0
- package/L4_Orchestration/lifecycle/lifecycle.js +8 -0
- package/L4_Orchestration/rendering/facade.js +94 -0
- package/L4_Orchestration/rendering/rendering.js +7 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/bifrost_client.js +204 -0
- package/bifrost_core.js +1686 -0
- package/docs/ARCHITECTURE.md +111 -0
- package/docs/PROTOCOL.md +106 -0
- package/docs/RENDERERS.md +101 -0
- package/docs/SECURITY.md +92 -0
- package/package.json +24 -0
- package/syntax/prism-zconfig.js +41 -0
- package/syntax/prism-zenv.js +69 -0
- package/syntax/prism-zolo-theme.css +288 -0
- package/syntax/prism-zolo.js +380 -0
- package/syntax/prism-zschema.js +38 -0
- package/syntax/prism-zspark.js +25 -0
- package/syntax/prism-zui.js +68 -0
- package/zSys/accessibility/accessibility.js +10 -0
- package/zSys/accessibility/emoji_accessibility.js +173 -0
- package/zSys/dom/block_utils.js +122 -0
- package/zSys/dom/container_utils.js +370 -0
- package/zSys/dom/dom.js +13 -0
- package/zSys/dom/dom_utils.js +328 -0
- package/zSys/dom/encoding_utils.js +117 -0
- package/zSys/dom/style_utils.js +71 -0
- package/zSys/errors/error_display.js +299 -0
- package/zSys/errors/errors.js +10 -0
- package/zSys/theme/color_utils.js +274 -0
- package/zSys/theme/dark_mode_utils.js +272 -0
- package/zSys/theme/size_utils.js +256 -0
- package/zSys/theme/spacing_utils.js +405 -0
- package/zSys/theme/theme.js +14 -0
- package/zSys/theme/zbase.css +1735 -0
- package/zSys/theme/zbase_inject.js +161 -0
- package/zSys/theme/ztheme_utils.js +305 -0
- package/zSys/validation/error_boundary.js +201 -0
- package/zSys/validation/validation.js +11 -0
- package/zSys/validation/validation_utils.js +238 -0
- package/zSys/zSys.js +14 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L1_Foundation/bootstrap/prism_loader.js
|
|
3
|
+
*
|
|
4
|
+
* Prism.js loader with sequential loading (core → components → .zolo languages).
|
|
5
|
+
* Extracted from bifrost_client.js _loadPrismJS() and _loadPrismZolo().
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Load Prism.js from CDN for syntax highlighting
|
|
10
|
+
*
|
|
11
|
+
* NOTE: Prism requires specific load order (core → components → .zolo languages)
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} logger - Logger instance for debug output
|
|
14
|
+
*/
|
|
15
|
+
export function loadPrismJS(logger) {
|
|
16
|
+
if (typeof document === 'undefined') {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
logger.debug('[Prism] Loading...');
|
|
21
|
+
|
|
22
|
+
const prismCDN = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0';
|
|
23
|
+
const prismTheme = `${prismCDN}/themes/prism-tomorrow.min.css`;
|
|
24
|
+
|
|
25
|
+
// Check if Prism CSS already loaded
|
|
26
|
+
if (!document.querySelector(`link[href="${prismTheme}"]`)) {
|
|
27
|
+
const link = document.createElement('link');
|
|
28
|
+
link.rel = 'stylesheet';
|
|
29
|
+
link.href = prismTheme;
|
|
30
|
+
document.head.appendChild(link);
|
|
31
|
+
logger.debug('[Prism] CSS loaded (prism-tomorrow)');
|
|
32
|
+
} else {
|
|
33
|
+
logger.debug('Prism.js CSS already loaded');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Load custom .zolo theme overrides — bundled WITH the client (syntax/),
|
|
37
|
+
// resolved relative to this module so the zlsp palette ships built-in.
|
|
38
|
+
const zoloTheme = new URL('../../syntax/prism-zolo-theme.css', import.meta.url).href;
|
|
39
|
+
if (!document.querySelector(`link[href="${zoloTheme}"]`)) {
|
|
40
|
+
const link = document.createElement('link');
|
|
41
|
+
link.rel = 'stylesheet';
|
|
42
|
+
link.href = zoloTheme;
|
|
43
|
+
document.head.appendChild(link);
|
|
44
|
+
logger.debug('[Prism] .zolo custom theme loaded');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Load Prism core + common languages
|
|
48
|
+
const scripts = [
|
|
49
|
+
{ src: `${prismCDN}/prism.min.js`, name: 'core' },
|
|
50
|
+
{ src: `${prismCDN}/components/prism-markup.min.js`, name: 'markup' },
|
|
51
|
+
{ src: `${prismCDN}/components/prism-css.min.js`, name: 'css' },
|
|
52
|
+
{ src: `${prismCDN}/components/prism-javascript.min.js`, name: 'javascript' },
|
|
53
|
+
{ src: `${prismCDN}/components/prism-python.min.js`, name: 'python' },
|
|
54
|
+
{ src: `${prismCDN}/components/prism-bash.min.js`, name: 'bash' },
|
|
55
|
+
{ src: `${prismCDN}/components/prism-yaml.min.js`, name: 'yaml' }
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
// Load scripts sequentially (Prism components depend on core)
|
|
59
|
+
const loadScript = (scriptInfo, index) => {
|
|
60
|
+
// Check if script already loaded
|
|
61
|
+
if (document.querySelector(`script[src="${scriptInfo.src}"]`)) {
|
|
62
|
+
// Already loaded, continue to next
|
|
63
|
+
logger.debug(`Prism ${scriptInfo.name} already loaded`);
|
|
64
|
+
if (index < scripts.length - 1) {
|
|
65
|
+
loadScript(scripts[index + 1], index + 1);
|
|
66
|
+
} else {
|
|
67
|
+
logger.debug('All Prism.js scripts already loaded');
|
|
68
|
+
loadPrismZolo(logger);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const script = document.createElement('script');
|
|
74
|
+
script.src = scriptInfo.src;
|
|
75
|
+
script.onload = () => {
|
|
76
|
+
// Load next script in sequence (silent)
|
|
77
|
+
if (index < scripts.length - 1) {
|
|
78
|
+
loadScript(scripts[index + 1], index + 1);
|
|
79
|
+
} else {
|
|
80
|
+
// Load custom .zolo language definition
|
|
81
|
+
loadPrismZolo(logger);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
script.onerror = () => {
|
|
85
|
+
logger.warn(`Failed to load Prism ${scriptInfo.name}`);
|
|
86
|
+
};
|
|
87
|
+
document.head.appendChild(script);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Start loading chain
|
|
91
|
+
loadScript(scripts[0], 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Load custom .zolo language definition for Prism.js
|
|
96
|
+
* @param {Object} logger - Logger instance
|
|
97
|
+
*/
|
|
98
|
+
function loadPrismZolo(logger) {
|
|
99
|
+
if (typeof document === 'undefined') {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
logger.debug('Loading custom .zolo languages...');
|
|
104
|
+
|
|
105
|
+
// Keep dependency order deterministic: extensions rely on base "zolo".
|
|
106
|
+
const zoloLanguages = ['zolo', 'zspark', 'zui', 'zschema', 'zconfig', 'zenv'];
|
|
107
|
+
const totalLanguages = zoloLanguages.length;
|
|
108
|
+
|
|
109
|
+
// zolo grammars ship WITH the client (syntax/), not the host app. Resolve
|
|
110
|
+
// them relative to this module so every zApp gets zolo highlighting for free.
|
|
111
|
+
const syntaxBase = new URL('../../syntax/', import.meta.url).href;
|
|
112
|
+
|
|
113
|
+
const alreadyLoaded = zoloLanguages.every((lang) => window.Prism?.languages?.[lang]);
|
|
114
|
+
if (alreadyLoaded) {
|
|
115
|
+
logger.debug('Prism .zolo languages already loaded');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const finishLoad = () => {
|
|
120
|
+
logger.debug('Prism.js loaded (7 languages + %s .zolo variants)', totalLanguages);
|
|
121
|
+
|
|
122
|
+
// Rehighlight ALL code blocks rendered before grammars loaded — covers
|
|
123
|
+
// zolo variants AND stock languages (bash, python, …) from zMD fences.
|
|
124
|
+
if (window.Prism) {
|
|
125
|
+
const codeBlocks = document.querySelectorAll('pre code[class*="language-"]');
|
|
126
|
+
if (codeBlocks.length > 0) {
|
|
127
|
+
logger.debug(`Rehighlighting ${codeBlocks.length} code blocks`);
|
|
128
|
+
codeBlocks.forEach(block => {
|
|
129
|
+
Prism.highlightElement(block);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const loadLanguageSequentially = (index) => {
|
|
136
|
+
if (index >= totalLanguages) {
|
|
137
|
+
finishLoad();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const lang = zoloLanguages[index];
|
|
142
|
+
const scriptSrc = `${syntaxBase}prism-${lang}.js`;
|
|
143
|
+
|
|
144
|
+
// Check if already loaded
|
|
145
|
+
if (document.querySelector(`script[src="${scriptSrc}"]`)) {
|
|
146
|
+
logger.debug(`Prism ${lang} already loaded`);
|
|
147
|
+
loadLanguageSequentially(index + 1);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const script = document.createElement('script');
|
|
152
|
+
script.src = scriptSrc;
|
|
153
|
+
script.onload = () => {
|
|
154
|
+
loadLanguageSequentially(index + 1);
|
|
155
|
+
};
|
|
156
|
+
script.onerror = () => {
|
|
157
|
+
logger.debug(`[Prism] language file not found: ${scriptSrc} (skipped)`);
|
|
158
|
+
loadLanguageSequentially(index + 1);
|
|
159
|
+
};
|
|
160
|
+
document.head.appendChild(script);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
loadLanguageSequentially(0);
|
|
164
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L1_Foundation/config/client_config.js
|
|
3
|
+
*
|
|
4
|
+
* Client configuration management, option validation, default values.
|
|
5
|
+
* Extracted from bifrost_client.js constructor logic.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Default values (mirror bifrost_constants.js TIMEOUTS)
|
|
9
|
+
const RECONNECT_DELAY_DEFAULT = 3000; // TIMEOUTS.RECONNECT_DELAY
|
|
10
|
+
const REQUEST_TIMEOUT_DEFAULT = 30000; // TIMEOUTS.REQUEST_TIMEOUT
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ClientConfig - Manages BifrostClient configuration
|
|
14
|
+
*/
|
|
15
|
+
export class ClientConfig {
|
|
16
|
+
/**
|
|
17
|
+
* Parse and validate client options
|
|
18
|
+
* @param {Object} options - Raw options from constructor
|
|
19
|
+
* @returns {Object} Validated and normalized options
|
|
20
|
+
*/
|
|
21
|
+
static parseOptions(options = {}) {
|
|
22
|
+
return {
|
|
23
|
+
autoConnect: options.autoConnect === true,
|
|
24
|
+
zTheme: options.zTheme !== false, // Default true
|
|
25
|
+
zThemeCDN: options.zThemeCDN || 'https://cdn.jsdelivr.net/gh/ZoloAi/zTheme@main/dist',
|
|
26
|
+
targetElement: options.targetElement || 'zVaF',
|
|
27
|
+
autoRequest: options.autoRequest || null,
|
|
28
|
+
autoReconnect: options.autoReconnect !== false, // Default true
|
|
29
|
+
reconnectDelay: typeof options.reconnectDelay === 'number' ? options.reconnectDelay : RECONNECT_DELAY_DEFAULT,
|
|
30
|
+
timeout: typeof options.timeout === 'number' ? options.timeout : REQUEST_TIMEOUT_DEFAULT,
|
|
31
|
+
debug: options.debug === true,
|
|
32
|
+
token: options.token || null,
|
|
33
|
+
hooks: options.hooks || {},
|
|
34
|
+
|
|
35
|
+
// Walker-specific options
|
|
36
|
+
zVaFile: options.zVaFile || null,
|
|
37
|
+
zVaFolder: options.zVaFolder || null,
|
|
38
|
+
zBlock: options.zBlock || null,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read zUI config from page (server-injected)
|
|
44
|
+
* @returns {Object} Parsed zUI config or empty object
|
|
45
|
+
*/
|
|
46
|
+
static readZUIConfig() {
|
|
47
|
+
if (typeof document === 'undefined') {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const zuiConfigEl = document.getElementById('zui-config');
|
|
52
|
+
if (!zuiConfigEl) {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(zuiConfigEl.textContent);
|
|
58
|
+
} catch (e) {
|
|
59
|
+
console.error('[ClientConfig] Failed to parse zui-config:', e);
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Auto-construct WebSocket URL from backend config
|
|
66
|
+
* @param {Object} zuiConfig - Parsed zUI config
|
|
67
|
+
* @returns {Object} { url, ssl_enabled }
|
|
68
|
+
*/
|
|
69
|
+
static autoConstructURL(zuiConfig) {
|
|
70
|
+
const wsConfig = zuiConfig.websocket || {};
|
|
71
|
+
const protocol = wsConfig.ssl_enabled ? 'wss:' : 'ws:';
|
|
72
|
+
const wsHost = wsConfig.host || '127.0.0.1';
|
|
73
|
+
const wsPort = wsConfig.port || 8765;
|
|
74
|
+
const url = `${protocol}//${wsHost}:${wsPort}`;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
url,
|
|
78
|
+
ssl_enabled: wsConfig.ssl_enabled || false
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Validate WebSocket URL
|
|
84
|
+
* @param {string} url - URL to validate
|
|
85
|
+
* @throws {Error} If URL is invalid
|
|
86
|
+
*/
|
|
87
|
+
static validateURL(url) {
|
|
88
|
+
if (typeof url !== 'string' || url.trim() === '') {
|
|
89
|
+
throw new Error('BifrostClient: URL must be a non-empty string');
|
|
90
|
+
}
|
|
91
|
+
if (!url.startsWith('ws://') && !url.startsWith('wss://')) {
|
|
92
|
+
throw new Error('BifrostClient: URL must start with ws:// or wss://');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Merge walker options from zUI config
|
|
98
|
+
* @param {Object} options - Current options
|
|
99
|
+
* @param {Object} zuiConfig - zUI config from page
|
|
100
|
+
* @returns {Object} Merged options
|
|
101
|
+
*/
|
|
102
|
+
static mergeWalkerOptions(options, zuiConfig) {
|
|
103
|
+
return {
|
|
104
|
+
...options,
|
|
105
|
+
zVaFile: options.zVaFile || zuiConfig.zVaFile || null,
|
|
106
|
+
zVaFolder: options.zVaFolder || zuiConfig.zVaFolder || null,
|
|
107
|
+
zBlock: options.zBlock || zuiConfig.zBlock || null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L1_Foundation/connection - WebSocket Connection Management
|
|
3
|
+
*
|
|
4
|
+
* Provides WebSocket connection management for BifrostClient.
|
|
5
|
+
* Handles connection lifecycle, auto-reconnect, and message routing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { WebSocketConnection } from './websocket_connection.js';
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L1_Foundation/connection/websocket_connection.js
|
|
3
|
+
*
|
|
4
|
+
* WebSocket Connection - Browser WebSocket Connection Management
|
|
5
|
+
*
|
|
6
|
+
* Handles WebSocket connection lifecycle, auto-reconnect, and message routing.
|
|
7
|
+
* Extracted from bifrost_client.js inline stub (Task 0, Step 1.5)
|
|
8
|
+
*
|
|
9
|
+
* @module connection/websocket_connection
|
|
10
|
+
* @layer L1 (Foundation)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { TIMEOUTS } from '../constants/bifrost_constants.js';
|
|
14
|
+
|
|
15
|
+
export class WebSocketConnection {
|
|
16
|
+
constructor(url, logger, hooks, options = {}) {
|
|
17
|
+
this.url = url;
|
|
18
|
+
this.logger = logger;
|
|
19
|
+
this.hooks = hooks;
|
|
20
|
+
this.options = {
|
|
21
|
+
autoReconnect: options.autoReconnect !== false,
|
|
22
|
+
reconnectDelay: options.reconnectDelay || TIMEOUTS.RECONNECT_DELAY
|
|
23
|
+
};
|
|
24
|
+
this.ws = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if WebSocket is connected
|
|
29
|
+
* @returns {boolean}
|
|
30
|
+
*/
|
|
31
|
+
isConnected() {
|
|
32
|
+
return this.ws && this.ws.readyState === WebSocket.OPEN;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Effective connect URL — appends the blue-green resume id (prior server
|
|
37
|
+
* full_session_id) when one is remembered, so a reconnect that lands on a
|
|
38
|
+
* swapped-in instance resumes the same session. No id (first connect / cleared
|
|
39
|
+
* storage) → the base URL, i.e. today's fresh-mint behavior.
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
_effectiveUrl() {
|
|
43
|
+
let url = this.url;
|
|
44
|
+
try {
|
|
45
|
+
const resumeId = (typeof sessionStorage !== 'undefined')
|
|
46
|
+
? sessionStorage.getItem('zOS_resume_id') : null;
|
|
47
|
+
if (resumeId) {
|
|
48
|
+
url += (url.includes('?') ? '&' : '?') + 'zresume=' + encodeURIComponent(resumeId);
|
|
49
|
+
}
|
|
50
|
+
} catch (_) { /* storage unavailable — connect without resume */ }
|
|
51
|
+
return url;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Connect to WebSocket server
|
|
56
|
+
* @returns {Promise<void>}
|
|
57
|
+
*/
|
|
58
|
+
async connect() {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
this.ws = new WebSocket(this._effectiveUrl());
|
|
61
|
+
|
|
62
|
+
this.ws.onopen = () => {
|
|
63
|
+
this.logger.info('Connected to server');
|
|
64
|
+
this.hooks.call('onConnected', { url: this.url });
|
|
65
|
+
resolve();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
this.ws.onerror = (error) => {
|
|
69
|
+
this.logger.error('WebSocket error:', error);
|
|
70
|
+
this.hooks.call('onError', error);
|
|
71
|
+
reject(error);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
this.ws.onclose = (event) => {
|
|
75
|
+
this.logger.info('Disconnected from server');
|
|
76
|
+
this.hooks.call('onDisconnected', event);
|
|
77
|
+
|
|
78
|
+
// Auto-reconnect if enabled and connection was not cleanly closed
|
|
79
|
+
if (this.options.autoReconnect && !event.wasClean) {
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
this.logger.info('Attempting to reconnect...');
|
|
82
|
+
this.connect().catch(err => {
|
|
83
|
+
this.logger.error('Reconnect failed:', err);
|
|
84
|
+
});
|
|
85
|
+
}, this.options.reconnectDelay);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Disconnect from WebSocket server
|
|
93
|
+
*/
|
|
94
|
+
disconnect() {
|
|
95
|
+
if (this.ws) {
|
|
96
|
+
this.ws.close();
|
|
97
|
+
this.ws = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Send message to server
|
|
103
|
+
* @param {string} msg - Message to send (JSON string)
|
|
104
|
+
*/
|
|
105
|
+
send(msg) {
|
|
106
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
107
|
+
this.ws.send(msg);
|
|
108
|
+
} else {
|
|
109
|
+
this.logger.warn('Cannot send message: WebSocket not connected');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Set message handler callback
|
|
115
|
+
* @param {Function} callback - Callback function for incoming messages
|
|
116
|
+
*/
|
|
117
|
+
onMessage(callback) {
|
|
118
|
+
if (this.ws) {
|
|
119
|
+
this.ws.onmessage = callback;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|