mdas-jsview-sdk 1.0.21-uat.0 → 1.0.23-uat.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/dist/mdas-sdk.esm.js +256 -514
- package/dist/mdas-sdk.esm.js.map +1 -1
- package/dist/mdas-sdk.js +256 -514
- package/dist/mdas-sdk.js.map +1 -1
- package/dist/mdas-sdk.min.js +8 -8
- package/dist/mdas-sdk.min.js.map +1 -1
- package/package.json +1 -1
package/dist/mdas-sdk.esm.js
CHANGED
|
@@ -406,6 +406,85 @@ class BaseWidget {
|
|
|
406
406
|
console.log(`[${this.constructor.name}] Connection failed after ${status.maxAttempts} attempts`);
|
|
407
407
|
}
|
|
408
408
|
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Validate initial symbol with the API and handle access/permission errors
|
|
412
|
+
* This is a reusable method for all widgets that need to validate symbols on initialization
|
|
413
|
+
*
|
|
414
|
+
* @param {string} apiMethod - The API method name to call (e.g., 'quotel1', 'quoteOptionl1')
|
|
415
|
+
* @param {string} symbol - The symbol to validate
|
|
416
|
+
* @param {Function} onSuccess - Callback when validation succeeds with data, receives (data, result)
|
|
417
|
+
* @returns {boolean} - Returns false if access denied (stops initialization), true otherwise
|
|
418
|
+
*/
|
|
419
|
+
async validateInitialSymbol(apiMethod, symbol) {
|
|
420
|
+
let onSuccess = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
|
421
|
+
try {
|
|
422
|
+
const apiService = this.wsManager?.getApiService();
|
|
423
|
+
if (!apiService) {
|
|
424
|
+
if (this.debug) {
|
|
425
|
+
console.log(`[${this.constructor.name}] API service not available for initial validation`);
|
|
426
|
+
}
|
|
427
|
+
return true; // Continue without validation
|
|
428
|
+
}
|
|
429
|
+
if (!apiService[apiMethod]) {
|
|
430
|
+
console.error(`[${this.constructor.name}] API method ${apiMethod} does not exist`);
|
|
431
|
+
return true; // Continue without validation
|
|
432
|
+
}
|
|
433
|
+
const result = await apiService[apiMethod](symbol);
|
|
434
|
+
if (result && result.data && result.data.length > 0) {
|
|
435
|
+
if (this.debug) {
|
|
436
|
+
console.log(`[${this.constructor.name}] Initial symbol validated:`, {
|
|
437
|
+
symbol,
|
|
438
|
+
method: apiMethod,
|
|
439
|
+
dataCount: result.data.length
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Call success callback if provided
|
|
444
|
+
if (onSuccess && typeof onSuccess === 'function') {
|
|
445
|
+
onSuccess(result.data, result);
|
|
446
|
+
}
|
|
447
|
+
return true; // Validation successful
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// No data returned - might be access issue, but continue anyway
|
|
451
|
+
if (this.debug) {
|
|
452
|
+
console.log(`[${this.constructor.name}] No data returned from ${apiMethod} for ${symbol}`);
|
|
453
|
+
}
|
|
454
|
+
return true; // Continue anyway, WebSocket might still work
|
|
455
|
+
} catch (error) {
|
|
456
|
+
if (this.debug) {
|
|
457
|
+
console.warn(`[${this.constructor.name}] Initial symbol validation failed:`, error);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Check if it's an access/permission error
|
|
461
|
+
const errorMessage = (error.message || '').toLowerCase();
|
|
462
|
+
const isAccessError = errorMessage.includes('400') ||
|
|
463
|
+
// Bad Request (often used for access issues)
|
|
464
|
+
errorMessage.includes('401') ||
|
|
465
|
+
// Unauthorized
|
|
466
|
+
errorMessage.includes('403') ||
|
|
467
|
+
// Forbidden
|
|
468
|
+
errorMessage.includes('forbidden') || errorMessage.includes('unauthorized') || errorMessage.includes('no access') || errorMessage.includes('no opra access') ||
|
|
469
|
+
// Specific OPRA access denial
|
|
470
|
+
errorMessage.includes('permission denied') || errorMessage.includes('access denied');
|
|
471
|
+
if (isAccessError) {
|
|
472
|
+
// Hide loading and show access error
|
|
473
|
+
this.hideLoading();
|
|
474
|
+
|
|
475
|
+
// Extract more specific error message if available
|
|
476
|
+
let userMessage = `Access denied: You don't have permission to view data for ${symbol}`;
|
|
477
|
+
if (errorMessage.includes('no opra access')) {
|
|
478
|
+
userMessage = `Access denied: You don't have OPRA access for option ${symbol}`;
|
|
479
|
+
}
|
|
480
|
+
this.showError(userMessage);
|
|
481
|
+
return false; // Stop initialization
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// For other errors, continue with WebSocket (might still work)
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
409
488
|
}
|
|
410
489
|
|
|
411
490
|
// src/models/MarketDataModel.js
|
|
@@ -2504,28 +2583,6 @@ const TimeSalesStyles = `
|
|
|
2504
2583
|
font-size: 1.71em;
|
|
2505
2584
|
font-weight: 700;
|
|
2506
2585
|
color: #111827;
|
|
2507
|
-
cursor: pointer;
|
|
2508
|
-
transition: all 0.2s ease;
|
|
2509
|
-
position: relative;
|
|
2510
|
-
}
|
|
2511
|
-
|
|
2512
|
-
.time-sales-widget .symbol.editable-symbol:hover {
|
|
2513
|
-
opacity: 0.8;
|
|
2514
|
-
transform: scale(1.02);
|
|
2515
|
-
}
|
|
2516
|
-
|
|
2517
|
-
.time-sales-widget .symbol.editable-symbol:hover::after {
|
|
2518
|
-
content: "✎";
|
|
2519
|
-
position: absolute;
|
|
2520
|
-
top: -8px;
|
|
2521
|
-
right: -8px;
|
|
2522
|
-
background: #3b82f6;
|
|
2523
|
-
color: white;
|
|
2524
|
-
font-size: 10px;
|
|
2525
|
-
padding: 2px 4px;
|
|
2526
|
-
border-radius: 4px;
|
|
2527
|
-
opacity: 0.8;
|
|
2528
|
-
pointer-events: none;
|
|
2529
2586
|
}
|
|
2530
2587
|
|
|
2531
2588
|
/* ========================================
|
|
@@ -4477,99 +4534,46 @@ class MarketDataWidget extends BaseWidget {
|
|
|
4477
4534
|
this.showLoading();
|
|
4478
4535
|
|
|
4479
4536
|
// Validate initial symbol via API to get company info
|
|
4480
|
-
|
|
4481
|
-
this.
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
const apiService = this.wsManager.getApiService();
|
|
4486
|
-
if (!apiService) {
|
|
4487
|
-
if (this.debug) {
|
|
4488
|
-
console.log('[MarketDataWidget] API service not available for initial validation');
|
|
4489
|
-
}
|
|
4490
|
-
return;
|
|
4491
|
-
}
|
|
4492
|
-
const result = await apiService.quotel1(this.symbol);
|
|
4493
|
-
if (result && result.data && result.data[0]) {
|
|
4494
|
-
const symbolData = result.data[0];
|
|
4495
|
-
// Store for use in updateWidget
|
|
4496
|
-
this.initialValidationData = symbolData;
|
|
4497
|
-
if (this.debug) {
|
|
4498
|
-
console.log('[MarketDataWidget] Initial symbol validated:', {
|
|
4499
|
-
symbol: this.symbol,
|
|
4500
|
-
companyName: symbolData.comp_name,
|
|
4501
|
-
exchangeName: symbolData.market_name
|
|
4502
|
-
});
|
|
4503
|
-
}
|
|
4537
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting data
|
|
4538
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
4539
|
+
// Store for use in updateWidget
|
|
4540
|
+
if (data && data[0]) {
|
|
4541
|
+
this.initialValidationData = data[0];
|
|
4504
4542
|
}
|
|
4505
|
-
}
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
//
|
|
4543
|
+
});
|
|
4544
|
+
|
|
4545
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
4546
|
+
if (validationSuccess === false) {
|
|
4547
|
+
return; // Error is already shown, don't continue
|
|
4510
4548
|
}
|
|
4549
|
+
this.subscribeToData();
|
|
4511
4550
|
}
|
|
4512
4551
|
subscribeToData() {
|
|
4513
4552
|
// Subscribe with symbol for routing
|
|
4514
4553
|
this.unsubscribe = this.wsManager.subscribe(this.widgetId, ['queryl1'], this.handleMessage.bind(this), this.symbol // Pass symbol for routing
|
|
4515
4554
|
);
|
|
4516
|
-
|
|
4517
|
-
// Send subscription message
|
|
4518
|
-
/* this.wsManager.send({
|
|
4519
|
-
type: 'queryl1',
|
|
4520
|
-
symbol: this.symbol
|
|
4521
|
-
}); */
|
|
4522
4555
|
}
|
|
4556
|
+
handleData(message) {
|
|
4557
|
+
//console.log('DEBUG', message)
|
|
4523
4558
|
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
if (this.debug) {
|
|
4529
|
-
console.log('[MarketDataWidget] Received:', event, data);
|
|
4530
|
-
}
|
|
4531
|
-
if (event === 'connection') {
|
|
4532
|
-
this.handleConnectionStatus(data);
|
|
4533
|
-
return;
|
|
4534
|
-
}
|
|
4535
|
-
if (event === 'data') {
|
|
4536
|
-
this.handleData(data);
|
|
4537
|
-
}
|
|
4538
|
-
if (event === 'session_revoked') {
|
|
4539
|
-
if (data.status === 'attempting_relogin') {
|
|
4540
|
-
this.showLoading();
|
|
4541
|
-
} else if (data.status === 'relogin_failed') {
|
|
4542
|
-
this.showError(data.error);
|
|
4543
|
-
} else if (data.status === 'relogin_successful') {
|
|
4544
|
-
this.hideLoading();
|
|
4545
|
-
}
|
|
4546
|
-
return;
|
|
4547
|
-
}
|
|
4548
|
-
} catch (error) {
|
|
4549
|
-
console.error('[MarketDataWidget] Error handling message:', error);
|
|
4550
|
-
this.showError('Error processing data');
|
|
4559
|
+
// Check for error: false and display message if present
|
|
4560
|
+
if (message.error === true) {
|
|
4561
|
+
if (this.debug) {
|
|
4562
|
+
console.log('[MarketDataWidget] Received error response:', message.message);
|
|
4551
4563
|
}
|
|
4552
|
-
|
|
4564
|
+
const errorMsg = message.message || message.Message;
|
|
4553
4565
|
|
|
4554
|
-
|
|
4555
|
-
if (
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
// Re-send subscription when reconnected
|
|
4560
|
-
this.wsManager.send({
|
|
4561
|
-
type: 'queryl1',
|
|
4562
|
-
symbol: this.symbol
|
|
4563
|
-
});
|
|
4564
|
-
} else if (status.status === 'disconnected') {
|
|
4565
|
-
this.showError('Disconnected from data service');
|
|
4566
|
-
} else if (status.status === 'error') {
|
|
4567
|
-
this.showError(status.error || 'Connection error');
|
|
4566
|
+
// If there's a message field, show it as an error
|
|
4567
|
+
if (errorMsg) {
|
|
4568
|
+
this.hideLoading();
|
|
4569
|
+
this.showError(errorMsg);
|
|
4570
|
+
return;
|
|
4568
4571
|
}
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4572
|
+
} else if (message.Data && typeof message.Data === 'string') {
|
|
4573
|
+
this.hideLoading();
|
|
4574
|
+
this.showError(message.Message);
|
|
4575
|
+
return;
|
|
4576
|
+
}
|
|
4573
4577
|
if (this.loadingTimeout) {
|
|
4574
4578
|
clearTimeout(this.loadingTimeout);
|
|
4575
4579
|
this.loadingTimeout = null;
|
|
@@ -4655,126 +4659,6 @@ class MarketDataWidget extends BaseWidget {
|
|
|
4655
4659
|
this.showConnectionQuality(); // Show cache indicator
|
|
4656
4660
|
}
|
|
4657
4661
|
}
|
|
4658
|
-
showNoDataState() {
|
|
4659
|
-
let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
4660
|
-
if (this.isDestroyed) return;
|
|
4661
|
-
try {
|
|
4662
|
-
// Hide loading overlay
|
|
4663
|
-
this.hideLoading();
|
|
4664
|
-
|
|
4665
|
-
// Clear any existing errors
|
|
4666
|
-
this.clearError();
|
|
4667
|
-
|
|
4668
|
-
// Update header with dimmed styling and basic info from data
|
|
4669
|
-
const symbol = data.Symbol || this.symbol;
|
|
4670
|
-
|
|
4671
|
-
// Add null checks for all DOM elements
|
|
4672
|
-
const symbolElement = this.container.querySelector('.symbol');
|
|
4673
|
-
if (symbolElement) {
|
|
4674
|
-
symbolElement.textContent = symbol;
|
|
4675
|
-
}
|
|
4676
|
-
const companyNameElement = this.container.querySelector('.company-name');
|
|
4677
|
-
if (companyNameElement) {
|
|
4678
|
-
companyNameElement.textContent = `${symbol} Inc`;
|
|
4679
|
-
}
|
|
4680
|
-
|
|
4681
|
-
// Set price to $0.00 and add dimmed class
|
|
4682
|
-
const currentPriceElement = this.container.querySelector('.current-price');
|
|
4683
|
-
if (currentPriceElement) {
|
|
4684
|
-
currentPriceElement.textContent = '$0.00';
|
|
4685
|
-
}
|
|
4686
|
-
const changeElement = this.container.querySelector('.price-change');
|
|
4687
|
-
if (changeElement) {
|
|
4688
|
-
const changeValueElement = changeElement.querySelector('.change-value');
|
|
4689
|
-
const changePercentElement = changeElement.querySelector('.change-percent');
|
|
4690
|
-
if (changeValueElement) {
|
|
4691
|
-
changeValueElement.textContent = '+0.00';
|
|
4692
|
-
}
|
|
4693
|
-
if (changePercentElement) {
|
|
4694
|
-
changePercentElement.textContent = ' (0.00%)';
|
|
4695
|
-
}
|
|
4696
|
-
changeElement.classList.remove('positive', 'negative');
|
|
4697
|
-
changeElement.classList.add('neutral');
|
|
4698
|
-
}
|
|
4699
|
-
|
|
4700
|
-
// Add dimmed styling to header
|
|
4701
|
-
const widgetHeader = this.container.querySelector('.widget-header');
|
|
4702
|
-
if (widgetHeader) {
|
|
4703
|
-
widgetHeader.classList.add('dimmed');
|
|
4704
|
-
}
|
|
4705
|
-
|
|
4706
|
-
// Replace the data grid with no data message
|
|
4707
|
-
this.showNoDataMessage(symbol, data);
|
|
4708
|
-
|
|
4709
|
-
// Update footer with current timestamp
|
|
4710
|
-
const lastUpdateElement = this.container.querySelector('.last-update');
|
|
4711
|
-
if (lastUpdateElement) {
|
|
4712
|
-
const timestamp = formatTimestampET();
|
|
4713
|
-
lastUpdateElement.textContent = `Checked: ${timestamp}`;
|
|
4714
|
-
}
|
|
4715
|
-
const dataSourceElement = this.container.querySelector('.data-source');
|
|
4716
|
-
if (dataSourceElement) {
|
|
4717
|
-
dataSourceElement.textContent = 'Source: No data available';
|
|
4718
|
-
}
|
|
4719
|
-
} catch (error) {
|
|
4720
|
-
console.error('Error showing no data state:', error);
|
|
4721
|
-
this.showError('Error displaying no data state');
|
|
4722
|
-
}
|
|
4723
|
-
}
|
|
4724
|
-
showNoDataMessage(symbol) {
|
|
4725
|
-
const dataGrid = this.container.querySelector('.data-grid');
|
|
4726
|
-
|
|
4727
|
-
// Hide the data grid if it exists
|
|
4728
|
-
if (dataGrid) {
|
|
4729
|
-
dataGrid.style.display = 'none';
|
|
4730
|
-
}
|
|
4731
|
-
|
|
4732
|
-
// Remove existing no data message if present
|
|
4733
|
-
const existingNoData = this.container.querySelector('.no-data-state');
|
|
4734
|
-
if (existingNoData) {
|
|
4735
|
-
existingNoData.remove();
|
|
4736
|
-
}
|
|
4737
|
-
|
|
4738
|
-
// Create no data message element - safely without XSS risk
|
|
4739
|
-
const noDataElement = createElement('div', '', 'no-data-state');
|
|
4740
|
-
const noDataContent = createElement('div', '', 'no-data-content');
|
|
4741
|
-
|
|
4742
|
-
// Create icon
|
|
4743
|
-
const iconDiv = document.createElement('div');
|
|
4744
|
-
iconDiv.className = 'no-data-icon';
|
|
4745
|
-
iconDiv.innerHTML = `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
4746
|
-
<circle cx="12" cy="12" r="10" stroke="#9ca3af" stroke-width="2"/>
|
|
4747
|
-
<path d="M12 8v4" stroke="#9ca3af" stroke-width="2" stroke-linecap="round"/>
|
|
4748
|
-
<circle cx="12" cy="16" r="1" fill="#9ca3af"/>
|
|
4749
|
-
</svg>`;
|
|
4750
|
-
noDataContent.appendChild(iconDiv);
|
|
4751
|
-
|
|
4752
|
-
// Create title
|
|
4753
|
-
const title = createElement('h3', 'No Market Data', 'no-data-title');
|
|
4754
|
-
noDataContent.appendChild(title);
|
|
4755
|
-
|
|
4756
|
-
// Create description with sanitized symbol
|
|
4757
|
-
const description = createElement('p', '', 'no-data-description');
|
|
4758
|
-
description.appendChild(document.createTextNode('Market data for '));
|
|
4759
|
-
const symbolStrong = createElement('strong', sanitizeSymbol(symbol));
|
|
4760
|
-
description.appendChild(symbolStrong);
|
|
4761
|
-
description.appendChild(document.createTextNode(' was not found'));
|
|
4762
|
-
noDataContent.appendChild(description);
|
|
4763
|
-
|
|
4764
|
-
// Create guidance
|
|
4765
|
-
const guidance = createElement('p', 'Please check the symbol spelling or try a different symbol', 'no-data-guidance');
|
|
4766
|
-
noDataContent.appendChild(guidance);
|
|
4767
|
-
noDataElement.appendChild(noDataContent);
|
|
4768
|
-
|
|
4769
|
-
// Insert before footer, with null check
|
|
4770
|
-
const footer = this.container.querySelector('.widget-footer');
|
|
4771
|
-
if (footer && footer.parentNode) {
|
|
4772
|
-
footer.parentNode.insertBefore(noDataElement, footer);
|
|
4773
|
-
} else {
|
|
4774
|
-
// Fallback: append to container if footer not found
|
|
4775
|
-
this.container.appendChild(noDataElement);
|
|
4776
|
-
}
|
|
4777
|
-
}
|
|
4778
4662
|
updateWidget(data) {
|
|
4779
4663
|
if (this.isDestroyed) return;
|
|
4780
4664
|
try {
|
|
@@ -5279,7 +5163,20 @@ class NightSessionWidget extends BaseWidget {
|
|
|
5279
5163
|
this.showLoading();
|
|
5280
5164
|
|
|
5281
5165
|
// Validate initial symbol via API to get company info
|
|
5282
|
-
|
|
5166
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting company info
|
|
5167
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
5168
|
+
// Extract company info from first data item
|
|
5169
|
+
if (data && data[0]) {
|
|
5170
|
+
this.companyName = data[0].comp_name || '';
|
|
5171
|
+
this.exchangeName = data[0].market_name || '';
|
|
5172
|
+
this.mic = data[0].mic || '';
|
|
5173
|
+
}
|
|
5174
|
+
});
|
|
5175
|
+
|
|
5176
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
5177
|
+
if (validationSuccess === false) {
|
|
5178
|
+
return; // Error is already shown, don't continue
|
|
5179
|
+
}
|
|
5283
5180
|
|
|
5284
5181
|
// Set timeout to detect no data on initial load
|
|
5285
5182
|
this.loadingTimeout = setTimeout(() => {
|
|
@@ -5296,38 +5193,6 @@ class NightSessionWidget extends BaseWidget {
|
|
|
5296
5193
|
|
|
5297
5194
|
this.subscribeToData();
|
|
5298
5195
|
}
|
|
5299
|
-
async validateInitialSymbol() {
|
|
5300
|
-
try {
|
|
5301
|
-
const apiService = this.wsManager.getApiService();
|
|
5302
|
-
if (!apiService) {
|
|
5303
|
-
if (this.debug) {
|
|
5304
|
-
console.log('[NightSessionWidget] API service not available for initial validation');
|
|
5305
|
-
}
|
|
5306
|
-
return;
|
|
5307
|
-
}
|
|
5308
|
-
const result = await apiService.quotel1(this.symbol);
|
|
5309
|
-
if (result && result.data && result.data[0]) {
|
|
5310
|
-
const symbolData = result.data[0];
|
|
5311
|
-
// Extract company info
|
|
5312
|
-
this.companyName = symbolData.comp_name || '';
|
|
5313
|
-
this.exchangeName = symbolData.market_name || '';
|
|
5314
|
-
this.mic = symbolData.mic || '';
|
|
5315
|
-
if (this.debug) {
|
|
5316
|
-
console.log('[NightSessionWidget] Initial symbol validated:', {
|
|
5317
|
-
symbol: this.symbol,
|
|
5318
|
-
companyName: this.companyName,
|
|
5319
|
-
exchangeName: this.exchangeName,
|
|
5320
|
-
mic: this.mic
|
|
5321
|
-
});
|
|
5322
|
-
}
|
|
5323
|
-
}
|
|
5324
|
-
} catch (error) {
|
|
5325
|
-
if (this.debug) {
|
|
5326
|
-
console.warn('[NightSessionWidget] Initial symbol validation failed:', error);
|
|
5327
|
-
}
|
|
5328
|
-
// Don't throw - let the widget continue with WebSocket data
|
|
5329
|
-
}
|
|
5330
|
-
}
|
|
5331
5196
|
subscribeToData() {
|
|
5332
5197
|
let subscriptionType;
|
|
5333
5198
|
if (this.source === 'bruce') {
|
|
@@ -5347,6 +5212,7 @@ class NightSessionWidget extends BaseWidget {
|
|
|
5347
5212
|
}
|
|
5348
5213
|
handleData(message) {
|
|
5349
5214
|
console.log('DEBUG NIGHT', message);
|
|
5215
|
+
|
|
5350
5216
|
//message = message.data || message.Data
|
|
5351
5217
|
|
|
5352
5218
|
if (this.loadingTimeout) {
|
|
@@ -5354,64 +5220,41 @@ class NightSessionWidget extends BaseWidget {
|
|
|
5354
5220
|
this.loadingTimeout = null;
|
|
5355
5221
|
}
|
|
5356
5222
|
|
|
5357
|
-
//
|
|
5358
|
-
if (message.
|
|
5223
|
+
// Check for error: false and display message if present
|
|
5224
|
+
if (message.error === true) {
|
|
5359
5225
|
if (this.debug) {
|
|
5360
|
-
console.log('[
|
|
5226
|
+
console.log('[Nigh] Received error response:', message.message);
|
|
5361
5227
|
}
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
NotFound: true,
|
|
5367
|
-
message: message.message
|
|
5368
|
-
});
|
|
5369
|
-
} else {
|
|
5228
|
+
const errorMsg = message.message || message.Message;
|
|
5229
|
+
|
|
5230
|
+
// If there's a message field, show it as an error
|
|
5231
|
+
if (errorMsg) {
|
|
5370
5232
|
this.hideLoading();
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
}
|
|
5233
|
+
this.showError(errorMsg);
|
|
5234
|
+
return;
|
|
5374
5235
|
}
|
|
5236
|
+
} else if (message.Data && typeof message.Data === 'string') {
|
|
5237
|
+
this.hideLoading();
|
|
5238
|
+
this.showError(message.Message);
|
|
5375
5239
|
return;
|
|
5376
5240
|
}
|
|
5377
5241
|
|
|
5378
|
-
// Handle
|
|
5242
|
+
// Handle error messages from server (plain text converted to structured format)
|
|
5379
5243
|
if (message.type === 'error') {
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
this.showNoDataState({
|
|
5387
|
-
Symbol: this.symbol,
|
|
5388
|
-
NotFound: true,
|
|
5389
|
-
isAccessError: true,
|
|
5390
|
-
message: errorMsg
|
|
5391
|
-
});
|
|
5392
|
-
} else {
|
|
5393
|
-
this.hideLoading();
|
|
5394
|
-
if (this.debug) {
|
|
5395
|
-
console.log('[NightSessionWidget] Access error but keeping cached data');
|
|
5396
|
-
}
|
|
5397
|
-
}
|
|
5244
|
+
if (this.debug) {
|
|
5245
|
+
console.log('[NightSessionWidget] Received no data message:', message.message);
|
|
5246
|
+
}
|
|
5247
|
+
// Only show no data state if we don't have cached data
|
|
5248
|
+
if (!this.data) {
|
|
5249
|
+
this.showError(errorMsg);
|
|
5398
5250
|
} else {
|
|
5399
|
-
|
|
5400
|
-
if (
|
|
5401
|
-
console.log('
|
|
5402
|
-
this.showError(errorMsg);
|
|
5403
|
-
} else {
|
|
5404
|
-
this.hideLoading();
|
|
5405
|
-
if (this.debug) {
|
|
5406
|
-
console.log('[NightSessionWidget] Error received but keeping cached data:', errorMsg);
|
|
5407
|
-
}
|
|
5251
|
+
this.hideLoading();
|
|
5252
|
+
if (this.debug) {
|
|
5253
|
+
console.log('[MarketDataWidget] Error received but keeping cached data:', errorMsg);
|
|
5408
5254
|
}
|
|
5409
5255
|
}
|
|
5410
5256
|
return;
|
|
5411
5257
|
}
|
|
5412
|
-
|
|
5413
|
-
// Filter for night session data
|
|
5414
|
-
|
|
5415
5258
|
if (Array.isArray(message.data)) {
|
|
5416
5259
|
// First, try to find data matching our symbol regardless of MarketName
|
|
5417
5260
|
const symbolData = message.data.find(item => item.Symbol === this.symbol);
|
|
@@ -6312,36 +6155,20 @@ class OptionsWidget extends BaseWidget {
|
|
|
6312
6155
|
this.showLoading();
|
|
6313
6156
|
|
|
6314
6157
|
// Validate initial symbol via API to get symbol info
|
|
6315
|
-
|
|
6316
|
-
this.
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
if (!apiService) {
|
|
6322
|
-
if (this.debug) {
|
|
6323
|
-
console.log('[OptionsWidget] API service not available for initial validation');
|
|
6324
|
-
}
|
|
6325
|
-
return;
|
|
6158
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting data
|
|
6159
|
+
const validationSuccess = await super.validateInitialSymbol('quoteOptionl1', this.symbol, data => {
|
|
6160
|
+
// Store for use in updateWidget
|
|
6161
|
+
// Note: quoteOptionl1 returns array directly, not wrapped in data field
|
|
6162
|
+
if (data && data[0] && !data[0].error && !data[0].not_found) {
|
|
6163
|
+
this.initialValidationData = data[0];
|
|
6326
6164
|
}
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
if (this.debug) {
|
|
6333
|
-
console.log('[OptionsWidget] Initial symbol validated:', {
|
|
6334
|
-
symbol: this.symbol,
|
|
6335
|
-
underlying: symbolData.Underlying || symbolData.RootSymbol
|
|
6336
|
-
});
|
|
6337
|
-
}
|
|
6338
|
-
}
|
|
6339
|
-
} catch (error) {
|
|
6340
|
-
if (this.debug) {
|
|
6341
|
-
console.warn('[OptionsWidget] Initial symbol validation failed:', error);
|
|
6342
|
-
}
|
|
6343
|
-
// Don't throw - let the widget continue with WebSocket data
|
|
6165
|
+
});
|
|
6166
|
+
|
|
6167
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
6168
|
+
if (validationSuccess === false) {
|
|
6169
|
+
return; // Error is already shown, don't continue
|
|
6344
6170
|
}
|
|
6171
|
+
this.subscribeToData();
|
|
6345
6172
|
}
|
|
6346
6173
|
subscribeToData() {
|
|
6347
6174
|
// Subscribe with symbol for routing
|
|
@@ -38698,7 +38525,20 @@ class IntradayChartWidget extends BaseWidget {
|
|
|
38698
38525
|
}
|
|
38699
38526
|
async initialize() {
|
|
38700
38527
|
// Validate initial symbol via API to get company info
|
|
38701
|
-
|
|
38528
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting company info
|
|
38529
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
38530
|
+
// Extract company info from first data item
|
|
38531
|
+
if (data && data[0]) {
|
|
38532
|
+
this.companyName = data[0].comp_name || '';
|
|
38533
|
+
this.exchangeName = data[0].market_name || '';
|
|
38534
|
+
this.mic = data[0].mic || '';
|
|
38535
|
+
}
|
|
38536
|
+
});
|
|
38537
|
+
|
|
38538
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
38539
|
+
if (validationSuccess === false) {
|
|
38540
|
+
return; // Error is already shown, don't continue
|
|
38541
|
+
}
|
|
38702
38542
|
|
|
38703
38543
|
// Update company name in the header
|
|
38704
38544
|
this.updateCompanyName();
|
|
@@ -38706,38 +38546,6 @@ class IntradayChartWidget extends BaseWidget {
|
|
|
38706
38546
|
// Load chart data
|
|
38707
38547
|
await this.loadChartData();
|
|
38708
38548
|
}
|
|
38709
|
-
async validateInitialSymbol() {
|
|
38710
|
-
try {
|
|
38711
|
-
const apiService = this.wsManager.getApiService();
|
|
38712
|
-
if (!apiService) {
|
|
38713
|
-
if (this.debug) {
|
|
38714
|
-
console.log('[IntradayChartWidget] API service not available for initial validation');
|
|
38715
|
-
}
|
|
38716
|
-
return;
|
|
38717
|
-
}
|
|
38718
|
-
const result = await apiService.quotel1(this.symbol);
|
|
38719
|
-
if (result && result.data && result.data[0]) {
|
|
38720
|
-
const symbolData = result.data[0];
|
|
38721
|
-
// Extract company info
|
|
38722
|
-
this.companyName = symbolData.comp_name || '';
|
|
38723
|
-
this.exchangeName = symbolData.market_name || '';
|
|
38724
|
-
this.mic = symbolData.mic || '';
|
|
38725
|
-
if (this.debug) {
|
|
38726
|
-
console.log('[IntradayChartWidget] Initial symbol validated:', {
|
|
38727
|
-
symbol: this.symbol,
|
|
38728
|
-
companyName: this.companyName,
|
|
38729
|
-
exchangeName: this.exchangeName,
|
|
38730
|
-
mic: this.mic
|
|
38731
|
-
});
|
|
38732
|
-
}
|
|
38733
|
-
}
|
|
38734
|
-
} catch (error) {
|
|
38735
|
-
if (this.debug) {
|
|
38736
|
-
console.warn('[IntradayChartWidget] Initial symbol validation failed:', error);
|
|
38737
|
-
}
|
|
38738
|
-
// Don't throw - let the widget continue with chart data
|
|
38739
|
-
}
|
|
38740
|
-
}
|
|
38741
38549
|
updateCompanyName() {
|
|
38742
38550
|
const companyNameElement = this.container.querySelector('.intraday-company-name');
|
|
38743
38551
|
if (companyNameElement) {
|
|
@@ -39161,19 +38969,12 @@ class IntradayChartWidget extends BaseWidget {
|
|
|
39161
38969
|
// Handle array format (standard night session format)
|
|
39162
38970
|
if (Array.isArray(message.data)) {
|
|
39163
38971
|
console.log('[IntradayChartWidget] Processing array format, length:', message.length);
|
|
39164
|
-
const symbolData = message.find(item => item.Symbol === this.symbol);
|
|
38972
|
+
const symbolData = message.data.find(item => item.Symbol === this.symbol);
|
|
39165
38973
|
console.log('[IntradayChartWidget] Found symbol data:', symbolData);
|
|
39166
38974
|
if (symbolData && !symbolData.NotFound) {
|
|
39167
38975
|
priceData = symbolData;
|
|
39168
38976
|
}
|
|
39169
38977
|
}
|
|
39170
|
-
// Handle wrapped format
|
|
39171
|
-
else if (message.type === 'queryblueoceanl1' || message.type === 'querybrucel1') {
|
|
39172
|
-
console.log('[IntradayChartWidget] Processing wrapped format');
|
|
39173
|
-
if (message['0']?.Symbol === this.symbol && !message['0'].NotFound) {
|
|
39174
|
-
priceData = message['0'];
|
|
39175
|
-
}
|
|
39176
|
-
}
|
|
39177
38978
|
// Handle direct data format
|
|
39178
38979
|
else if (message.Symbol === this.symbol && !message.NotFound) {
|
|
39179
38980
|
console.log('[IntradayChartWidget] Processing direct format');
|
|
@@ -40276,7 +40077,19 @@ class ONBBOLevel2Widget extends BaseWidget {
|
|
|
40276
40077
|
this.showLoading();
|
|
40277
40078
|
|
|
40278
40079
|
// Fetch company info for initial symbol
|
|
40279
|
-
|
|
40080
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting company info
|
|
40081
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
40082
|
+
// Extract company info from first data item
|
|
40083
|
+
if (data && data[0]) {
|
|
40084
|
+
this.companyName = data[0].comp_name || '';
|
|
40085
|
+
this.exchangeName = data[0].market_name || '';
|
|
40086
|
+
}
|
|
40087
|
+
});
|
|
40088
|
+
|
|
40089
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
40090
|
+
if (validationSuccess === false) {
|
|
40091
|
+
return; // Error is already shown, don't continue
|
|
40092
|
+
}
|
|
40280
40093
|
|
|
40281
40094
|
// Set timeout to detect no data on initial load
|
|
40282
40095
|
this.loadingTimeout = setTimeout(() => {
|
|
@@ -40288,36 +40101,6 @@ class ONBBOLevel2Widget extends BaseWidget {
|
|
|
40288
40101
|
}, 10000);
|
|
40289
40102
|
this.subscribeToData();
|
|
40290
40103
|
}
|
|
40291
|
-
async validateInitialSymbol() {
|
|
40292
|
-
try {
|
|
40293
|
-
const apiService = this.wsManager.getApiService();
|
|
40294
|
-
if (!apiService) {
|
|
40295
|
-
if (this.debug) {
|
|
40296
|
-
console.log('[ONBBO L2] API service not available for initial validation');
|
|
40297
|
-
}
|
|
40298
|
-
return;
|
|
40299
|
-
}
|
|
40300
|
-
const result = await apiService.quotel1(this.symbol);
|
|
40301
|
-
if (result && result.data && result.data[0]) {
|
|
40302
|
-
const symbolData = result.data[0];
|
|
40303
|
-
// Extract company info
|
|
40304
|
-
this.companyName = symbolData.comp_name || '';
|
|
40305
|
-
this.exchangeName = symbolData.market_name || '';
|
|
40306
|
-
if (this.debug) {
|
|
40307
|
-
console.log('[ONBBO L2] Initial symbol validated:', {
|
|
40308
|
-
symbol: this.symbol,
|
|
40309
|
-
companyName: this.companyName,
|
|
40310
|
-
exchangeName: this.exchangeName
|
|
40311
|
-
});
|
|
40312
|
-
}
|
|
40313
|
-
}
|
|
40314
|
-
} catch (error) {
|
|
40315
|
-
if (this.debug) {
|
|
40316
|
-
console.warn('[ONBBO L2] Initial symbol validation failed:', error);
|
|
40317
|
-
}
|
|
40318
|
-
// Don't throw - let the widget continue with WebSocket data
|
|
40319
|
-
}
|
|
40320
|
-
}
|
|
40321
40104
|
subscribeToData() {
|
|
40322
40105
|
// Subscribe to ONBBO Level 2 data (order book)
|
|
40323
40106
|
// Use separate widget IDs to avoid "already active" duplicate detection
|
|
@@ -40836,6 +40619,7 @@ class TimeSalesWidget extends BaseWidget {
|
|
|
40836
40619
|
this.styled = options.styled !== undefined ? options.styled : true;
|
|
40837
40620
|
this.maxTrades = options.maxTrades || 20; // Maximum number of trades to display
|
|
40838
40621
|
this.data = new TimeSalesModel([], this.maxTrades);
|
|
40622
|
+
this.data.symbol = this.symbol; // Set the symbol in the data model
|
|
40839
40623
|
this.isDestroyed = false;
|
|
40840
40624
|
this.unsubscribe = null;
|
|
40841
40625
|
this.symbolEditor = null;
|
|
@@ -40847,6 +40631,7 @@ class TimeSalesWidget extends BaseWidget {
|
|
|
40847
40631
|
|
|
40848
40632
|
// Create widget structure
|
|
40849
40633
|
this.createWidgetStructure();
|
|
40634
|
+
this.initializeSymbolEditor();
|
|
40850
40635
|
|
|
40851
40636
|
// Initialize the widget
|
|
40852
40637
|
this.initialize();
|
|
@@ -40863,7 +40648,26 @@ class TimeSalesWidget extends BaseWidget {
|
|
|
40863
40648
|
}
|
|
40864
40649
|
}
|
|
40865
40650
|
this.addStyles();
|
|
40866
|
-
this.setupSymbolEditor();
|
|
40651
|
+
//this.setupSymbolEditor();
|
|
40652
|
+
}
|
|
40653
|
+
initializeSymbolEditor() {
|
|
40654
|
+
// Initialize symbol editor with stock symbol validation
|
|
40655
|
+
this.symbolEditor = new SymbolEditor(this, {
|
|
40656
|
+
maxLength: 10,
|
|
40657
|
+
placeholder: 'Enter symbol...',
|
|
40658
|
+
//validator: this.validateStockSymbol.bind(this),
|
|
40659
|
+
onSymbolChange: this.handleSymbolChange.bind(this),
|
|
40660
|
+
debug: this.debug,
|
|
40661
|
+
autoUppercase: true,
|
|
40662
|
+
symbolType: 'quotel1'
|
|
40663
|
+
});
|
|
40664
|
+
|
|
40665
|
+
// Set initial symbol
|
|
40666
|
+
const symbolElement = this.container.querySelector('.symbol');
|
|
40667
|
+
if (symbolElement) {
|
|
40668
|
+
symbolElement.textContent = this.symbol;
|
|
40669
|
+
symbolElement.dataset.originalSymbol = this.symbol;
|
|
40670
|
+
}
|
|
40867
40671
|
}
|
|
40868
40672
|
addStyles() {
|
|
40869
40673
|
// Inject styles from the styles file into document head
|
|
@@ -40886,6 +40690,8 @@ class TimeSalesWidget extends BaseWidget {
|
|
|
40886
40690
|
// Keep editor open when clicking buttons
|
|
40887
40691
|
symbolType: 'quotel1' // Use standard quote validation
|
|
40888
40692
|
});
|
|
40693
|
+
|
|
40694
|
+
//console.log('SETUP COMPLETE')
|
|
40889
40695
|
}
|
|
40890
40696
|
async handleSymbolChange(newSymbol, oldSymbol) {
|
|
40891
40697
|
let validationData = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
|
@@ -40988,7 +40794,19 @@ class TimeSalesWidget extends BaseWidget {
|
|
|
40988
40794
|
this.showLoading();
|
|
40989
40795
|
|
|
40990
40796
|
// Validate initial symbol via API to get company info
|
|
40991
|
-
|
|
40797
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting company info
|
|
40798
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
40799
|
+
// Extract company info from first data item
|
|
40800
|
+
if (data && data[0]) {
|
|
40801
|
+
this.companyName = data[0].comp_name || '';
|
|
40802
|
+
this.exchangeName = data[0].market_name || '';
|
|
40803
|
+
}
|
|
40804
|
+
});
|
|
40805
|
+
|
|
40806
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
40807
|
+
if (validationSuccess === false) {
|
|
40808
|
+
return; // Error is already shown, don't continue
|
|
40809
|
+
}
|
|
40992
40810
|
|
|
40993
40811
|
// Set timeout to detect no data on initial load
|
|
40994
40812
|
this.loadingTimeout = setTimeout(() => {
|
|
@@ -41001,36 +40819,6 @@ class TimeSalesWidget extends BaseWidget {
|
|
|
41001
40819
|
|
|
41002
40820
|
this.subscribeToData();
|
|
41003
40821
|
}
|
|
41004
|
-
async validateInitialSymbol() {
|
|
41005
|
-
try {
|
|
41006
|
-
const apiService = this.wsManager.getApiService();
|
|
41007
|
-
if (!apiService) {
|
|
41008
|
-
if (this.debug) {
|
|
41009
|
-
console.log('[TimeSalesWidget] API service not available for initial validation');
|
|
41010
|
-
}
|
|
41011
|
-
return;
|
|
41012
|
-
}
|
|
41013
|
-
const result = await apiService.quotel1(this.symbol);
|
|
41014
|
-
if (result && result.data && result.data[0]) {
|
|
41015
|
-
const symbolData = result.data[0];
|
|
41016
|
-
// Extract company info
|
|
41017
|
-
this.companyName = symbolData.comp_name || '';
|
|
41018
|
-
this.exchangeName = symbolData.market_name || '';
|
|
41019
|
-
if (this.debug) {
|
|
41020
|
-
console.log('[TimeSalesWidget] Initial symbol validated:', {
|
|
41021
|
-
symbol: this.symbol,
|
|
41022
|
-
companyName: this.companyName,
|
|
41023
|
-
exchangeName: this.exchangeName
|
|
41024
|
-
});
|
|
41025
|
-
}
|
|
41026
|
-
}
|
|
41027
|
-
} catch (error) {
|
|
41028
|
-
if (this.debug) {
|
|
41029
|
-
console.warn('[TimeSalesWidget] Initial symbol validation failed:', error);
|
|
41030
|
-
}
|
|
41031
|
-
// Don't throw - let the widget continue with WebSocket data
|
|
41032
|
-
}
|
|
41033
|
-
}
|
|
41034
40822
|
subscribeToData() {
|
|
41035
40823
|
// Subscribe to Time & Sales data (using queryonbbotimesale)
|
|
41036
40824
|
this.unsubscribe = this.wsManager.subscribe(this.widgetId, ['queryonbbotimesale'],
|
|
@@ -41046,6 +40834,9 @@ class TimeSalesWidget extends BaseWidget {
|
|
|
41046
40834
|
clearTimeout(this.loadingTimeout);
|
|
41047
40835
|
this.loadingTimeout = null;
|
|
41048
40836
|
}
|
|
40837
|
+
if (message.type != 'queryonbbotimesale') {
|
|
40838
|
+
return;
|
|
40839
|
+
}
|
|
41049
40840
|
|
|
41050
40841
|
// Handle error messages
|
|
41051
40842
|
if (message.type === 'error') {
|
|
@@ -41058,30 +40849,16 @@ class TimeSalesWidget extends BaseWidget {
|
|
|
41058
40849
|
}
|
|
41059
40850
|
|
|
41060
40851
|
// Handle Time & Sales data - Array of trades
|
|
41061
|
-
if (Array.isArray(message)) {
|
|
40852
|
+
if (Array.isArray(message.data)) {
|
|
41062
40853
|
if (this.debug) {
|
|
41063
40854
|
console.log('[TimeSalesWidget] Received trades array:', message);
|
|
41064
40855
|
}
|
|
41065
40856
|
|
|
41066
40857
|
// Add new trades to existing data
|
|
41067
|
-
this.data.addTrades(message);
|
|
40858
|
+
this.data.addTrades(message.data);
|
|
41068
40859
|
this.updateWidget();
|
|
41069
40860
|
return;
|
|
41070
40861
|
}
|
|
41071
|
-
|
|
41072
|
-
// Handle wrapped format
|
|
41073
|
-
if ((message.type === 'queryonbbotimesale' || message.type === 'querytns') && message['0']) {
|
|
41074
|
-
if (Array.isArray(message['0'])) {
|
|
41075
|
-
if (this.debug) {
|
|
41076
|
-
console.log('[TimeSalesWidget] Received wrapped trades array:', message['0']);
|
|
41077
|
-
}
|
|
41078
|
-
|
|
41079
|
-
// Add new trades to existing data
|
|
41080
|
-
this.data.addTrades(message['0']);
|
|
41081
|
-
this.updateWidget();
|
|
41082
|
-
return;
|
|
41083
|
-
}
|
|
41084
|
-
}
|
|
41085
40862
|
if (this.debug) {
|
|
41086
40863
|
console.log('[TimeSalesWidget] Unexpected message format:', message);
|
|
41087
40864
|
}
|
|
@@ -41308,7 +41085,15 @@ class ApiService {
|
|
|
41308
41085
|
try {
|
|
41309
41086
|
const response = await fetch(url, config);
|
|
41310
41087
|
if (!response.ok) {
|
|
41311
|
-
|
|
41088
|
+
// Try to get response body for error details
|
|
41089
|
+
let errorBody = '';
|
|
41090
|
+
try {
|
|
41091
|
+
errorBody = await response.text();
|
|
41092
|
+
console.error(`[ApiService] HTTP ${response.status} - Response body:`, errorBody);
|
|
41093
|
+
} catch (e) {
|
|
41094
|
+
console.error(`[ApiService] Could not read error response body:`, e);
|
|
41095
|
+
}
|
|
41096
|
+
throw new Error(`HTTP error! status: ${response.status}, body: ${errorBody}`);
|
|
41312
41097
|
}
|
|
41313
41098
|
return await response.json();
|
|
41314
41099
|
} catch (error) {
|
|
@@ -42068,8 +41853,8 @@ class WebSocketManager {
|
|
|
42068
41853
|
*/
|
|
42069
41854
|
_normalizeMessage(message) {
|
|
42070
41855
|
// Check for error field (case-insensitive)
|
|
42071
|
-
const errorField = message.error
|
|
42072
|
-
const typeField = message.type
|
|
41856
|
+
const errorField = message.error;
|
|
41857
|
+
const typeField = message.type;
|
|
42073
41858
|
const messageField = message.message || message.Message;
|
|
42074
41859
|
|
|
42075
41860
|
// Handle explicit error messages - route based on type field
|
|
@@ -42109,54 +41894,16 @@ class WebSocketManager {
|
|
|
42109
41894
|
}
|
|
42110
41895
|
}
|
|
42111
41896
|
|
|
42112
|
-
// Check for new format: has 'type'/'Type' AND 'data'/'Data' fields
|
|
42113
|
-
const dataField = message.data || message.Data;
|
|
42114
|
-
if (typeField && dataField !== undefined) {
|
|
42115
|
-
if (this.config.debug) {
|
|
42116
|
-
console.log(`[WebSocketManager] Detected new message format with type: ${typeField}`);
|
|
42117
|
-
}
|
|
42118
|
-
|
|
42119
|
-
// Extract the actual data and add the type to it for routing
|
|
42120
|
-
let normalizedData;
|
|
42121
|
-
if (Array.isArray(dataField)) {
|
|
42122
|
-
// If data is an array, process each item
|
|
42123
|
-
normalizedData = dataField;
|
|
42124
|
-
// Add type info to the structure for routing
|
|
42125
|
-
if (normalizedData.length > 0) {
|
|
42126
|
-
normalizedData._messageType = typeField;
|
|
42127
|
-
}
|
|
42128
|
-
} else if (typeof dataField === 'object' && dataField !== null) {
|
|
42129
|
-
// If data is an object, use it directly
|
|
42130
|
-
normalizedData = dataField;
|
|
42131
|
-
// Add type info for routing
|
|
42132
|
-
normalizedData._messageType = typeField;
|
|
42133
|
-
} else {
|
|
42134
|
-
// Primitive value, wrap it
|
|
42135
|
-
normalizedData = {
|
|
42136
|
-
value: dataField,
|
|
42137
|
-
_messageType: typeField
|
|
42138
|
-
};
|
|
42139
|
-
}
|
|
42140
|
-
|
|
42141
|
-
// Only add type field if the normalized data doesn't already have one
|
|
42142
|
-
if (typeof normalizedData === 'object' && !normalizedData.type && !normalizedData.Type) {
|
|
42143
|
-
normalizedData.type = typeField;
|
|
42144
|
-
}
|
|
42145
|
-
return normalizedData;
|
|
42146
|
-
}
|
|
42147
|
-
|
|
42148
41897
|
// Old format or no type/data structure, return as is
|
|
42149
41898
|
return message;
|
|
42150
41899
|
}
|
|
42151
41900
|
_routeMessage(message) {
|
|
42152
41901
|
//console.log('message', message);
|
|
42153
41902
|
|
|
42154
|
-
|
|
42155
|
-
|
|
42156
|
-
|
|
42157
|
-
|
|
42158
|
-
return; // Don't route empty arrays
|
|
42159
|
-
}
|
|
41903
|
+
// IMPORTANT: Don't filter out empty arrays here - route them based on message.type
|
|
41904
|
+
// Widgets need to receive empty arrays to clear loading states and show "no data" messages
|
|
41905
|
+
// The message structure is: { type: 'queryoptionchain', data: [] }
|
|
41906
|
+
// We need to route based on the 'type' field, not the data content
|
|
42160
41907
|
|
|
42161
41908
|
// Cache the message for later use by new subscribers
|
|
42162
41909
|
this._cacheMessage(message);
|
|
@@ -42255,22 +42002,28 @@ class WebSocketManager {
|
|
|
42255
42002
|
_getRelevantWidgets(message) {
|
|
42256
42003
|
const relevantWidgets = new Set();
|
|
42257
42004
|
|
|
42005
|
+
// OPTIMIZATION: Extract message type once from the entire message, not from each data item
|
|
42006
|
+
// Message structure: { type: 'queryoptionchain', data: [] }
|
|
42007
|
+
let dataField = message.Data || message.data;
|
|
42008
|
+
const messageType = message.type || this._extractMessageType(dataField);
|
|
42009
|
+
|
|
42258
42010
|
// Handle array messages
|
|
42259
|
-
this._addRelevantWidgetsForItem(message, relevantWidgets);
|
|
42011
|
+
this._addRelevantWidgetsForItem(message, relevantWidgets, messageType);
|
|
42260
42012
|
return relevantWidgets;
|
|
42261
42013
|
}
|
|
42262
|
-
_addRelevantWidgetsForItem(item, relevantWidgets) {
|
|
42014
|
+
_addRelevantWidgetsForItem(item, relevantWidgets, messageType) {
|
|
42263
42015
|
// If item has Data array, process it
|
|
42264
42016
|
if (item.Data && Array.isArray(item.Data)) {
|
|
42265
42017
|
//console.log('Data array found with length:', item.Data.length);
|
|
42266
42018
|
item.Data.forEach(dataItem => {
|
|
42267
|
-
|
|
42019
|
+
// Pass messageType from parent message, don't extract from each data item
|
|
42020
|
+
this._processDataItem(dataItem, relevantWidgets, messageType);
|
|
42268
42021
|
});
|
|
42269
42022
|
} else if (item && item[0] && item[0].Strike !== undefined && item[0].Expire && !item[0].underlyingSymbol) {
|
|
42270
42023
|
this._processOptionChainData(item, relevantWidgets);
|
|
42271
42024
|
} else {
|
|
42272
|
-
// Process single item
|
|
42273
|
-
this._processDataItem(item, relevantWidgets);
|
|
42025
|
+
// Process single item - messageType already extracted from message
|
|
42026
|
+
this._processDataItem(item, relevantWidgets, messageType);
|
|
42274
42027
|
}
|
|
42275
42028
|
}
|
|
42276
42029
|
|
|
@@ -42314,12 +42067,11 @@ class WebSocketManager {
|
|
|
42314
42067
|
}
|
|
42315
42068
|
}
|
|
42316
42069
|
}
|
|
42317
|
-
_processDataItem(dataItem, relevantWidgets) {
|
|
42070
|
+
_processDataItem(dataItem, relevantWidgets, messageType) {
|
|
42318
42071
|
// Process individual data items and route to appropriate widgets
|
|
42319
42072
|
// Option chain arrays are handled separately by _processOptionChainData
|
|
42320
42073
|
|
|
42321
42074
|
const symbol = this._extractSymbol(dataItem);
|
|
42322
|
-
const messageType = this._extractMessageType(dataItem);
|
|
42323
42075
|
if (this.config.debug) {
|
|
42324
42076
|
console.log('[WebSocketManager] Processing data item:', {
|
|
42325
42077
|
symbol,
|
|
@@ -42479,25 +42231,15 @@ class WebSocketManager {
|
|
|
42479
42231
|
return symbolMappings[extracted] || extracted;
|
|
42480
42232
|
}
|
|
42481
42233
|
_extractMessageType(item) {
|
|
42482
|
-
//
|
|
42483
|
-
if (item._messageType) {
|
|
42484
|
-
return item._messageType;
|
|
42485
|
-
}
|
|
42486
|
-
|
|
42487
|
-
// Determine message type based on content
|
|
42488
|
-
if (item.type) return item.type;
|
|
42489
|
-
|
|
42490
|
-
// Infer type from data structure
|
|
42234
|
+
// FALLBACK: Infer type from data structure (for legacy/malformed messages)
|
|
42491
42235
|
|
|
42492
42236
|
// IMPORTANT: Check for MMID field FIRST to distinguish ONBBO L2 from L1
|
|
42493
42237
|
// Level 2 ONBBO data - array of MMID objects or object with MMID field
|
|
42494
42238
|
if (Array.isArray(item) && item.length > 0 && item[0].MMID !== undefined) {
|
|
42495
42239
|
return 'queryonbbol2';
|
|
42496
42240
|
}
|
|
42497
|
-
|
|
42498
|
-
|
|
42499
|
-
if (item.MMID !== undefined) {
|
|
42500
|
-
return 'queryonbbol2';
|
|
42241
|
+
if (Array.isArray(item) && item.length > 0 && item[0].Strike !== undefined) {
|
|
42242
|
+
return 'queryoptionchain';
|
|
42501
42243
|
}
|
|
42502
42244
|
|
|
42503
42245
|
// Check for Source field AFTER MMID check
|