@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,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,7 @@
1
+ /**
2
+ * L1_Foundation/config - Configuration Management
3
+ *
4
+ * Client configuration, option validation, defaults.
5
+ */
6
+
7
+ export { ClientConfig } from './client_config.js';
@@ -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
+ }