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.js
CHANGED
|
@@ -412,6 +412,85 @@
|
|
|
412
412
|
console.log(`[${this.constructor.name}] Connection failed after ${status.maxAttempts} attempts`);
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Validate initial symbol with the API and handle access/permission errors
|
|
418
|
+
* This is a reusable method for all widgets that need to validate symbols on initialization
|
|
419
|
+
*
|
|
420
|
+
* @param {string} apiMethod - The API method name to call (e.g., 'quotel1', 'quoteOptionl1')
|
|
421
|
+
* @param {string} symbol - The symbol to validate
|
|
422
|
+
* @param {Function} onSuccess - Callback when validation succeeds with data, receives (data, result)
|
|
423
|
+
* @returns {boolean} - Returns false if access denied (stops initialization), true otherwise
|
|
424
|
+
*/
|
|
425
|
+
async validateInitialSymbol(apiMethod, symbol) {
|
|
426
|
+
let onSuccess = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
|
427
|
+
try {
|
|
428
|
+
const apiService = this.wsManager?.getApiService();
|
|
429
|
+
if (!apiService) {
|
|
430
|
+
if (this.debug) {
|
|
431
|
+
console.log(`[${this.constructor.name}] API service not available for initial validation`);
|
|
432
|
+
}
|
|
433
|
+
return true; // Continue without validation
|
|
434
|
+
}
|
|
435
|
+
if (!apiService[apiMethod]) {
|
|
436
|
+
console.error(`[${this.constructor.name}] API method ${apiMethod} does not exist`);
|
|
437
|
+
return true; // Continue without validation
|
|
438
|
+
}
|
|
439
|
+
const result = await apiService[apiMethod](symbol);
|
|
440
|
+
if (result && result.data && result.data.length > 0) {
|
|
441
|
+
if (this.debug) {
|
|
442
|
+
console.log(`[${this.constructor.name}] Initial symbol validated:`, {
|
|
443
|
+
symbol,
|
|
444
|
+
method: apiMethod,
|
|
445
|
+
dataCount: result.data.length
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Call success callback if provided
|
|
450
|
+
if (onSuccess && typeof onSuccess === 'function') {
|
|
451
|
+
onSuccess(result.data, result);
|
|
452
|
+
}
|
|
453
|
+
return true; // Validation successful
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// No data returned - might be access issue, but continue anyway
|
|
457
|
+
if (this.debug) {
|
|
458
|
+
console.log(`[${this.constructor.name}] No data returned from ${apiMethod} for ${symbol}`);
|
|
459
|
+
}
|
|
460
|
+
return true; // Continue anyway, WebSocket might still work
|
|
461
|
+
} catch (error) {
|
|
462
|
+
if (this.debug) {
|
|
463
|
+
console.warn(`[${this.constructor.name}] Initial symbol validation failed:`, error);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Check if it's an access/permission error
|
|
467
|
+
const errorMessage = (error.message || '').toLowerCase();
|
|
468
|
+
const isAccessError = errorMessage.includes('400') ||
|
|
469
|
+
// Bad Request (often used for access issues)
|
|
470
|
+
errorMessage.includes('401') ||
|
|
471
|
+
// Unauthorized
|
|
472
|
+
errorMessage.includes('403') ||
|
|
473
|
+
// Forbidden
|
|
474
|
+
errorMessage.includes('forbidden') || errorMessage.includes('unauthorized') || errorMessage.includes('no access') || errorMessage.includes('no opra access') ||
|
|
475
|
+
// Specific OPRA access denial
|
|
476
|
+
errorMessage.includes('permission denied') || errorMessage.includes('access denied');
|
|
477
|
+
if (isAccessError) {
|
|
478
|
+
// Hide loading and show access error
|
|
479
|
+
this.hideLoading();
|
|
480
|
+
|
|
481
|
+
// Extract more specific error message if available
|
|
482
|
+
let userMessage = `Access denied: You don't have permission to view data for ${symbol}`;
|
|
483
|
+
if (errorMessage.includes('no opra access')) {
|
|
484
|
+
userMessage = `Access denied: You don't have OPRA access for option ${symbol}`;
|
|
485
|
+
}
|
|
486
|
+
this.showError(userMessage);
|
|
487
|
+
return false; // Stop initialization
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// For other errors, continue with WebSocket (might still work)
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
415
494
|
}
|
|
416
495
|
|
|
417
496
|
// src/models/MarketDataModel.js
|
|
@@ -2510,28 +2589,6 @@
|
|
|
2510
2589
|
font-size: 1.71em;
|
|
2511
2590
|
font-weight: 700;
|
|
2512
2591
|
color: #111827;
|
|
2513
|
-
cursor: pointer;
|
|
2514
|
-
transition: all 0.2s ease;
|
|
2515
|
-
position: relative;
|
|
2516
|
-
}
|
|
2517
|
-
|
|
2518
|
-
.time-sales-widget .symbol.editable-symbol:hover {
|
|
2519
|
-
opacity: 0.8;
|
|
2520
|
-
transform: scale(1.02);
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
.time-sales-widget .symbol.editable-symbol:hover::after {
|
|
2524
|
-
content: "✎";
|
|
2525
|
-
position: absolute;
|
|
2526
|
-
top: -8px;
|
|
2527
|
-
right: -8px;
|
|
2528
|
-
background: #3b82f6;
|
|
2529
|
-
color: white;
|
|
2530
|
-
font-size: 10px;
|
|
2531
|
-
padding: 2px 4px;
|
|
2532
|
-
border-radius: 4px;
|
|
2533
|
-
opacity: 0.8;
|
|
2534
|
-
pointer-events: none;
|
|
2535
2592
|
}
|
|
2536
2593
|
|
|
2537
2594
|
/* ========================================
|
|
@@ -4483,99 +4540,46 @@
|
|
|
4483
4540
|
this.showLoading();
|
|
4484
4541
|
|
|
4485
4542
|
// Validate initial symbol via API to get company info
|
|
4486
|
-
|
|
4487
|
-
this.
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
const apiService = this.wsManager.getApiService();
|
|
4492
|
-
if (!apiService) {
|
|
4493
|
-
if (this.debug) {
|
|
4494
|
-
console.log('[MarketDataWidget] API service not available for initial validation');
|
|
4495
|
-
}
|
|
4496
|
-
return;
|
|
4497
|
-
}
|
|
4498
|
-
const result = await apiService.quotel1(this.symbol);
|
|
4499
|
-
if (result && result.data && result.data[0]) {
|
|
4500
|
-
const symbolData = result.data[0];
|
|
4501
|
-
// Store for use in updateWidget
|
|
4502
|
-
this.initialValidationData = symbolData;
|
|
4503
|
-
if (this.debug) {
|
|
4504
|
-
console.log('[MarketDataWidget] Initial symbol validated:', {
|
|
4505
|
-
symbol: this.symbol,
|
|
4506
|
-
companyName: symbolData.comp_name,
|
|
4507
|
-
exchangeName: symbolData.market_name
|
|
4508
|
-
});
|
|
4509
|
-
}
|
|
4543
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting data
|
|
4544
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
4545
|
+
// Store for use in updateWidget
|
|
4546
|
+
if (data && data[0]) {
|
|
4547
|
+
this.initialValidationData = data[0];
|
|
4510
4548
|
}
|
|
4511
|
-
}
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
//
|
|
4549
|
+
});
|
|
4550
|
+
|
|
4551
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
4552
|
+
if (validationSuccess === false) {
|
|
4553
|
+
return; // Error is already shown, don't continue
|
|
4516
4554
|
}
|
|
4555
|
+
this.subscribeToData();
|
|
4517
4556
|
}
|
|
4518
4557
|
subscribeToData() {
|
|
4519
4558
|
// Subscribe with symbol for routing
|
|
4520
4559
|
this.unsubscribe = this.wsManager.subscribe(this.widgetId, ['queryl1'], this.handleMessage.bind(this), this.symbol // Pass symbol for routing
|
|
4521
4560
|
);
|
|
4522
|
-
|
|
4523
|
-
// Send subscription message
|
|
4524
|
-
/* this.wsManager.send({
|
|
4525
|
-
type: 'queryl1',
|
|
4526
|
-
symbol: this.symbol
|
|
4527
|
-
}); */
|
|
4528
4561
|
}
|
|
4562
|
+
handleData(message) {
|
|
4563
|
+
//console.log('DEBUG', message)
|
|
4529
4564
|
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
if (this.debug) {
|
|
4535
|
-
console.log('[MarketDataWidget] Received:', event, data);
|
|
4536
|
-
}
|
|
4537
|
-
if (event === 'connection') {
|
|
4538
|
-
this.handleConnectionStatus(data);
|
|
4539
|
-
return;
|
|
4540
|
-
}
|
|
4541
|
-
if (event === 'data') {
|
|
4542
|
-
this.handleData(data);
|
|
4543
|
-
}
|
|
4544
|
-
if (event === 'session_revoked') {
|
|
4545
|
-
if (data.status === 'attempting_relogin') {
|
|
4546
|
-
this.showLoading();
|
|
4547
|
-
} else if (data.status === 'relogin_failed') {
|
|
4548
|
-
this.showError(data.error);
|
|
4549
|
-
} else if (data.status === 'relogin_successful') {
|
|
4550
|
-
this.hideLoading();
|
|
4551
|
-
}
|
|
4552
|
-
return;
|
|
4553
|
-
}
|
|
4554
|
-
} catch (error) {
|
|
4555
|
-
console.error('[MarketDataWidget] Error handling message:', error);
|
|
4556
|
-
this.showError('Error processing data');
|
|
4565
|
+
// Check for error: false and display message if present
|
|
4566
|
+
if (message.error === true) {
|
|
4567
|
+
if (this.debug) {
|
|
4568
|
+
console.log('[MarketDataWidget] Received error response:', message.message);
|
|
4557
4569
|
}
|
|
4558
|
-
|
|
4570
|
+
const errorMsg = message.message || message.Message;
|
|
4559
4571
|
|
|
4560
|
-
|
|
4561
|
-
if (
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
// Re-send subscription when reconnected
|
|
4566
|
-
this.wsManager.send({
|
|
4567
|
-
type: 'queryl1',
|
|
4568
|
-
symbol: this.symbol
|
|
4569
|
-
});
|
|
4570
|
-
} else if (status.status === 'disconnected') {
|
|
4571
|
-
this.showError('Disconnected from data service');
|
|
4572
|
-
} else if (status.status === 'error') {
|
|
4573
|
-
this.showError(status.error || 'Connection error');
|
|
4572
|
+
// If there's a message field, show it as an error
|
|
4573
|
+
if (errorMsg) {
|
|
4574
|
+
this.hideLoading();
|
|
4575
|
+
this.showError(errorMsg);
|
|
4576
|
+
return;
|
|
4574
4577
|
}
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4578
|
+
} else if (message.Data && typeof message.Data === 'string') {
|
|
4579
|
+
this.hideLoading();
|
|
4580
|
+
this.showError(message.Message);
|
|
4581
|
+
return;
|
|
4582
|
+
}
|
|
4579
4583
|
if (this.loadingTimeout) {
|
|
4580
4584
|
clearTimeout(this.loadingTimeout);
|
|
4581
4585
|
this.loadingTimeout = null;
|
|
@@ -4661,126 +4665,6 @@
|
|
|
4661
4665
|
this.showConnectionQuality(); // Show cache indicator
|
|
4662
4666
|
}
|
|
4663
4667
|
}
|
|
4664
|
-
showNoDataState() {
|
|
4665
|
-
let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
4666
|
-
if (this.isDestroyed) return;
|
|
4667
|
-
try {
|
|
4668
|
-
// Hide loading overlay
|
|
4669
|
-
this.hideLoading();
|
|
4670
|
-
|
|
4671
|
-
// Clear any existing errors
|
|
4672
|
-
this.clearError();
|
|
4673
|
-
|
|
4674
|
-
// Update header with dimmed styling and basic info from data
|
|
4675
|
-
const symbol = data.Symbol || this.symbol;
|
|
4676
|
-
|
|
4677
|
-
// Add null checks for all DOM elements
|
|
4678
|
-
const symbolElement = this.container.querySelector('.symbol');
|
|
4679
|
-
if (symbolElement) {
|
|
4680
|
-
symbolElement.textContent = symbol;
|
|
4681
|
-
}
|
|
4682
|
-
const companyNameElement = this.container.querySelector('.company-name');
|
|
4683
|
-
if (companyNameElement) {
|
|
4684
|
-
companyNameElement.textContent = `${symbol} Inc`;
|
|
4685
|
-
}
|
|
4686
|
-
|
|
4687
|
-
// Set price to $0.00 and add dimmed class
|
|
4688
|
-
const currentPriceElement = this.container.querySelector('.current-price');
|
|
4689
|
-
if (currentPriceElement) {
|
|
4690
|
-
currentPriceElement.textContent = '$0.00';
|
|
4691
|
-
}
|
|
4692
|
-
const changeElement = this.container.querySelector('.price-change');
|
|
4693
|
-
if (changeElement) {
|
|
4694
|
-
const changeValueElement = changeElement.querySelector('.change-value');
|
|
4695
|
-
const changePercentElement = changeElement.querySelector('.change-percent');
|
|
4696
|
-
if (changeValueElement) {
|
|
4697
|
-
changeValueElement.textContent = '+0.00';
|
|
4698
|
-
}
|
|
4699
|
-
if (changePercentElement) {
|
|
4700
|
-
changePercentElement.textContent = ' (0.00%)';
|
|
4701
|
-
}
|
|
4702
|
-
changeElement.classList.remove('positive', 'negative');
|
|
4703
|
-
changeElement.classList.add('neutral');
|
|
4704
|
-
}
|
|
4705
|
-
|
|
4706
|
-
// Add dimmed styling to header
|
|
4707
|
-
const widgetHeader = this.container.querySelector('.widget-header');
|
|
4708
|
-
if (widgetHeader) {
|
|
4709
|
-
widgetHeader.classList.add('dimmed');
|
|
4710
|
-
}
|
|
4711
|
-
|
|
4712
|
-
// Replace the data grid with no data message
|
|
4713
|
-
this.showNoDataMessage(symbol, data);
|
|
4714
|
-
|
|
4715
|
-
// Update footer with current timestamp
|
|
4716
|
-
const lastUpdateElement = this.container.querySelector('.last-update');
|
|
4717
|
-
if (lastUpdateElement) {
|
|
4718
|
-
const timestamp = formatTimestampET();
|
|
4719
|
-
lastUpdateElement.textContent = `Checked: ${timestamp}`;
|
|
4720
|
-
}
|
|
4721
|
-
const dataSourceElement = this.container.querySelector('.data-source');
|
|
4722
|
-
if (dataSourceElement) {
|
|
4723
|
-
dataSourceElement.textContent = 'Source: No data available';
|
|
4724
|
-
}
|
|
4725
|
-
} catch (error) {
|
|
4726
|
-
console.error('Error showing no data state:', error);
|
|
4727
|
-
this.showError('Error displaying no data state');
|
|
4728
|
-
}
|
|
4729
|
-
}
|
|
4730
|
-
showNoDataMessage(symbol) {
|
|
4731
|
-
const dataGrid = this.container.querySelector('.data-grid');
|
|
4732
|
-
|
|
4733
|
-
// Hide the data grid if it exists
|
|
4734
|
-
if (dataGrid) {
|
|
4735
|
-
dataGrid.style.display = 'none';
|
|
4736
|
-
}
|
|
4737
|
-
|
|
4738
|
-
// Remove existing no data message if present
|
|
4739
|
-
const existingNoData = this.container.querySelector('.no-data-state');
|
|
4740
|
-
if (existingNoData) {
|
|
4741
|
-
existingNoData.remove();
|
|
4742
|
-
}
|
|
4743
|
-
|
|
4744
|
-
// Create no data message element - safely without XSS risk
|
|
4745
|
-
const noDataElement = createElement('div', '', 'no-data-state');
|
|
4746
|
-
const noDataContent = createElement('div', '', 'no-data-content');
|
|
4747
|
-
|
|
4748
|
-
// Create icon
|
|
4749
|
-
const iconDiv = document.createElement('div');
|
|
4750
|
-
iconDiv.className = 'no-data-icon';
|
|
4751
|
-
iconDiv.innerHTML = `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
4752
|
-
<circle cx="12" cy="12" r="10" stroke="#9ca3af" stroke-width="2"/>
|
|
4753
|
-
<path d="M12 8v4" stroke="#9ca3af" stroke-width="2" stroke-linecap="round"/>
|
|
4754
|
-
<circle cx="12" cy="16" r="1" fill="#9ca3af"/>
|
|
4755
|
-
</svg>`;
|
|
4756
|
-
noDataContent.appendChild(iconDiv);
|
|
4757
|
-
|
|
4758
|
-
// Create title
|
|
4759
|
-
const title = createElement('h3', 'No Market Data', 'no-data-title');
|
|
4760
|
-
noDataContent.appendChild(title);
|
|
4761
|
-
|
|
4762
|
-
// Create description with sanitized symbol
|
|
4763
|
-
const description = createElement('p', '', 'no-data-description');
|
|
4764
|
-
description.appendChild(document.createTextNode('Market data for '));
|
|
4765
|
-
const symbolStrong = createElement('strong', sanitizeSymbol(symbol));
|
|
4766
|
-
description.appendChild(symbolStrong);
|
|
4767
|
-
description.appendChild(document.createTextNode(' was not found'));
|
|
4768
|
-
noDataContent.appendChild(description);
|
|
4769
|
-
|
|
4770
|
-
// Create guidance
|
|
4771
|
-
const guidance = createElement('p', 'Please check the symbol spelling or try a different symbol', 'no-data-guidance');
|
|
4772
|
-
noDataContent.appendChild(guidance);
|
|
4773
|
-
noDataElement.appendChild(noDataContent);
|
|
4774
|
-
|
|
4775
|
-
// Insert before footer, with null check
|
|
4776
|
-
const footer = this.container.querySelector('.widget-footer');
|
|
4777
|
-
if (footer && footer.parentNode) {
|
|
4778
|
-
footer.parentNode.insertBefore(noDataElement, footer);
|
|
4779
|
-
} else {
|
|
4780
|
-
// Fallback: append to container if footer not found
|
|
4781
|
-
this.container.appendChild(noDataElement);
|
|
4782
|
-
}
|
|
4783
|
-
}
|
|
4784
4668
|
updateWidget(data) {
|
|
4785
4669
|
if (this.isDestroyed) return;
|
|
4786
4670
|
try {
|
|
@@ -5285,7 +5169,20 @@
|
|
|
5285
5169
|
this.showLoading();
|
|
5286
5170
|
|
|
5287
5171
|
// Validate initial symbol via API to get company info
|
|
5288
|
-
|
|
5172
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting company info
|
|
5173
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
5174
|
+
// Extract company info from first data item
|
|
5175
|
+
if (data && data[0]) {
|
|
5176
|
+
this.companyName = data[0].comp_name || '';
|
|
5177
|
+
this.exchangeName = data[0].market_name || '';
|
|
5178
|
+
this.mic = data[0].mic || '';
|
|
5179
|
+
}
|
|
5180
|
+
});
|
|
5181
|
+
|
|
5182
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
5183
|
+
if (validationSuccess === false) {
|
|
5184
|
+
return; // Error is already shown, don't continue
|
|
5185
|
+
}
|
|
5289
5186
|
|
|
5290
5187
|
// Set timeout to detect no data on initial load
|
|
5291
5188
|
this.loadingTimeout = setTimeout(() => {
|
|
@@ -5302,38 +5199,6 @@
|
|
|
5302
5199
|
|
|
5303
5200
|
this.subscribeToData();
|
|
5304
5201
|
}
|
|
5305
|
-
async validateInitialSymbol() {
|
|
5306
|
-
try {
|
|
5307
|
-
const apiService = this.wsManager.getApiService();
|
|
5308
|
-
if (!apiService) {
|
|
5309
|
-
if (this.debug) {
|
|
5310
|
-
console.log('[NightSessionWidget] API service not available for initial validation');
|
|
5311
|
-
}
|
|
5312
|
-
return;
|
|
5313
|
-
}
|
|
5314
|
-
const result = await apiService.quotel1(this.symbol);
|
|
5315
|
-
if (result && result.data && result.data[0]) {
|
|
5316
|
-
const symbolData = result.data[0];
|
|
5317
|
-
// Extract company info
|
|
5318
|
-
this.companyName = symbolData.comp_name || '';
|
|
5319
|
-
this.exchangeName = symbolData.market_name || '';
|
|
5320
|
-
this.mic = symbolData.mic || '';
|
|
5321
|
-
if (this.debug) {
|
|
5322
|
-
console.log('[NightSessionWidget] Initial symbol validated:', {
|
|
5323
|
-
symbol: this.symbol,
|
|
5324
|
-
companyName: this.companyName,
|
|
5325
|
-
exchangeName: this.exchangeName,
|
|
5326
|
-
mic: this.mic
|
|
5327
|
-
});
|
|
5328
|
-
}
|
|
5329
|
-
}
|
|
5330
|
-
} catch (error) {
|
|
5331
|
-
if (this.debug) {
|
|
5332
|
-
console.warn('[NightSessionWidget] Initial symbol validation failed:', error);
|
|
5333
|
-
}
|
|
5334
|
-
// Don't throw - let the widget continue with WebSocket data
|
|
5335
|
-
}
|
|
5336
|
-
}
|
|
5337
5202
|
subscribeToData() {
|
|
5338
5203
|
let subscriptionType;
|
|
5339
5204
|
if (this.source === 'bruce') {
|
|
@@ -5353,6 +5218,7 @@
|
|
|
5353
5218
|
}
|
|
5354
5219
|
handleData(message) {
|
|
5355
5220
|
console.log('DEBUG NIGHT', message);
|
|
5221
|
+
|
|
5356
5222
|
//message = message.data || message.Data
|
|
5357
5223
|
|
|
5358
5224
|
if (this.loadingTimeout) {
|
|
@@ -5360,64 +5226,41 @@
|
|
|
5360
5226
|
this.loadingTimeout = null;
|
|
5361
5227
|
}
|
|
5362
5228
|
|
|
5363
|
-
//
|
|
5364
|
-
if (message.
|
|
5229
|
+
// Check for error: false and display message if present
|
|
5230
|
+
if (message.error === true) {
|
|
5365
5231
|
if (this.debug) {
|
|
5366
|
-
console.log('[
|
|
5232
|
+
console.log('[Nigh] Received error response:', message.message);
|
|
5367
5233
|
}
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
NotFound: true,
|
|
5373
|
-
message: message.message
|
|
5374
|
-
});
|
|
5375
|
-
} else {
|
|
5234
|
+
const errorMsg = message.message || message.Message;
|
|
5235
|
+
|
|
5236
|
+
// If there's a message field, show it as an error
|
|
5237
|
+
if (errorMsg) {
|
|
5376
5238
|
this.hideLoading();
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
}
|
|
5239
|
+
this.showError(errorMsg);
|
|
5240
|
+
return;
|
|
5380
5241
|
}
|
|
5242
|
+
} else if (message.Data && typeof message.Data === 'string') {
|
|
5243
|
+
this.hideLoading();
|
|
5244
|
+
this.showError(message.Message);
|
|
5381
5245
|
return;
|
|
5382
5246
|
}
|
|
5383
5247
|
|
|
5384
|
-
// Handle
|
|
5248
|
+
// Handle error messages from server (plain text converted to structured format)
|
|
5385
5249
|
if (message.type === 'error') {
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
this.showNoDataState({
|
|
5393
|
-
Symbol: this.symbol,
|
|
5394
|
-
NotFound: true,
|
|
5395
|
-
isAccessError: true,
|
|
5396
|
-
message: errorMsg
|
|
5397
|
-
});
|
|
5398
|
-
} else {
|
|
5399
|
-
this.hideLoading();
|
|
5400
|
-
if (this.debug) {
|
|
5401
|
-
console.log('[NightSessionWidget] Access error but keeping cached data');
|
|
5402
|
-
}
|
|
5403
|
-
}
|
|
5250
|
+
if (this.debug) {
|
|
5251
|
+
console.log('[NightSessionWidget] Received no data message:', message.message);
|
|
5252
|
+
}
|
|
5253
|
+
// Only show no data state if we don't have cached data
|
|
5254
|
+
if (!this.data) {
|
|
5255
|
+
this.showError(errorMsg);
|
|
5404
5256
|
} else {
|
|
5405
|
-
|
|
5406
|
-
if (
|
|
5407
|
-
console.log('
|
|
5408
|
-
this.showError(errorMsg);
|
|
5409
|
-
} else {
|
|
5410
|
-
this.hideLoading();
|
|
5411
|
-
if (this.debug) {
|
|
5412
|
-
console.log('[NightSessionWidget] Error received but keeping cached data:', errorMsg);
|
|
5413
|
-
}
|
|
5257
|
+
this.hideLoading();
|
|
5258
|
+
if (this.debug) {
|
|
5259
|
+
console.log('[MarketDataWidget] Error received but keeping cached data:', errorMsg);
|
|
5414
5260
|
}
|
|
5415
5261
|
}
|
|
5416
5262
|
return;
|
|
5417
5263
|
}
|
|
5418
|
-
|
|
5419
|
-
// Filter for night session data
|
|
5420
|
-
|
|
5421
5264
|
if (Array.isArray(message.data)) {
|
|
5422
5265
|
// First, try to find data matching our symbol regardless of MarketName
|
|
5423
5266
|
const symbolData = message.data.find(item => item.Symbol === this.symbol);
|
|
@@ -6318,36 +6161,20 @@
|
|
|
6318
6161
|
this.showLoading();
|
|
6319
6162
|
|
|
6320
6163
|
// Validate initial symbol via API to get symbol info
|
|
6321
|
-
|
|
6322
|
-
this.
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
if (!apiService) {
|
|
6328
|
-
if (this.debug) {
|
|
6329
|
-
console.log('[OptionsWidget] API service not available for initial validation');
|
|
6330
|
-
}
|
|
6331
|
-
return;
|
|
6164
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting data
|
|
6165
|
+
const validationSuccess = await super.validateInitialSymbol('quoteOptionl1', this.symbol, data => {
|
|
6166
|
+
// Store for use in updateWidget
|
|
6167
|
+
// Note: quoteOptionl1 returns array directly, not wrapped in data field
|
|
6168
|
+
if (data && data[0] && !data[0].error && !data[0].not_found) {
|
|
6169
|
+
this.initialValidationData = data[0];
|
|
6332
6170
|
}
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
if (this.debug) {
|
|
6339
|
-
console.log('[OptionsWidget] Initial symbol validated:', {
|
|
6340
|
-
symbol: this.symbol,
|
|
6341
|
-
underlying: symbolData.Underlying || symbolData.RootSymbol
|
|
6342
|
-
});
|
|
6343
|
-
}
|
|
6344
|
-
}
|
|
6345
|
-
} catch (error) {
|
|
6346
|
-
if (this.debug) {
|
|
6347
|
-
console.warn('[OptionsWidget] Initial symbol validation failed:', error);
|
|
6348
|
-
}
|
|
6349
|
-
// Don't throw - let the widget continue with WebSocket data
|
|
6171
|
+
});
|
|
6172
|
+
|
|
6173
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
6174
|
+
if (validationSuccess === false) {
|
|
6175
|
+
return; // Error is already shown, don't continue
|
|
6350
6176
|
}
|
|
6177
|
+
this.subscribeToData();
|
|
6351
6178
|
}
|
|
6352
6179
|
subscribeToData() {
|
|
6353
6180
|
// Subscribe with symbol for routing
|
|
@@ -38704,7 +38531,20 @@ ${SharedStyles}
|
|
|
38704
38531
|
}
|
|
38705
38532
|
async initialize() {
|
|
38706
38533
|
// Validate initial symbol via API to get company info
|
|
38707
|
-
|
|
38534
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting company info
|
|
38535
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
38536
|
+
// Extract company info from first data item
|
|
38537
|
+
if (data && data[0]) {
|
|
38538
|
+
this.companyName = data[0].comp_name || '';
|
|
38539
|
+
this.exchangeName = data[0].market_name || '';
|
|
38540
|
+
this.mic = data[0].mic || '';
|
|
38541
|
+
}
|
|
38542
|
+
});
|
|
38543
|
+
|
|
38544
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
38545
|
+
if (validationSuccess === false) {
|
|
38546
|
+
return; // Error is already shown, don't continue
|
|
38547
|
+
}
|
|
38708
38548
|
|
|
38709
38549
|
// Update company name in the header
|
|
38710
38550
|
this.updateCompanyName();
|
|
@@ -38712,38 +38552,6 @@ ${SharedStyles}
|
|
|
38712
38552
|
// Load chart data
|
|
38713
38553
|
await this.loadChartData();
|
|
38714
38554
|
}
|
|
38715
|
-
async validateInitialSymbol() {
|
|
38716
|
-
try {
|
|
38717
|
-
const apiService = this.wsManager.getApiService();
|
|
38718
|
-
if (!apiService) {
|
|
38719
|
-
if (this.debug) {
|
|
38720
|
-
console.log('[IntradayChartWidget] API service not available for initial validation');
|
|
38721
|
-
}
|
|
38722
|
-
return;
|
|
38723
|
-
}
|
|
38724
|
-
const result = await apiService.quotel1(this.symbol);
|
|
38725
|
-
if (result && result.data && result.data[0]) {
|
|
38726
|
-
const symbolData = result.data[0];
|
|
38727
|
-
// Extract company info
|
|
38728
|
-
this.companyName = symbolData.comp_name || '';
|
|
38729
|
-
this.exchangeName = symbolData.market_name || '';
|
|
38730
|
-
this.mic = symbolData.mic || '';
|
|
38731
|
-
if (this.debug) {
|
|
38732
|
-
console.log('[IntradayChartWidget] Initial symbol validated:', {
|
|
38733
|
-
symbol: this.symbol,
|
|
38734
|
-
companyName: this.companyName,
|
|
38735
|
-
exchangeName: this.exchangeName,
|
|
38736
|
-
mic: this.mic
|
|
38737
|
-
});
|
|
38738
|
-
}
|
|
38739
|
-
}
|
|
38740
|
-
} catch (error) {
|
|
38741
|
-
if (this.debug) {
|
|
38742
|
-
console.warn('[IntradayChartWidget] Initial symbol validation failed:', error);
|
|
38743
|
-
}
|
|
38744
|
-
// Don't throw - let the widget continue with chart data
|
|
38745
|
-
}
|
|
38746
|
-
}
|
|
38747
38555
|
updateCompanyName() {
|
|
38748
38556
|
const companyNameElement = this.container.querySelector('.intraday-company-name');
|
|
38749
38557
|
if (companyNameElement) {
|
|
@@ -39167,19 +38975,12 @@ ${SharedStyles}
|
|
|
39167
38975
|
// Handle array format (standard night session format)
|
|
39168
38976
|
if (Array.isArray(message.data)) {
|
|
39169
38977
|
console.log('[IntradayChartWidget] Processing array format, length:', message.length);
|
|
39170
|
-
const symbolData = message.find(item => item.Symbol === this.symbol);
|
|
38978
|
+
const symbolData = message.data.find(item => item.Symbol === this.symbol);
|
|
39171
38979
|
console.log('[IntradayChartWidget] Found symbol data:', symbolData);
|
|
39172
38980
|
if (symbolData && !symbolData.NotFound) {
|
|
39173
38981
|
priceData = symbolData;
|
|
39174
38982
|
}
|
|
39175
38983
|
}
|
|
39176
|
-
// Handle wrapped format
|
|
39177
|
-
else if (message.type === 'queryblueoceanl1' || message.type === 'querybrucel1') {
|
|
39178
|
-
console.log('[IntradayChartWidget] Processing wrapped format');
|
|
39179
|
-
if (message['0']?.Symbol === this.symbol && !message['0'].NotFound) {
|
|
39180
|
-
priceData = message['0'];
|
|
39181
|
-
}
|
|
39182
|
-
}
|
|
39183
38984
|
// Handle direct data format
|
|
39184
38985
|
else if (message.Symbol === this.symbol && !message.NotFound) {
|
|
39185
38986
|
console.log('[IntradayChartWidget] Processing direct format');
|
|
@@ -40282,7 +40083,19 @@ ${SharedStyles}
|
|
|
40282
40083
|
this.showLoading();
|
|
40283
40084
|
|
|
40284
40085
|
// Fetch company info for initial symbol
|
|
40285
|
-
|
|
40086
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting company info
|
|
40087
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
40088
|
+
// Extract company info from first data item
|
|
40089
|
+
if (data && data[0]) {
|
|
40090
|
+
this.companyName = data[0].comp_name || '';
|
|
40091
|
+
this.exchangeName = data[0].market_name || '';
|
|
40092
|
+
}
|
|
40093
|
+
});
|
|
40094
|
+
|
|
40095
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
40096
|
+
if (validationSuccess === false) {
|
|
40097
|
+
return; // Error is already shown, don't continue
|
|
40098
|
+
}
|
|
40286
40099
|
|
|
40287
40100
|
// Set timeout to detect no data on initial load
|
|
40288
40101
|
this.loadingTimeout = setTimeout(() => {
|
|
@@ -40294,36 +40107,6 @@ ${SharedStyles}
|
|
|
40294
40107
|
}, 10000);
|
|
40295
40108
|
this.subscribeToData();
|
|
40296
40109
|
}
|
|
40297
|
-
async validateInitialSymbol() {
|
|
40298
|
-
try {
|
|
40299
|
-
const apiService = this.wsManager.getApiService();
|
|
40300
|
-
if (!apiService) {
|
|
40301
|
-
if (this.debug) {
|
|
40302
|
-
console.log('[ONBBO L2] API service not available for initial validation');
|
|
40303
|
-
}
|
|
40304
|
-
return;
|
|
40305
|
-
}
|
|
40306
|
-
const result = await apiService.quotel1(this.symbol);
|
|
40307
|
-
if (result && result.data && result.data[0]) {
|
|
40308
|
-
const symbolData = result.data[0];
|
|
40309
|
-
// Extract company info
|
|
40310
|
-
this.companyName = symbolData.comp_name || '';
|
|
40311
|
-
this.exchangeName = symbolData.market_name || '';
|
|
40312
|
-
if (this.debug) {
|
|
40313
|
-
console.log('[ONBBO L2] Initial symbol validated:', {
|
|
40314
|
-
symbol: this.symbol,
|
|
40315
|
-
companyName: this.companyName,
|
|
40316
|
-
exchangeName: this.exchangeName
|
|
40317
|
-
});
|
|
40318
|
-
}
|
|
40319
|
-
}
|
|
40320
|
-
} catch (error) {
|
|
40321
|
-
if (this.debug) {
|
|
40322
|
-
console.warn('[ONBBO L2] Initial symbol validation failed:', error);
|
|
40323
|
-
}
|
|
40324
|
-
// Don't throw - let the widget continue with WebSocket data
|
|
40325
|
-
}
|
|
40326
|
-
}
|
|
40327
40110
|
subscribeToData() {
|
|
40328
40111
|
// Subscribe to ONBBO Level 2 data (order book)
|
|
40329
40112
|
// Use separate widget IDs to avoid "already active" duplicate detection
|
|
@@ -40842,6 +40625,7 @@ ${SharedStyles}
|
|
|
40842
40625
|
this.styled = options.styled !== undefined ? options.styled : true;
|
|
40843
40626
|
this.maxTrades = options.maxTrades || 20; // Maximum number of trades to display
|
|
40844
40627
|
this.data = new TimeSalesModel([], this.maxTrades);
|
|
40628
|
+
this.data.symbol = this.symbol; // Set the symbol in the data model
|
|
40845
40629
|
this.isDestroyed = false;
|
|
40846
40630
|
this.unsubscribe = null;
|
|
40847
40631
|
this.symbolEditor = null;
|
|
@@ -40853,6 +40637,7 @@ ${SharedStyles}
|
|
|
40853
40637
|
|
|
40854
40638
|
// Create widget structure
|
|
40855
40639
|
this.createWidgetStructure();
|
|
40640
|
+
this.initializeSymbolEditor();
|
|
40856
40641
|
|
|
40857
40642
|
// Initialize the widget
|
|
40858
40643
|
this.initialize();
|
|
@@ -40869,7 +40654,26 @@ ${SharedStyles}
|
|
|
40869
40654
|
}
|
|
40870
40655
|
}
|
|
40871
40656
|
this.addStyles();
|
|
40872
|
-
this.setupSymbolEditor();
|
|
40657
|
+
//this.setupSymbolEditor();
|
|
40658
|
+
}
|
|
40659
|
+
initializeSymbolEditor() {
|
|
40660
|
+
// Initialize symbol editor with stock symbol validation
|
|
40661
|
+
this.symbolEditor = new SymbolEditor(this, {
|
|
40662
|
+
maxLength: 10,
|
|
40663
|
+
placeholder: 'Enter symbol...',
|
|
40664
|
+
//validator: this.validateStockSymbol.bind(this),
|
|
40665
|
+
onSymbolChange: this.handleSymbolChange.bind(this),
|
|
40666
|
+
debug: this.debug,
|
|
40667
|
+
autoUppercase: true,
|
|
40668
|
+
symbolType: 'quotel1'
|
|
40669
|
+
});
|
|
40670
|
+
|
|
40671
|
+
// Set initial symbol
|
|
40672
|
+
const symbolElement = this.container.querySelector('.symbol');
|
|
40673
|
+
if (symbolElement) {
|
|
40674
|
+
symbolElement.textContent = this.symbol;
|
|
40675
|
+
symbolElement.dataset.originalSymbol = this.symbol;
|
|
40676
|
+
}
|
|
40873
40677
|
}
|
|
40874
40678
|
addStyles() {
|
|
40875
40679
|
// Inject styles from the styles file into document head
|
|
@@ -40892,6 +40696,8 @@ ${SharedStyles}
|
|
|
40892
40696
|
// Keep editor open when clicking buttons
|
|
40893
40697
|
symbolType: 'quotel1' // Use standard quote validation
|
|
40894
40698
|
});
|
|
40699
|
+
|
|
40700
|
+
//console.log('SETUP COMPLETE')
|
|
40895
40701
|
}
|
|
40896
40702
|
async handleSymbolChange(newSymbol, oldSymbol) {
|
|
40897
40703
|
let validationData = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
|
@@ -40994,7 +40800,19 @@ ${SharedStyles}
|
|
|
40994
40800
|
this.showLoading();
|
|
40995
40801
|
|
|
40996
40802
|
// Validate initial symbol via API to get company info
|
|
40997
|
-
|
|
40803
|
+
// Uses BaseWidget's validateInitialSymbol method with callback for extracting company info
|
|
40804
|
+
const validationSuccess = await super.validateInitialSymbol('quotel1', this.symbol, data => {
|
|
40805
|
+
// Extract company info from first data item
|
|
40806
|
+
if (data && data[0]) {
|
|
40807
|
+
this.companyName = data[0].comp_name || '';
|
|
40808
|
+
this.exchangeName = data[0].market_name || '';
|
|
40809
|
+
}
|
|
40810
|
+
});
|
|
40811
|
+
|
|
40812
|
+
// If validation failed due to access/permission issues, stop initialization
|
|
40813
|
+
if (validationSuccess === false) {
|
|
40814
|
+
return; // Error is already shown, don't continue
|
|
40815
|
+
}
|
|
40998
40816
|
|
|
40999
40817
|
// Set timeout to detect no data on initial load
|
|
41000
40818
|
this.loadingTimeout = setTimeout(() => {
|
|
@@ -41007,36 +40825,6 @@ ${SharedStyles}
|
|
|
41007
40825
|
|
|
41008
40826
|
this.subscribeToData();
|
|
41009
40827
|
}
|
|
41010
|
-
async validateInitialSymbol() {
|
|
41011
|
-
try {
|
|
41012
|
-
const apiService = this.wsManager.getApiService();
|
|
41013
|
-
if (!apiService) {
|
|
41014
|
-
if (this.debug) {
|
|
41015
|
-
console.log('[TimeSalesWidget] API service not available for initial validation');
|
|
41016
|
-
}
|
|
41017
|
-
return;
|
|
41018
|
-
}
|
|
41019
|
-
const result = await apiService.quotel1(this.symbol);
|
|
41020
|
-
if (result && result.data && result.data[0]) {
|
|
41021
|
-
const symbolData = result.data[0];
|
|
41022
|
-
// Extract company info
|
|
41023
|
-
this.companyName = symbolData.comp_name || '';
|
|
41024
|
-
this.exchangeName = symbolData.market_name || '';
|
|
41025
|
-
if (this.debug) {
|
|
41026
|
-
console.log('[TimeSalesWidget] Initial symbol validated:', {
|
|
41027
|
-
symbol: this.symbol,
|
|
41028
|
-
companyName: this.companyName,
|
|
41029
|
-
exchangeName: this.exchangeName
|
|
41030
|
-
});
|
|
41031
|
-
}
|
|
41032
|
-
}
|
|
41033
|
-
} catch (error) {
|
|
41034
|
-
if (this.debug) {
|
|
41035
|
-
console.warn('[TimeSalesWidget] Initial symbol validation failed:', error);
|
|
41036
|
-
}
|
|
41037
|
-
// Don't throw - let the widget continue with WebSocket data
|
|
41038
|
-
}
|
|
41039
|
-
}
|
|
41040
40828
|
subscribeToData() {
|
|
41041
40829
|
// Subscribe to Time & Sales data (using queryonbbotimesale)
|
|
41042
40830
|
this.unsubscribe = this.wsManager.subscribe(this.widgetId, ['queryonbbotimesale'],
|
|
@@ -41052,6 +40840,9 @@ ${SharedStyles}
|
|
|
41052
40840
|
clearTimeout(this.loadingTimeout);
|
|
41053
40841
|
this.loadingTimeout = null;
|
|
41054
40842
|
}
|
|
40843
|
+
if (message.type != 'queryonbbotimesale') {
|
|
40844
|
+
return;
|
|
40845
|
+
}
|
|
41055
40846
|
|
|
41056
40847
|
// Handle error messages
|
|
41057
40848
|
if (message.type === 'error') {
|
|
@@ -41064,30 +40855,16 @@ ${SharedStyles}
|
|
|
41064
40855
|
}
|
|
41065
40856
|
|
|
41066
40857
|
// Handle Time & Sales data - Array of trades
|
|
41067
|
-
if (Array.isArray(message)) {
|
|
40858
|
+
if (Array.isArray(message.data)) {
|
|
41068
40859
|
if (this.debug) {
|
|
41069
40860
|
console.log('[TimeSalesWidget] Received trades array:', message);
|
|
41070
40861
|
}
|
|
41071
40862
|
|
|
41072
40863
|
// Add new trades to existing data
|
|
41073
|
-
this.data.addTrades(message);
|
|
40864
|
+
this.data.addTrades(message.data);
|
|
41074
40865
|
this.updateWidget();
|
|
41075
40866
|
return;
|
|
41076
40867
|
}
|
|
41077
|
-
|
|
41078
|
-
// Handle wrapped format
|
|
41079
|
-
if ((message.type === 'queryonbbotimesale' || message.type === 'querytns') && message['0']) {
|
|
41080
|
-
if (Array.isArray(message['0'])) {
|
|
41081
|
-
if (this.debug) {
|
|
41082
|
-
console.log('[TimeSalesWidget] Received wrapped trades array:', message['0']);
|
|
41083
|
-
}
|
|
41084
|
-
|
|
41085
|
-
// Add new trades to existing data
|
|
41086
|
-
this.data.addTrades(message['0']);
|
|
41087
|
-
this.updateWidget();
|
|
41088
|
-
return;
|
|
41089
|
-
}
|
|
41090
|
-
}
|
|
41091
40868
|
if (this.debug) {
|
|
41092
40869
|
console.log('[TimeSalesWidget] Unexpected message format:', message);
|
|
41093
40870
|
}
|
|
@@ -41314,7 +41091,15 @@ ${SharedStyles}
|
|
|
41314
41091
|
try {
|
|
41315
41092
|
const response = await fetch(url, config);
|
|
41316
41093
|
if (!response.ok) {
|
|
41317
|
-
|
|
41094
|
+
// Try to get response body for error details
|
|
41095
|
+
let errorBody = '';
|
|
41096
|
+
try {
|
|
41097
|
+
errorBody = await response.text();
|
|
41098
|
+
console.error(`[ApiService] HTTP ${response.status} - Response body:`, errorBody);
|
|
41099
|
+
} catch (e) {
|
|
41100
|
+
console.error(`[ApiService] Could not read error response body:`, e);
|
|
41101
|
+
}
|
|
41102
|
+
throw new Error(`HTTP error! status: ${response.status}, body: ${errorBody}`);
|
|
41318
41103
|
}
|
|
41319
41104
|
return await response.json();
|
|
41320
41105
|
} catch (error) {
|
|
@@ -42074,8 +41859,8 @@ ${SharedStyles}
|
|
|
42074
41859
|
*/
|
|
42075
41860
|
_normalizeMessage(message) {
|
|
42076
41861
|
// Check for error field (case-insensitive)
|
|
42077
|
-
const errorField = message.error
|
|
42078
|
-
const typeField = message.type
|
|
41862
|
+
const errorField = message.error;
|
|
41863
|
+
const typeField = message.type;
|
|
42079
41864
|
const messageField = message.message || message.Message;
|
|
42080
41865
|
|
|
42081
41866
|
// Handle explicit error messages - route based on type field
|
|
@@ -42115,54 +41900,16 @@ ${SharedStyles}
|
|
|
42115
41900
|
}
|
|
42116
41901
|
}
|
|
42117
41902
|
|
|
42118
|
-
// Check for new format: has 'type'/'Type' AND 'data'/'Data' fields
|
|
42119
|
-
const dataField = message.data || message.Data;
|
|
42120
|
-
if (typeField && dataField !== undefined) {
|
|
42121
|
-
if (this.config.debug) {
|
|
42122
|
-
console.log(`[WebSocketManager] Detected new message format with type: ${typeField}`);
|
|
42123
|
-
}
|
|
42124
|
-
|
|
42125
|
-
// Extract the actual data and add the type to it for routing
|
|
42126
|
-
let normalizedData;
|
|
42127
|
-
if (Array.isArray(dataField)) {
|
|
42128
|
-
// If data is an array, process each item
|
|
42129
|
-
normalizedData = dataField;
|
|
42130
|
-
// Add type info to the structure for routing
|
|
42131
|
-
if (normalizedData.length > 0) {
|
|
42132
|
-
normalizedData._messageType = typeField;
|
|
42133
|
-
}
|
|
42134
|
-
} else if (typeof dataField === 'object' && dataField !== null) {
|
|
42135
|
-
// If data is an object, use it directly
|
|
42136
|
-
normalizedData = dataField;
|
|
42137
|
-
// Add type info for routing
|
|
42138
|
-
normalizedData._messageType = typeField;
|
|
42139
|
-
} else {
|
|
42140
|
-
// Primitive value, wrap it
|
|
42141
|
-
normalizedData = {
|
|
42142
|
-
value: dataField,
|
|
42143
|
-
_messageType: typeField
|
|
42144
|
-
};
|
|
42145
|
-
}
|
|
42146
|
-
|
|
42147
|
-
// Only add type field if the normalized data doesn't already have one
|
|
42148
|
-
if (typeof normalizedData === 'object' && !normalizedData.type && !normalizedData.Type) {
|
|
42149
|
-
normalizedData.type = typeField;
|
|
42150
|
-
}
|
|
42151
|
-
return normalizedData;
|
|
42152
|
-
}
|
|
42153
|
-
|
|
42154
41903
|
// Old format or no type/data structure, return as is
|
|
42155
41904
|
return message;
|
|
42156
41905
|
}
|
|
42157
41906
|
_routeMessage(message) {
|
|
42158
41907
|
//console.log('message', message);
|
|
42159
41908
|
|
|
42160
|
-
|
|
42161
|
-
|
|
42162
|
-
|
|
42163
|
-
|
|
42164
|
-
return; // Don't route empty arrays
|
|
42165
|
-
}
|
|
41909
|
+
// IMPORTANT: Don't filter out empty arrays here - route them based on message.type
|
|
41910
|
+
// Widgets need to receive empty arrays to clear loading states and show "no data" messages
|
|
41911
|
+
// The message structure is: { type: 'queryoptionchain', data: [] }
|
|
41912
|
+
// We need to route based on the 'type' field, not the data content
|
|
42166
41913
|
|
|
42167
41914
|
// Cache the message for later use by new subscribers
|
|
42168
41915
|
this._cacheMessage(message);
|
|
@@ -42261,22 +42008,28 @@ ${SharedStyles}
|
|
|
42261
42008
|
_getRelevantWidgets(message) {
|
|
42262
42009
|
const relevantWidgets = new Set();
|
|
42263
42010
|
|
|
42011
|
+
// OPTIMIZATION: Extract message type once from the entire message, not from each data item
|
|
42012
|
+
// Message structure: { type: 'queryoptionchain', data: [] }
|
|
42013
|
+
let dataField = message.Data || message.data;
|
|
42014
|
+
const messageType = message.type || this._extractMessageType(dataField);
|
|
42015
|
+
|
|
42264
42016
|
// Handle array messages
|
|
42265
|
-
this._addRelevantWidgetsForItem(message, relevantWidgets);
|
|
42017
|
+
this._addRelevantWidgetsForItem(message, relevantWidgets, messageType);
|
|
42266
42018
|
return relevantWidgets;
|
|
42267
42019
|
}
|
|
42268
|
-
_addRelevantWidgetsForItem(item, relevantWidgets) {
|
|
42020
|
+
_addRelevantWidgetsForItem(item, relevantWidgets, messageType) {
|
|
42269
42021
|
// If item has Data array, process it
|
|
42270
42022
|
if (item.Data && Array.isArray(item.Data)) {
|
|
42271
42023
|
//console.log('Data array found with length:', item.Data.length);
|
|
42272
42024
|
item.Data.forEach(dataItem => {
|
|
42273
|
-
|
|
42025
|
+
// Pass messageType from parent message, don't extract from each data item
|
|
42026
|
+
this._processDataItem(dataItem, relevantWidgets, messageType);
|
|
42274
42027
|
});
|
|
42275
42028
|
} else if (item && item[0] && item[0].Strike !== undefined && item[0].Expire && !item[0].underlyingSymbol) {
|
|
42276
42029
|
this._processOptionChainData(item, relevantWidgets);
|
|
42277
42030
|
} else {
|
|
42278
|
-
// Process single item
|
|
42279
|
-
this._processDataItem(item, relevantWidgets);
|
|
42031
|
+
// Process single item - messageType already extracted from message
|
|
42032
|
+
this._processDataItem(item, relevantWidgets, messageType);
|
|
42280
42033
|
}
|
|
42281
42034
|
}
|
|
42282
42035
|
|
|
@@ -42320,12 +42073,11 @@ ${SharedStyles}
|
|
|
42320
42073
|
}
|
|
42321
42074
|
}
|
|
42322
42075
|
}
|
|
42323
|
-
_processDataItem(dataItem, relevantWidgets) {
|
|
42076
|
+
_processDataItem(dataItem, relevantWidgets, messageType) {
|
|
42324
42077
|
// Process individual data items and route to appropriate widgets
|
|
42325
42078
|
// Option chain arrays are handled separately by _processOptionChainData
|
|
42326
42079
|
|
|
42327
42080
|
const symbol = this._extractSymbol(dataItem);
|
|
42328
|
-
const messageType = this._extractMessageType(dataItem);
|
|
42329
42081
|
if (this.config.debug) {
|
|
42330
42082
|
console.log('[WebSocketManager] Processing data item:', {
|
|
42331
42083
|
symbol,
|
|
@@ -42485,25 +42237,15 @@ ${SharedStyles}
|
|
|
42485
42237
|
return symbolMappings[extracted] || extracted;
|
|
42486
42238
|
}
|
|
42487
42239
|
_extractMessageType(item) {
|
|
42488
|
-
//
|
|
42489
|
-
if (item._messageType) {
|
|
42490
|
-
return item._messageType;
|
|
42491
|
-
}
|
|
42492
|
-
|
|
42493
|
-
// Determine message type based on content
|
|
42494
|
-
if (item.type) return item.type;
|
|
42495
|
-
|
|
42496
|
-
// Infer type from data structure
|
|
42240
|
+
// FALLBACK: Infer type from data structure (for legacy/malformed messages)
|
|
42497
42241
|
|
|
42498
42242
|
// IMPORTANT: Check for MMID field FIRST to distinguish ONBBO L2 from L1
|
|
42499
42243
|
// Level 2 ONBBO data - array of MMID objects or object with MMID field
|
|
42500
42244
|
if (Array.isArray(item) && item.length > 0 && item[0].MMID !== undefined) {
|
|
42501
42245
|
return 'queryonbbol2';
|
|
42502
42246
|
}
|
|
42503
|
-
|
|
42504
|
-
|
|
42505
|
-
if (item.MMID !== undefined) {
|
|
42506
|
-
return 'queryonbbol2';
|
|
42247
|
+
if (Array.isArray(item) && item.length > 0 && item[0].Strike !== undefined) {
|
|
42248
|
+
return 'queryoptionchain';
|
|
42507
42249
|
}
|
|
42508
42250
|
|
|
42509
42251
|
// Check for Source field AFTER MMID check
|