mdas-jsview-sdk 1.0.17-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.
@@ -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
@@ -1277,6 +1356,24 @@ const ONBBOLevel2Template = `
1277
1356
  <span class="l2-market-name"></span>
1278
1357
  </div>
1279
1358
  <span class="symbol editable-symbol">--</span>
1359
+ <div class="level1-info">
1360
+ <div class="l1-item">
1361
+ <span class="l1-label">Last:</span>
1362
+ <span class="l1-value l1-last-px">--</span>
1363
+ </div>
1364
+ <div class="l1-item">
1365
+ <span class="l1-label">Low:</span>
1366
+ <span class="l1-value l1-low-px">--</span>
1367
+ </div>
1368
+ <div class="l1-item">
1369
+ <span class="l1-label">High:</span>
1370
+ <span class="l1-value l1-high-px">--</span>
1371
+ </div>
1372
+ <div class="l1-item">
1373
+ <span class="l1-label">Volume:</span>
1374
+ <span class="l1-value l1-volume">--</span>
1375
+ </div>
1376
+ </div>
1280
1377
  </div>
1281
1378
  </div>
1282
1379
 
@@ -1951,6 +2048,38 @@ const ONBBOLevel2Styles = `
1951
2048
  color: #111827;
1952
2049
  }
1953
2050
 
2051
+ /* ========================================
2052
+ LEVEL 1 INFO (INSIDE HEADER)
2053
+ ======================================== */
2054
+
2055
+ .onbbo-level2-widget .level1-info {
2056
+ display: grid;
2057
+ grid-template-columns: repeat(4, 1fr);
2058
+ gap: 12px;
2059
+ margin-top: 10px;
2060
+ }
2061
+
2062
+ .onbbo-level2-widget .l1-item {
2063
+ display: flex;
2064
+ flex-direction: column;
2065
+ gap: 3px;
2066
+ }
2067
+
2068
+ .onbbo-level2-widget .l1-label {
2069
+ font-size: 11px;
2070
+ font-weight: 600;
2071
+ color: #6b7280;
2072
+ text-transform: uppercase;
2073
+ letter-spacing: 0.5px;
2074
+ }
2075
+
2076
+ .onbbo-level2-widget .l1-value {
2077
+ font-size: 15px;
2078
+ font-weight: 700;
2079
+ color: #111827;
2080
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2081
+ }
2082
+
1954
2083
  /* ========================================
1955
2084
  ORDER BOOK CONTAINER
1956
2085
  ======================================== */
@@ -2335,6 +2464,11 @@ const ONBBOLevel2Styles = `
2335
2464
  .onbbo-level2-widget .orderbook-panel:last-child {
2336
2465
  border-bottom: none;
2337
2466
  }
2467
+
2468
+ .onbbo-level2-widget .level1-info {
2469
+ grid-template-columns: repeat(2, 1fr);
2470
+ gap: 6px;
2471
+ }
2338
2472
  }
2339
2473
 
2340
2474
  @media (max-width: 480px) {
@@ -2353,6 +2487,19 @@ const ONBBOLevel2Styles = `
2353
2487
  text-align: left;
2354
2488
  }
2355
2489
 
2490
+ .onbbo-level2-widget .level1-info {
2491
+ grid-template-columns: repeat(2, 1fr);
2492
+ gap: 4px;
2493
+ }
2494
+
2495
+ .onbbo-level2-widget .l1-label {
2496
+ font-size: 9px;
2497
+ }
2498
+
2499
+ .onbbo-level2-widget .l1-value {
2500
+ font-size: 11px;
2501
+ }
2502
+
2356
2503
  .onbbo-level2-widget .panel-header {
2357
2504
  font-size: 10px;
2358
2505
  padding: 6px 2px;
@@ -2436,28 +2583,6 @@ const TimeSalesStyles = `
2436
2583
  font-size: 1.71em;
2437
2584
  font-weight: 700;
2438
2585
  color: #111827;
2439
- cursor: pointer;
2440
- transition: all 0.2s ease;
2441
- position: relative;
2442
- }
2443
-
2444
- .time-sales-widget .symbol.editable-symbol:hover {
2445
- opacity: 0.8;
2446
- transform: scale(1.02);
2447
- }
2448
-
2449
- .time-sales-widget .symbol.editable-symbol:hover::after {
2450
- content: "✎";
2451
- position: absolute;
2452
- top: -8px;
2453
- right: -8px;
2454
- background: #3b82f6;
2455
- color: white;
2456
- font-size: 10px;
2457
- padding: 2px 4px;
2458
- border-radius: 4px;
2459
- opacity: 0.8;
2460
- pointer-events: none;
2461
2586
  }
2462
2587
 
2463
2588
  /* ========================================
@@ -4409,105 +4534,53 @@ class MarketDataWidget extends BaseWidget {
4409
4534
  this.showLoading();
4410
4535
 
4411
4536
  // Validate initial symbol via API to get company info
4412
- await this.validateInitialSymbol();
4413
- this.subscribeToData();
4414
- }
4415
- async validateInitialSymbol() {
4416
- try {
4417
- const apiService = this.wsManager.getApiService();
4418
- if (!apiService) {
4419
- if (this.debug) {
4420
- console.log('[MarketDataWidget] API service not available for initial validation');
4421
- }
4422
- return;
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];
4423
4542
  }
4424
- const result = await apiService.quotel1(this.symbol);
4425
- if (result && result.data && result.data[0]) {
4426
- const symbolData = result.data[0];
4427
- // Store for use in updateWidget
4428
- this.initialValidationData = symbolData;
4429
- if (this.debug) {
4430
- console.log('[MarketDataWidget] Initial symbol validated:', {
4431
- symbol: this.symbol,
4432
- companyName: symbolData.comp_name,
4433
- exchangeName: symbolData.market_name
4434
- });
4435
- }
4436
- }
4437
- } catch (error) {
4438
- if (this.debug) {
4439
- console.warn('[MarketDataWidget] Initial symbol validation failed:', error);
4440
- }
4441
- // Don't throw - let the widget continue with WebSocket data
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
4442
4548
  }
4549
+ this.subscribeToData();
4443
4550
  }
4444
4551
  subscribeToData() {
4445
4552
  // Subscribe with symbol for routing
4446
4553
  this.unsubscribe = this.wsManager.subscribe(this.widgetId, ['queryl1'], this.handleMessage.bind(this), this.symbol // Pass symbol for routing
4447
4554
  );
4448
-
4449
- // Send subscription message
4450
- /* this.wsManager.send({
4451
- type: 'queryl1',
4452
- symbol: this.symbol
4453
- }); */
4454
4555
  }
4556
+ handleData(message) {
4557
+ //console.log('DEBUG', message)
4455
4558
 
4456
- /* handleMessage(messageWrapper) {
4457
- if (this.isDestroyed) return;
4458
- try {
4459
- const { event, data } = messageWrapper;
4460
- if (this.debug) {
4461
- console.log('[MarketDataWidget] Received:', event, data);
4462
- }
4463
- if (event === 'connection') {
4464
- this.handleConnectionStatus(data);
4465
- return;
4466
- }
4467
- if (event === 'data') {
4468
- this.handleData(data);
4469
- }
4470
- if (event === 'session_revoked') {
4471
- if (data.status === 'attempting_relogin') {
4472
- this.showLoading();
4473
- } else if (data.status === 'relogin_failed') {
4474
- this.showError(data.error);
4475
- } else if (data.status === 'relogin_successful') {
4476
- this.hideLoading();
4477
- }
4478
- return;
4479
- }
4480
- } catch (error) {
4481
- console.error('[MarketDataWidget] Error handling message:', error);
4482
- 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);
4483
4563
  }
4484
- } */
4564
+ const errorMsg = message.message || message.Message;
4485
4565
 
4486
- /* handleConnectionStatus(status) {
4487
- if (status.status === 'connected') {
4488
- if (this.debug) {
4489
- console.log('[MarketDataWidget] Connected to WebSocket');
4490
- }
4491
- // Re-send subscription when reconnected
4492
- this.wsManager.send({
4493
- type: 'queryl1',
4494
- symbol: this.symbol
4495
- });
4496
- } else if (status.status === 'disconnected') {
4497
- this.showError('Disconnected from data service');
4498
- } else if (status.status === 'error') {
4499
- 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;
4500
4571
  }
4501
- } */
4502
-
4503
- handleData(message) {
4572
+ } else if (message.Data && typeof message.Data === 'string') {
4573
+ this.hideLoading();
4574
+ this.showError(message.Message);
4575
+ return;
4576
+ }
4504
4577
  if (this.loadingTimeout) {
4505
4578
  clearTimeout(this.loadingTimeout);
4506
4579
  this.loadingTimeout = null;
4507
4580
  }
4508
4581
 
4509
4582
  // Handle error messages from server (plain text converted to structured format)
4510
- if (message.type === 'error' && message.noData) {
4583
+ if (message.type === 'error') {
4511
4584
  if (this.debug) {
4512
4585
  console.log('[MarketDataWidget] Received no data message:', message.message);
4513
4586
  }
@@ -4586,126 +4659,6 @@ class MarketDataWidget extends BaseWidget {
4586
4659
  this.showConnectionQuality(); // Show cache indicator
4587
4660
  }
4588
4661
  }
4589
- showNoDataState() {
4590
- let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
4591
- if (this.isDestroyed) return;
4592
- try {
4593
- // Hide loading overlay
4594
- this.hideLoading();
4595
-
4596
- // Clear any existing errors
4597
- this.clearError();
4598
-
4599
- // Update header with dimmed styling and basic info from data
4600
- const symbol = data.Symbol || this.symbol;
4601
-
4602
- // Add null checks for all DOM elements
4603
- const symbolElement = this.container.querySelector('.symbol');
4604
- if (symbolElement) {
4605
- symbolElement.textContent = symbol;
4606
- }
4607
- const companyNameElement = this.container.querySelector('.company-name');
4608
- if (companyNameElement) {
4609
- companyNameElement.textContent = `${symbol} Inc`;
4610
- }
4611
-
4612
- // Set price to $0.00 and add dimmed class
4613
- const currentPriceElement = this.container.querySelector('.current-price');
4614
- if (currentPriceElement) {
4615
- currentPriceElement.textContent = '$0.00';
4616
- }
4617
- const changeElement = this.container.querySelector('.price-change');
4618
- if (changeElement) {
4619
- const changeValueElement = changeElement.querySelector('.change-value');
4620
- const changePercentElement = changeElement.querySelector('.change-percent');
4621
- if (changeValueElement) {
4622
- changeValueElement.textContent = '+0.00';
4623
- }
4624
- if (changePercentElement) {
4625
- changePercentElement.textContent = ' (0.00%)';
4626
- }
4627
- changeElement.classList.remove('positive', 'negative');
4628
- changeElement.classList.add('neutral');
4629
- }
4630
-
4631
- // Add dimmed styling to header
4632
- const widgetHeader = this.container.querySelector('.widget-header');
4633
- if (widgetHeader) {
4634
- widgetHeader.classList.add('dimmed');
4635
- }
4636
-
4637
- // Replace the data grid with no data message
4638
- this.showNoDataMessage(symbol, data);
4639
-
4640
- // Update footer with current timestamp
4641
- const lastUpdateElement = this.container.querySelector('.last-update');
4642
- if (lastUpdateElement) {
4643
- const timestamp = formatTimestampET();
4644
- lastUpdateElement.textContent = `Checked: ${timestamp}`;
4645
- }
4646
- const dataSourceElement = this.container.querySelector('.data-source');
4647
- if (dataSourceElement) {
4648
- dataSourceElement.textContent = 'Source: No data available';
4649
- }
4650
- } catch (error) {
4651
- console.error('Error showing no data state:', error);
4652
- this.showError('Error displaying no data state');
4653
- }
4654
- }
4655
- showNoDataMessage(symbol) {
4656
- const dataGrid = this.container.querySelector('.data-grid');
4657
-
4658
- // Hide the data grid if it exists
4659
- if (dataGrid) {
4660
- dataGrid.style.display = 'none';
4661
- }
4662
-
4663
- // Remove existing no data message if present
4664
- const existingNoData = this.container.querySelector('.no-data-state');
4665
- if (existingNoData) {
4666
- existingNoData.remove();
4667
- }
4668
-
4669
- // Create no data message element - safely without XSS risk
4670
- const noDataElement = createElement('div', '', 'no-data-state');
4671
- const noDataContent = createElement('div', '', 'no-data-content');
4672
-
4673
- // Create icon
4674
- const iconDiv = document.createElement('div');
4675
- iconDiv.className = 'no-data-icon';
4676
- iconDiv.innerHTML = `<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
4677
- <circle cx="12" cy="12" r="10" stroke="#9ca3af" stroke-width="2"/>
4678
- <path d="M12 8v4" stroke="#9ca3af" stroke-width="2" stroke-linecap="round"/>
4679
- <circle cx="12" cy="16" r="1" fill="#9ca3af"/>
4680
- </svg>`;
4681
- noDataContent.appendChild(iconDiv);
4682
-
4683
- // Create title
4684
- const title = createElement('h3', 'No Market Data', 'no-data-title');
4685
- noDataContent.appendChild(title);
4686
-
4687
- // Create description with sanitized symbol
4688
- const description = createElement('p', '', 'no-data-description');
4689
- description.appendChild(document.createTextNode('Market data for '));
4690
- const symbolStrong = createElement('strong', sanitizeSymbol(symbol));
4691
- description.appendChild(symbolStrong);
4692
- description.appendChild(document.createTextNode(' was not found'));
4693
- noDataContent.appendChild(description);
4694
-
4695
- // Create guidance
4696
- const guidance = createElement('p', 'Please check the symbol spelling or try a different symbol', 'no-data-guidance');
4697
- noDataContent.appendChild(guidance);
4698
- noDataElement.appendChild(noDataContent);
4699
-
4700
- // Insert before footer, with null check
4701
- const footer = this.container.querySelector('.widget-footer');
4702
- if (footer && footer.parentNode) {
4703
- footer.parentNode.insertBefore(noDataElement, footer);
4704
- } else {
4705
- // Fallback: append to container if footer not found
4706
- this.container.appendChild(noDataElement);
4707
- }
4708
- }
4709
4662
  updateWidget(data) {
4710
4663
  if (this.isDestroyed) return;
4711
4664
  try {
@@ -5210,7 +5163,20 @@ class NightSessionWidget extends BaseWidget {
5210
5163
  this.showLoading();
5211
5164
 
5212
5165
  // Validate initial symbol via API to get company info
5213
- await this.validateInitialSymbol();
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
+ }
5214
5180
 
5215
5181
  // Set timeout to detect no data on initial load
5216
5182
  this.loadingTimeout = setTimeout(() => {
@@ -5227,38 +5193,6 @@ class NightSessionWidget extends BaseWidget {
5227
5193
 
5228
5194
  this.subscribeToData();
5229
5195
  }
5230
- async validateInitialSymbol() {
5231
- try {
5232
- const apiService = this.wsManager.getApiService();
5233
- if (!apiService) {
5234
- if (this.debug) {
5235
- console.log('[NightSessionWidget] API service not available for initial validation');
5236
- }
5237
- return;
5238
- }
5239
- const result = await apiService.quotel1(this.symbol);
5240
- if (result && result.data && result.data[0]) {
5241
- const symbolData = result.data[0];
5242
- // Extract company info
5243
- this.companyName = symbolData.comp_name || '';
5244
- this.exchangeName = symbolData.market_name || '';
5245
- this.mic = symbolData.mic || '';
5246
- if (this.debug) {
5247
- console.log('[NightSessionWidget] Initial symbol validated:', {
5248
- symbol: this.symbol,
5249
- companyName: this.companyName,
5250
- exchangeName: this.exchangeName,
5251
- mic: this.mic
5252
- });
5253
- }
5254
- }
5255
- } catch (error) {
5256
- if (this.debug) {
5257
- console.warn('[NightSessionWidget] Initial symbol validation failed:', error);
5258
- }
5259
- // Don't throw - let the widget continue with WebSocket data
5260
- }
5261
- }
5262
5196
  subscribeToData() {
5263
5197
  let subscriptionType;
5264
5198
  if (this.source === 'bruce') {
@@ -5277,71 +5211,53 @@ class NightSessionWidget extends BaseWidget {
5277
5211
  // to avoid duplicate timeouts
5278
5212
  }
5279
5213
  handleData(message) {
5214
+ console.log('DEBUG NIGHT', message);
5215
+
5216
+ //message = message.data || message.Data
5217
+
5280
5218
  if (this.loadingTimeout) {
5281
5219
  clearTimeout(this.loadingTimeout);
5282
5220
  this.loadingTimeout = null;
5283
5221
  }
5284
5222
 
5285
- // Handle error messages from server (plain text converted to structured format)
5286
- if (message.type === 'error' && message.noData) {
5223
+ // Check for error: false and display message if present
5224
+ if (message.error === true) {
5287
5225
  if (this.debug) {
5288
- console.log('[NightSessionWidget] Received no data message:', message.message);
5226
+ console.log('[Nigh] Received error response:', message.message);
5289
5227
  }
5290
- // Only show no data state if we don't have cached data
5291
- if (!this.data) {
5292
- this.showNoDataState({
5293
- Symbol: this.symbol,
5294
- NotFound: true,
5295
- message: message.message
5296
- });
5297
- } else {
5228
+ const errorMsg = message.message || message.Message;
5229
+
5230
+ // If there's a message field, show it as an error
5231
+ if (errorMsg) {
5298
5232
  this.hideLoading();
5299
- if (this.debug) {
5300
- console.log('[NightSessionWidget] No new data, keeping cached data visible');
5301
- }
5233
+ this.showError(errorMsg);
5234
+ return;
5302
5235
  }
5236
+ } else if (message.Data && typeof message.Data === 'string') {
5237
+ this.hideLoading();
5238
+ this.showError(message.Message);
5303
5239
  return;
5304
5240
  }
5305
5241
 
5306
- // Handle general error messages - CHECK IF IT'S ACCESS RELATED
5242
+ // Handle error messages from server (plain text converted to structured format)
5307
5243
  if (message.type === 'error') {
5308
- const errorMsg = message.message || 'Server error';
5309
-
5310
- // If it's an access/permission error, show as no-data state instead of separate error
5311
- if (errorMsg.toLowerCase().includes('access') || errorMsg.toLowerCase().includes('permission') || errorMsg.toLowerCase().includes('denied')) {
5312
- // Only show no data state if we don't have cached data
5313
- if (!this.data) {
5314
- this.showNoDataState({
5315
- Symbol: this.symbol,
5316
- NotFound: true,
5317
- isAccessError: true,
5318
- message: errorMsg
5319
- });
5320
- } else {
5321
- this.hideLoading();
5322
- if (this.debug) {
5323
- console.log('[NightSessionWidget] Access error but keeping cached data');
5324
- }
5325
- }
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);
5326
5250
  } else {
5327
- // Only show error if we don't have cached data
5328
- if (!this.data) {
5329
- console.log('errorMsg', errorMsg);
5330
- this.showError(errorMsg);
5331
- } else {
5332
- this.hideLoading();
5333
- if (this.debug) {
5334
- console.log('[NightSessionWidget] Error received but keeping cached data:', errorMsg);
5335
- }
5251
+ this.hideLoading();
5252
+ if (this.debug) {
5253
+ console.log('[MarketDataWidget] Error received but keeping cached data:', errorMsg);
5336
5254
  }
5337
5255
  }
5338
5256
  return;
5339
5257
  }
5340
-
5341
- // Filter for night session data
5342
- if (Array.isArray(message)) {
5258
+ if (Array.isArray(message.data)) {
5343
5259
  // First, try to find data matching our symbol regardless of MarketName
5344
- const symbolData = message.find(item => item.Symbol === this.symbol);
5260
+ const symbolData = message.data.find(item => item.Symbol === this.symbol);
5345
5261
  if (symbolData) {
5346
5262
  if (this.debug) {
5347
5263
  console.log('[NightSessionWidget] Found data for symbol:', symbolData);
@@ -5381,34 +5297,35 @@ class NightSessionWidget extends BaseWidget {
5381
5297
  }
5382
5298
  }
5383
5299
  // Handle wrapped format
5384
- else if (message.type === 'queryblueoceanl1' || message.type === 'querybrucel1' || message.type === 'queryonbbol1') {
5385
- if (message['0']?.Symbol === this.symbol) {
5386
- // For onbbo source, skip MarketName check
5387
- const isOnbbo = message.type === 'queryonbbol1';
5388
- const shouldShowNoData = message['0'].NotFound === true || !isOnbbo && (!message['0'].MarketName || message['0'].MarketName !== 'BLUE');
5389
- if (shouldShowNoData) {
5390
- // Only show no data state if we don't have cached data
5391
- if (!this.data) {
5392
- this.showNoDataState(message['0']);
5393
- } else {
5394
- this.hideLoading();
5395
- if (this.debug) {
5396
- console.log('[NightSessionWidget] No new data, keeping cached data visible');
5300
+ /* else if (message.type === 'queryblueoceanl1' || message.type === 'querybrucel1' || message.type === 'queryonbbol1') {
5301
+ if (message.data['0']?.Symbol === this.symbol) {
5302
+ // For onbbo source, skip MarketName check
5303
+ const isOnbbo = message.type === 'queryonbbol1';
5304
+ const shouldShowNoData = message['0'].NotFound === true ||
5305
+ (!isOnbbo && (!message['0'].MarketName || message['0'].MarketName !== 'BLUE'));
5306
+ if (shouldShowNoData) {
5307
+ // Only show no data state if we don't have cached data
5308
+ if (!this.data) {
5309
+ this.showNoDataState(message['0']);
5310
+ } else {
5311
+ this.hideLoading();
5312
+ if (this.debug) {
5313
+ console.log('[NightSessionWidget] No new data, keeping cached data visible');
5314
+ }
5315
+ }
5316
+ } else {
5317
+ const model = new NightSessionModel(message['0']);
5318
+ this.data = model; // Store for caching
5319
+ this.updateWidget(model);
5397
5320
  }
5398
- }
5399
5321
  } else {
5400
- const model = new NightSessionModel(message['0']);
5401
- this.data = model; // Store for caching
5402
- this.updateWidget(model);
5403
- }
5404
- } else {
5405
- // No matching symbol - keep cached data if available
5406
- if (this.debug) {
5407
- console.log('[NightSessionWidget] No matching symbol in response, keeping cached data');
5322
+ // No matching symbol - keep cached data if available
5323
+ if (this.debug) {
5324
+ console.log('[NightSessionWidget] No matching symbol in response, keeping cached data');
5325
+ }
5326
+ this.hideLoading();
5408
5327
  }
5409
- this.hideLoading();
5410
- }
5411
- }
5328
+ } */
5412
5329
  if (message._cached) ;
5413
5330
  }
5414
5331
  updateWidget(data) {
@@ -6238,36 +6155,20 @@ class OptionsWidget extends BaseWidget {
6238
6155
  this.showLoading();
6239
6156
 
6240
6157
  // Validate initial symbol via API to get symbol info
6241
- await this.validateInitialSymbol();
6242
- this.subscribeToData();
6243
- }
6244
- async validateInitialSymbol() {
6245
- try {
6246
- const apiService = this.wsManager.getApiService();
6247
- if (!apiService) {
6248
- if (this.debug) {
6249
- console.log('[OptionsWidget] API service not available for initial validation');
6250
- }
6251
- 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];
6252
6164
  }
6253
- const result = await apiService.quoteOptionl1(this.symbol);
6254
- if (result && result[0] && !result[0].error && !result[0].not_found) {
6255
- const symbolData = result[0];
6256
- // Store for use in updateWidget
6257
- this.initialValidationData = symbolData;
6258
- if (this.debug) {
6259
- console.log('[OptionsWidget] Initial symbol validated:', {
6260
- symbol: this.symbol,
6261
- underlying: symbolData.Underlying || symbolData.RootSymbol
6262
- });
6263
- }
6264
- }
6265
- } catch (error) {
6266
- if (this.debug) {
6267
- console.warn('[OptionsWidget] Initial symbol validation failed:', error);
6268
- }
6269
- // 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
6270
6170
  }
6171
+ this.subscribeToData();
6271
6172
  }
6272
6173
  subscribeToData() {
6273
6174
  // Subscribe with symbol for routing
@@ -38624,7 +38525,20 @@ class IntradayChartWidget extends BaseWidget {
38624
38525
  }
38625
38526
  async initialize() {
38626
38527
  // Validate initial symbol via API to get company info
38627
- await this.validateInitialSymbol();
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
+ }
38628
38542
 
38629
38543
  // Update company name in the header
38630
38544
  this.updateCompanyName();
@@ -38632,38 +38546,6 @@ class IntradayChartWidget extends BaseWidget {
38632
38546
  // Load chart data
38633
38547
  await this.loadChartData();
38634
38548
  }
38635
- async validateInitialSymbol() {
38636
- try {
38637
- const apiService = this.wsManager.getApiService();
38638
- if (!apiService) {
38639
- if (this.debug) {
38640
- console.log('[IntradayChartWidget] API service not available for initial validation');
38641
- }
38642
- return;
38643
- }
38644
- const result = await apiService.quotel1(this.symbol);
38645
- if (result && result.data && result.data[0]) {
38646
- const symbolData = result.data[0];
38647
- // Extract company info
38648
- this.companyName = symbolData.comp_name || '';
38649
- this.exchangeName = symbolData.market_name || '';
38650
- this.mic = symbolData.mic || '';
38651
- if (this.debug) {
38652
- console.log('[IntradayChartWidget] Initial symbol validated:', {
38653
- symbol: this.symbol,
38654
- companyName: this.companyName,
38655
- exchangeName: this.exchangeName,
38656
- mic: this.mic
38657
- });
38658
- }
38659
- }
38660
- } catch (error) {
38661
- if (this.debug) {
38662
- console.warn('[IntradayChartWidget] Initial symbol validation failed:', error);
38663
- }
38664
- // Don't throw - let the widget continue with chart data
38665
- }
38666
- }
38667
38549
  updateCompanyName() {
38668
38550
  const companyNameElement = this.container.querySelector('.intraday-company-name');
38669
38551
  if (companyNameElement) {
@@ -39085,21 +38967,14 @@ class IntradayChartWidget extends BaseWidget {
39085
38967
  let priceData = null;
39086
38968
 
39087
38969
  // Handle array format (standard night session format)
39088
- if (Array.isArray(message)) {
38970
+ if (Array.isArray(message.data)) {
39089
38971
  console.log('[IntradayChartWidget] Processing array format, length:', message.length);
39090
- const symbolData = message.find(item => item.Symbol === this.symbol);
38972
+ const symbolData = message.data.find(item => item.Symbol === this.symbol);
39091
38973
  console.log('[IntradayChartWidget] Found symbol data:', symbolData);
39092
38974
  if (symbolData && !symbolData.NotFound) {
39093
38975
  priceData = symbolData;
39094
38976
  }
39095
38977
  }
39096
- // Handle wrapped format
39097
- else if (message.type === 'queryblueoceanl1' || message.type === 'querybrucel1') {
39098
- console.log('[IntradayChartWidget] Processing wrapped format');
39099
- if (message['0']?.Symbol === this.symbol && !message['0'].NotFound) {
39100
- priceData = message['0'];
39101
- }
39102
- }
39103
38978
  // Handle direct data format
39104
38979
  else if (message.Symbol === this.symbol && !message.NotFound) {
39105
38980
  console.log('[IntradayChartWidget] Processing direct format');
@@ -40056,8 +39931,10 @@ class ONBBOLevel2Widget extends BaseWidget {
40056
39931
  this.styled = options.styled !== undefined ? options.styled : true;
40057
39932
  this.maxLevels = options.maxLevels || 10; // Number of levels to display
40058
39933
  this.data = null;
39934
+ this.level1Data = null; // Store Level 1 data separately
40059
39935
  this.isDestroyed = false;
40060
- this.unsubscribe = null;
39936
+ this.unsubscribeL2 = null;
39937
+ this.unsubscribeL1 = null;
40061
39938
  this.symbolEditor = null;
40062
39939
  this.loadingTimeout = null;
40063
39940
 
@@ -40158,13 +40035,22 @@ class ONBBOLevel2Widget extends BaseWidget {
40158
40035
  this.showError(`No data received for ${upperSymbol}. Please try again.`);
40159
40036
  }, 10000);
40160
40037
 
40161
- // Unsubscribe from old symbol
40162
- if (this.unsubscribe) {
40038
+ // Unsubscribe from old symbol's L2 and L1 data
40039
+ if (this.unsubscribeL2) {
40163
40040
  if (this.debug) {
40164
- console.log(`[ONBBOLevel2Widget] Unsubscribing from ${this.symbol}`);
40041
+ console.log(`[ONBBOLevel2Widget] Unsubscribing from ${this.symbol} L2`);
40165
40042
  }
40166
- this.unsubscribe();
40167
- this.unsubscribe = null;
40043
+ this.wsManager.sendUnsubscribe('queryonbbol2', this.symbol);
40044
+ this.unsubscribeL2();
40045
+ this.unsubscribeL2 = null;
40046
+ }
40047
+ if (this.unsubscribeL1) {
40048
+ if (this.debug) {
40049
+ console.log(`[ONBBOLevel2Widget] Unsubscribing from ${this.symbol} L1`);
40050
+ }
40051
+ this.wsManager.sendUnsubscribe('queryonbbol1', this.symbol);
40052
+ this.unsubscribeL1();
40053
+ this.unsubscribeL1 = null;
40168
40054
  }
40169
40055
 
40170
40056
  // Update internal symbol
@@ -40191,7 +40077,19 @@ class ONBBOLevel2Widget extends BaseWidget {
40191
40077
  this.showLoading();
40192
40078
 
40193
40079
  // Fetch company info for initial symbol
40194
- await this.validateInitialSymbol();
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
+ }
40195
40093
 
40196
40094
  // Set timeout to detect no data on initial load
40197
40095
  this.loadingTimeout = setTimeout(() => {
@@ -40203,41 +40101,52 @@ class ONBBOLevel2Widget extends BaseWidget {
40203
40101
  }, 10000);
40204
40102
  this.subscribeToData();
40205
40103
  }
40206
- async validateInitialSymbol() {
40207
- try {
40208
- const apiService = this.wsManager.getApiService();
40209
- if (!apiService) {
40210
- if (this.debug) {
40211
- console.log('[ONBBO L2] API service not available for initial validation');
40212
- }
40104
+ subscribeToData() {
40105
+ // Subscribe to ONBBO Level 2 data (order book)
40106
+ // Use separate widget IDs to avoid "already active" duplicate detection
40107
+ this.unsubscribeL2 = this.wsManager.subscribe(`${this.widgetId}-l2`, ['queryonbbol2'], messageWrapper => {
40108
+ const {
40109
+ event,
40110
+ data
40111
+ } = messageWrapper;
40112
+
40113
+ // Handle connection events
40114
+ if (event === 'connection') {
40115
+ this.handleConnectionStatus(data);
40213
40116
  return;
40214
40117
  }
40215
- const result = await apiService.quotel1(this.symbol);
40216
- if (result && result.data && result.data[0]) {
40217
- const symbolData = result.data[0];
40218
- // Extract company info
40219
- this.companyName = symbolData.comp_name || '';
40220
- this.exchangeName = symbolData.market_name || '';
40221
- if (this.debug) {
40222
- console.log('[ONBBO L2] Initial symbol validated:', {
40223
- symbol: this.symbol,
40224
- companyName: this.companyName,
40225
- exchangeName: this.exchangeName
40226
- });
40227
- }
40118
+
40119
+ // For data events, add type context
40120
+ if (event === 'data') {
40121
+ data._dataType = 'level2';
40122
+ this.handleMessage({
40123
+ event,
40124
+ data
40125
+ });
40228
40126
  }
40229
- } catch (error) {
40230
- if (this.debug) {
40231
- console.warn('[ONBBO L2] Initial symbol validation failed:', error);
40127
+ }, this.symbol);
40128
+
40129
+ // Subscribe to ONBBO Level 1 data (statistics: LastPx, LowPx, HighPx, Volume)
40130
+ this.unsubscribeL1 = this.wsManager.subscribe(`${this.widgetId}-l1`, ['queryonbbol1'], messageWrapper => {
40131
+ const {
40132
+ event,
40133
+ data
40134
+ } = messageWrapper;
40135
+
40136
+ // Connection already handled by L2 subscription
40137
+ if (event === 'connection') {
40138
+ return;
40232
40139
  }
40233
- // Don't throw - let the widget continue with WebSocket data
40234
- }
40235
- }
40236
- subscribeToData() {
40237
- // Subscribe to ONBBO Level 2 data
40238
- this.unsubscribe = this.wsManager.subscribe(this.widgetId, ['queryonbbol2'],
40239
- // ONBBO Level 2 subscription type
40240
- this.handleMessage.bind(this), this.symbol);
40140
+
40141
+ // For data events, add type context
40142
+ if (event === 'data') {
40143
+ data._dataType = 'level1';
40144
+ this.handleMessage({
40145
+ event,
40146
+ data
40147
+ });
40148
+ }
40149
+ }, this.symbol);
40241
40150
  }
40242
40151
  handleData(message) {
40243
40152
  if (this.loadingTimeout) {
@@ -40245,8 +40154,14 @@ class ONBBOLevel2Widget extends BaseWidget {
40245
40154
  this.loadingTimeout = null;
40246
40155
  }
40247
40156
 
40157
+ // Extract data type from metadata (added by subscription callback)
40158
+ const dataType = message._dataType;
40159
+ if (this.debug) {
40160
+ console.log(`[ONBBOLevel2Widget] handleData called with type: ${dataType}`, message);
40161
+ }
40162
+
40248
40163
  // Handle error messages
40249
- if (message.type === 'error') {
40164
+ if (message.type === 'error' || message.error == true) {
40250
40165
  const errorMsg = message.message || 'Server error';
40251
40166
  if (this.debug) {
40252
40167
  console.log('[ONBBOLevel2Widget] Error:', errorMsg);
@@ -40255,16 +40170,54 @@ class ONBBOLevel2Widget extends BaseWidget {
40255
40170
  return;
40256
40171
  }
40257
40172
 
40173
+ // Handle Level 1 data (statistics: LastPx, LowPx, HighPx, Volume)
40174
+ if (message.type === 'queryonbbol1') {
40175
+ if (this.debug) {
40176
+ console.log('[ONBBOLevel2Widget] Processing Level 1 data:', message);
40177
+ }
40178
+
40179
+ // Handle array format (new format with Data wrapper)
40180
+ if (message.data && Array.isArray(message.data)) {
40181
+ const l1Data = message.data.find(d => d.Symbol === this.symbol);
40182
+ if (l1Data) {
40183
+ this.level1Data = {
40184
+ lastPx: l1Data.LastPx || 0,
40185
+ lowPx: l1Data.LowPx || 0,
40186
+ highPx: l1Data.HighPx || 0,
40187
+ volume: l1Data.Volume || 0
40188
+ };
40189
+ this.updateLevel1Display();
40190
+ }
40191
+ }
40192
+ // Handle direct array format (standard format)
40193
+ else if (Array.isArray(message)) {
40194
+ const l1Data = message.find(d => d.Symbol === this.symbol);
40195
+ if (l1Data) {
40196
+ this.level1Data = {
40197
+ lastPx: l1Data.LastPx || 0,
40198
+ lowPx: l1Data.LowPx || 0,
40199
+ highPx: l1Data.HighPx || 0,
40200
+ volume: l1Data.Volume || 0
40201
+ };
40202
+ this.updateLevel1Display();
40203
+ }
40204
+ }
40205
+
40206
+ // Clean up metadata
40207
+ delete message._dataType;
40208
+ return;
40209
+ }
40210
+
40258
40211
  // Handle Level 2 data - Array of MMID quotes
40259
- if (Array.isArray(message)) {
40212
+ if (Array.isArray(message.data)) {
40260
40213
  // Check if it's an array of MMID objects (ONBBO format)
40261
- if (message.length > 0 && message[0].MMID) {
40214
+ if (message.data.length > 0 && message.data[0].MMID) {
40262
40215
  if (this.debug) {
40263
40216
  console.log('[ONBBOLevel2Widget] Received MMID array:', message);
40264
40217
  }
40265
40218
 
40266
40219
  // Create model from MMID array
40267
- const model = new ONBBOLevel2Model(message);
40220
+ const model = new ONBBOLevel2Model(message.data);
40268
40221
  model.symbol = this.symbol; // Set symbol from widget
40269
40222
  this.data = model;
40270
40223
  this.updateWidget(model);
@@ -40272,7 +40225,7 @@ class ONBBOLevel2Widget extends BaseWidget {
40272
40225
  }
40273
40226
 
40274
40227
  // Try to find symbol in array (alternative format)
40275
- const symbolData = message.find(item => item.Symbol === this.symbol);
40228
+ const symbolData = message.data.find(item => item.Symbol === this.symbol);
40276
40229
  if (symbolData) {
40277
40230
  if (symbolData.NotFound === true) {
40278
40231
  this.showError(`No Level 2 data available for ${this.symbol}`);
@@ -40286,32 +40239,31 @@ class ONBBOLevel2Widget extends BaseWidget {
40286
40239
  this.showError(`No Level 2 data available for ${this.symbol}`);
40287
40240
  }
40288
40241
  // Handle wrapped format
40289
- else if (message.type === 'queryonbbol2') {
40290
- // Check if wrapped data contains MMID array
40291
- if (message['0'] && Array.isArray(message['0'])) {
40292
- if (this.debug) {
40293
- console.log('[ONBBOLevel2Widget] Received wrapped MMID array:', message['0']);
40242
+ /* else if (message.type === 'queryonbbol2') {
40243
+ // Check if wrapped data contains MMID array
40244
+ if (message['0'] && Array.isArray(message['0'])) {
40245
+ if (this.debug) {
40246
+ console.log('[ONBBOLevel2Widget] Received wrapped MMID array:', message['0']);
40247
+ }
40248
+ const model = new ONBBOLevel2Model(message['0']);
40249
+ model.symbol = this.symbol;
40250
+ this.data = model;
40251
+ this.updateWidget(model);
40252
+ return;
40294
40253
  }
40295
- const model = new ONBBOLevel2Model(message['0']);
40296
- model.symbol = this.symbol;
40297
- this.data = model;
40298
- this.updateWidget(model);
40299
- return;
40300
- }
40301
-
40302
- // Check for symbol match in wrapped format
40303
- if (message['0']?.Symbol === this.symbol) {
40304
- if (message['0'].NotFound === true) {
40305
- this.showError(`No Level 2 data available for ${this.symbol}`);
40254
+ // Check for symbol match in wrapped format
40255
+ if (message['0']?.Symbol === this.symbol) {
40256
+ if (message['0'].NotFound === true) {
40257
+ this.showError(`No Level 2 data available for ${this.symbol}`);
40258
+ } else {
40259
+ const model = new ONBBOLevel2Model(message['0']);
40260
+ this.data = model;
40261
+ this.updateWidget(model);
40262
+ }
40306
40263
  } else {
40307
- const model = new ONBBOLevel2Model(message['0']);
40308
- this.data = model;
40309
- this.updateWidget(model);
40264
+ this.showError(`No Level 2 data available for ${this.symbol}`);
40310
40265
  }
40311
- } else {
40312
- this.showError(`No Level 2 data available for ${this.symbol}`);
40313
- }
40314
- }
40266
+ } */
40315
40267
  }
40316
40268
  updateWidget(data) {
40317
40269
  if (this.isDestroyed) return;
@@ -40409,6 +40361,43 @@ class ONBBOLevel2Widget extends BaseWidget {
40409
40361
  askBody.appendChild(row);
40410
40362
  });
40411
40363
  }
40364
+ updateLevel1Display() {
40365
+ if (!this.level1Data) return;
40366
+ const formatPrice = price => {
40367
+ return price ? price.toFixed(2) : '--';
40368
+ };
40369
+ const formatVolume = volume => {
40370
+ if (!volume) return '--';
40371
+ return volume.toLocaleString();
40372
+ };
40373
+
40374
+ // Update Last Price
40375
+ const lastPxElement = this.container.querySelector('.l1-last-px');
40376
+ if (lastPxElement) {
40377
+ lastPxElement.textContent = formatPrice(this.level1Data.lastPx);
40378
+ }
40379
+
40380
+ // Update Low Price
40381
+ const lowPxElement = this.container.querySelector('.l1-low-px');
40382
+ if (lowPxElement) {
40383
+ lowPxElement.textContent = formatPrice(this.level1Data.lowPx);
40384
+ }
40385
+
40386
+ // Update High Price
40387
+ const highPxElement = this.container.querySelector('.l1-high-px');
40388
+ if (highPxElement) {
40389
+ highPxElement.textContent = formatPrice(this.level1Data.highPx);
40390
+ }
40391
+
40392
+ // Update Volume
40393
+ const volumeElement = this.container.querySelector('.l1-volume');
40394
+ if (volumeElement) {
40395
+ volumeElement.textContent = formatVolume(this.level1Data.volume);
40396
+ }
40397
+ if (this.debug) {
40398
+ console.log('[ONBBOLevel2Widget] Updated Level 1 display:', this.level1Data);
40399
+ }
40400
+ }
40412
40401
  showLoading() {
40413
40402
  const loadingOverlay = this.container.querySelector('.widget-loading-overlay');
40414
40403
  if (loadingOverlay) {
@@ -40455,9 +40444,17 @@ class ONBBOLevel2Widget extends BaseWidget {
40455
40444
  this.clearTimeout(this.loadingTimeout);
40456
40445
  this.loadingTimeout = null;
40457
40446
  }
40458
- if (this.unsubscribe) {
40459
- this.unsubscribe();
40460
- this.unsubscribe = null;
40447
+
40448
+ // Unsubscribe from L2 data
40449
+ if (this.unsubscribeL2) {
40450
+ this.unsubscribeL2();
40451
+ this.unsubscribeL2 = null;
40452
+ }
40453
+
40454
+ // Unsubscribe from L1 data
40455
+ if (this.unsubscribeL1) {
40456
+ this.unsubscribeL1();
40457
+ this.unsubscribeL1 = null;
40461
40458
  }
40462
40459
 
40463
40460
  // Destroy the symbol editor
@@ -40622,6 +40619,7 @@ class TimeSalesWidget extends BaseWidget {
40622
40619
  this.styled = options.styled !== undefined ? options.styled : true;
40623
40620
  this.maxTrades = options.maxTrades || 20; // Maximum number of trades to display
40624
40621
  this.data = new TimeSalesModel([], this.maxTrades);
40622
+ this.data.symbol = this.symbol; // Set the symbol in the data model
40625
40623
  this.isDestroyed = false;
40626
40624
  this.unsubscribe = null;
40627
40625
  this.symbolEditor = null;
@@ -40633,6 +40631,7 @@ class TimeSalesWidget extends BaseWidget {
40633
40631
 
40634
40632
  // Create widget structure
40635
40633
  this.createWidgetStructure();
40634
+ this.initializeSymbolEditor();
40636
40635
 
40637
40636
  // Initialize the widget
40638
40637
  this.initialize();
@@ -40649,7 +40648,26 @@ class TimeSalesWidget extends BaseWidget {
40649
40648
  }
40650
40649
  }
40651
40650
  this.addStyles();
40652
- 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
+ }
40653
40671
  }
40654
40672
  addStyles() {
40655
40673
  // Inject styles from the styles file into document head
@@ -40672,6 +40690,8 @@ class TimeSalesWidget extends BaseWidget {
40672
40690
  // Keep editor open when clicking buttons
40673
40691
  symbolType: 'quotel1' // Use standard quote validation
40674
40692
  });
40693
+
40694
+ //console.log('SETUP COMPLETE')
40675
40695
  }
40676
40696
  async handleSymbolChange(newSymbol, oldSymbol) {
40677
40697
  let validationData = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
@@ -40774,7 +40794,19 @@ class TimeSalesWidget extends BaseWidget {
40774
40794
  this.showLoading();
40775
40795
 
40776
40796
  // Validate initial symbol via API to get company info
40777
- await this.validateInitialSymbol();
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
+ }
40778
40810
 
40779
40811
  // Set timeout to detect no data on initial load
40780
40812
  this.loadingTimeout = setTimeout(() => {
@@ -40787,36 +40819,6 @@ class TimeSalesWidget extends BaseWidget {
40787
40819
 
40788
40820
  this.subscribeToData();
40789
40821
  }
40790
- async validateInitialSymbol() {
40791
- try {
40792
- const apiService = this.wsManager.getApiService();
40793
- if (!apiService) {
40794
- if (this.debug) {
40795
- console.log('[TimeSalesWidget] API service not available for initial validation');
40796
- }
40797
- return;
40798
- }
40799
- const result = await apiService.quotel1(this.symbol);
40800
- if (result && result.data && result.data[0]) {
40801
- const symbolData = result.data[0];
40802
- // Extract company info
40803
- this.companyName = symbolData.comp_name || '';
40804
- this.exchangeName = symbolData.market_name || '';
40805
- if (this.debug) {
40806
- console.log('[TimeSalesWidget] Initial symbol validated:', {
40807
- symbol: this.symbol,
40808
- companyName: this.companyName,
40809
- exchangeName: this.exchangeName
40810
- });
40811
- }
40812
- }
40813
- } catch (error) {
40814
- if (this.debug) {
40815
- console.warn('[TimeSalesWidget] Initial symbol validation failed:', error);
40816
- }
40817
- // Don't throw - let the widget continue with WebSocket data
40818
- }
40819
- }
40820
40822
  subscribeToData() {
40821
40823
  // Subscribe to Time & Sales data (using queryonbbotimesale)
40822
40824
  this.unsubscribe = this.wsManager.subscribe(this.widgetId, ['queryonbbotimesale'],
@@ -40832,6 +40834,9 @@ class TimeSalesWidget extends BaseWidget {
40832
40834
  clearTimeout(this.loadingTimeout);
40833
40835
  this.loadingTimeout = null;
40834
40836
  }
40837
+ if (message.type != 'queryonbbotimesale') {
40838
+ return;
40839
+ }
40835
40840
 
40836
40841
  // Handle error messages
40837
40842
  if (message.type === 'error') {
@@ -40844,30 +40849,16 @@ class TimeSalesWidget extends BaseWidget {
40844
40849
  }
40845
40850
 
40846
40851
  // Handle Time & Sales data - Array of trades
40847
- if (Array.isArray(message)) {
40852
+ if (Array.isArray(message.data)) {
40848
40853
  if (this.debug) {
40849
40854
  console.log('[TimeSalesWidget] Received trades array:', message);
40850
40855
  }
40851
40856
 
40852
40857
  // Add new trades to existing data
40853
- this.data.addTrades(message);
40858
+ this.data.addTrades(message.data);
40854
40859
  this.updateWidget();
40855
40860
  return;
40856
40861
  }
40857
-
40858
- // Handle wrapped format
40859
- if ((message.type === 'queryonbbotimesale' || message.type === 'querytns') && message['0']) {
40860
- if (Array.isArray(message['0'])) {
40861
- if (this.debug) {
40862
- console.log('[TimeSalesWidget] Received wrapped trades array:', message['0']);
40863
- }
40864
-
40865
- // Add new trades to existing data
40866
- this.data.addTrades(message['0']);
40867
- this.updateWidget();
40868
- return;
40869
- }
40870
- }
40871
40862
  if (this.debug) {
40872
40863
  console.log('[TimeSalesWidget] Unexpected message format:', message);
40873
40864
  }
@@ -41094,7 +41085,15 @@ class ApiService {
41094
41085
  try {
41095
41086
  const response = await fetch(url, config);
41096
41087
  if (!response.ok) {
41097
- throw new Error(`HTTP error! status: ${response.status}`);
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}`);
41098
41097
  }
41099
41098
  return await response.json();
41100
41099
  } catch (error) {
@@ -41583,6 +41582,7 @@ class WebSocketManager {
41583
41582
  }
41584
41583
  }
41585
41584
  handleMessage(event) {
41585
+ //console.log('EVENT', event)
41586
41586
  try {
41587
41587
  // Update activity tracking - connection is alive!
41588
41588
  this.lastMessageReceived = Date.now();
@@ -41607,22 +41607,20 @@ class WebSocketManager {
41607
41607
  return;
41608
41608
  }
41609
41609
 
41610
- // Determine target type based on error content
41611
- const targetType = this._getTargetTypeFromErrorMessage(textMessage);
41610
+ // Determine type based on error content, default to 'error'
41611
+ const errorType = this._getTargetTypeFromErrorMessage(textMessage) || 'error';
41612
41612
  if (textMessage.toLowerCase().includes('no night session') || textMessage.toLowerCase().includes('no data')) {
41613
41613
  message = {
41614
- type: 'error',
41614
+ type: errorType,
41615
41615
  message: textMessage,
41616
- error: textMessage,
41617
- noData: true,
41618
- targetType: targetType // Will be 'querynightsession' for night session errors
41616
+ error: true,
41617
+ noData: true
41619
41618
  };
41620
41619
  } else {
41621
41620
  message = {
41622
- type: 'error',
41621
+ type: errorType,
41623
41622
  message: textMessage,
41624
- error: textMessage,
41625
- targetType: targetType // Could be null for general errors
41623
+ error: true
41626
41624
  };
41627
41625
  }
41628
41626
  }
@@ -41630,9 +41628,16 @@ class WebSocketManager {
41630
41628
  console.log('[WebSocketManager] Processed message:', message);
41631
41629
  }
41632
41630
 
41633
- // Handle new message format with type and data fields (case-insensitive)
41634
- const normalizedMessage = this._normalizeMessage(message);
41635
- this._routeMessage(normalizedMessage);
41631
+ // Extract data field for night session messages to avoid nested structure
41632
+ // Night session format: { type: 'queryonbbol1', error: false, data: [...] }
41633
+ // We want to send just the data array to widgets
41634
+ let dataToRoute = message;
41635
+ /* if (message.data !== undefined && message.type !== undefined) {
41636
+ // This is night session format - extract the data
41637
+ dataToRoute = message.data;
41638
+ } */
41639
+
41640
+ this._routeMessage(dataToRoute);
41636
41641
  } catch (error) {
41637
41642
  console.error('[WebSocketManager] Error handling message:', error);
41638
41643
  this._notifyWidgets('error', {
@@ -41771,7 +41776,31 @@ class WebSocketManager {
41771
41776
  }
41772
41777
  } else {
41773
41778
  if (this.config.debug) {
41774
- console.log(`[WebSocketManager] Subscription ${subscriptionKey} already active, skipping`);
41779
+ console.log(`[WebSocketManager] Subscription ${subscriptionKey} already active, skipping server subscription`);
41780
+ }
41781
+
41782
+ // Subscription already active, but send cached data to newly subscribing widgets
41783
+ const cachedMessage = this.lastMessageCache.get(subscriptionKey);
41784
+ console.log('LAST', this.lastMessageCache);
41785
+ if (cachedMessage) {
41786
+ if (this.config.debug) {
41787
+ console.log(`[WebSocketManager] Sending cached data to newly subscribing widgets for ${subscriptionKey}`);
41788
+ }
41789
+
41790
+ // Find all widgets subscribed to this type:symbol combination
41791
+ this.subscriptions.forEach((subscription, widgetId) => {
41792
+ if (subscription.types.has(type) && subscription.symbol === symbol) {
41793
+ try {
41794
+ subscription.callback({
41795
+ event: 'data',
41796
+ data: cachedMessage,
41797
+ widgetId
41798
+ });
41799
+ } catch (error) {
41800
+ console.error(`[WebSocketManager] Error sending cached data to widget ${widgetId}:`, error);
41801
+ }
41802
+ }
41803
+ });
41775
41804
  }
41776
41805
  }
41777
41806
  });
@@ -41823,14 +41852,9 @@ class WebSocketManager {
41823
41852
  * The data field structure varies based on the type
41824
41853
  */
41825
41854
  _normalizeMessage(message) {
41826
- // If message already has targetType (error messages), return as is
41827
- if (message.targetType) {
41828
- return message;
41829
- }
41830
-
41831
41855
  // Check for error field (case-insensitive)
41832
- const errorField = message.error || message.Error;
41833
- const typeField = message.type || message.Type;
41856
+ const errorField = message.error;
41857
+ const typeField = message.type;
41834
41858
  const messageField = message.message || message.Message;
41835
41859
 
41836
41860
  // Handle explicit error messages - route based on type field
@@ -41838,13 +41862,12 @@ class WebSocketManager {
41838
41862
  if (this.config.debug) {
41839
41863
  console.log(`[WebSocketManager] Detected error message with type: ${typeField}`);
41840
41864
  }
41841
-
41842
- // Set targetType for routing to specific widget types
41843
41865
  return {
41844
41866
  ...message,
41845
- targetType: typeField,
41846
- type: typeField,
41847
- isError: true
41867
+ // Only add type if message doesn't already have one
41868
+ ...(!message.type && !message.Type && typeField ? {
41869
+ type: typeField
41870
+ } : {})
41848
41871
  };
41849
41872
  }
41850
41873
 
@@ -41862,92 +41885,30 @@ class WebSocketManager {
41862
41885
  // Treat as error and route based on type
41863
41886
  return {
41864
41887
  ...message,
41865
- targetType: typeField,
41866
- type: typeField,
41867
- isError: true,
41868
- error: true // Mark as error even if it was false
41888
+ // Only add type if message doesn't already have one
41889
+ ...(!message.type && !message.Type && typeField ? {
41890
+ type: typeField
41891
+ } : {}),
41892
+ error: true // Mark as error since it was detected as implicit error
41869
41893
  };
41870
41894
  }
41871
41895
  }
41872
41896
 
41873
- // Check for new format: has 'type'/'Type' AND 'data'/'Data' fields
41874
- const dataField = message.data || message.Data;
41875
- if (typeField && dataField !== undefined) {
41876
- if (this.config.debug) {
41877
- console.log(`[WebSocketManager] Detected new message format with type: ${typeField}`);
41878
- }
41879
-
41880
- // Extract the actual data and add the type to it for routing
41881
- let normalizedData;
41882
- if (Array.isArray(dataField)) {
41883
- // If data is an array, process each item
41884
- normalizedData = dataField;
41885
- // Add type info to the structure for routing
41886
- if (normalizedData.length > 0) {
41887
- normalizedData._messageType = typeField;
41888
- }
41889
- } else if (typeof dataField === 'object' && dataField !== null) {
41890
- // If data is an object, use it directly
41891
- normalizedData = dataField;
41892
- // Add type info for routing
41893
- normalizedData._messageType = typeField;
41894
- } else {
41895
- // Primitive value, wrap it
41896
- normalizedData = {
41897
- value: dataField,
41898
- _messageType: typeField
41899
- };
41900
- }
41901
-
41902
- // Also preserve the original type at the root level for backward compatibility
41903
- if (typeof normalizedData === 'object' && !normalizedData.type) {
41904
- normalizedData.type = typeField;
41905
- }
41906
- return normalizedData;
41907
- }
41908
-
41909
41897
  // Old format or no type/data structure, return as is
41910
41898
  return message;
41911
41899
  }
41912
41900
  _routeMessage(message) {
41913
41901
  //console.log('message', message);
41914
41902
 
41915
- if (Array.isArray(message) && message.length === 0) {
41916
- if (this.config.debug) {
41917
- console.log('[WebSocketManager] Received empty array, ignoring');
41918
- }
41919
- return; // Don't route empty arrays
41920
- }
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
41921
41907
 
41922
41908
  // Cache the message for later use by new subscribers
41923
41909
  this._cacheMessage(message);
41924
41910
 
41925
- // Check if message has a specific target type (like night session errors)
41926
- if (message.targetType) {
41927
- const targetWidgets = this.typeSubscriptions.get(message.targetType);
41928
- if (targetWidgets && targetWidgets.size > 0) {
41929
- if (this.config.debug) {
41930
- console.log(`[WebSocketManager] Routing ${message.targetType} error to specific widgets:`, [...targetWidgets]);
41931
- }
41932
- targetWidgets.forEach(widgetId => {
41933
- const subscription = this.subscriptions.get(widgetId);
41934
- if (subscription) {
41935
- try {
41936
- subscription.callback({
41937
- event: 'data',
41938
- data: message,
41939
- widgetId
41940
- });
41941
- } catch (error) {
41942
- console.error(`[WebSocketManager] Error in widget ${widgetId} callback:`, error);
41943
- }
41944
- }
41945
- });
41946
- return; // Don't fall through to broadcast
41947
- }
41948
- }
41949
-
41950
- //console.log('here');
41911
+ // Get relevant widgets based on message type and symbol
41951
41912
  const relevantWidgets = this._getRelevantWidgets(message);
41952
41913
  if (this.config.debug && relevantWidgets.size > 0) {
41953
41914
  console.log(`[WebSocketManager] Routing message to ${relevantWidgets.size} relevant widgets`);
@@ -41990,42 +41951,49 @@ class WebSocketManager {
41990
41951
  _cacheMessage(message) {
41991
41952
  try {
41992
41953
  // Extract symbol and message type to create cache key
41993
- const symbol = this._extractSymbol(message);
41994
- const messageType = this._extractMessageType(message);
41954
+ // This allows new widgets to get instant data when subscribing
41955
+ // check in message.data or message.Data
41956
+ let data;
41957
+ if (message.data) {
41958
+ data = message.data;
41959
+ } else if (message.Data) {
41960
+ data = message.Data;
41961
+ } else {
41962
+ data = message;
41963
+ }
41964
+ const symbol = this._extractSymbol(data);
41965
+ const messageType = message.type;
41966
+ if (this.config.debug) {
41967
+ console.log(`[WebSocketManager] _cacheMessage - extracted symbol: "${symbol}", messageType: "${messageType}"`);
41968
+ }
41969
+
41970
+ // Cache by type:symbol combination
41995
41971
  if (messageType && symbol) {
41996
41972
  const cacheKey = `${messageType}:${symbol}`;
41997
41973
  this.lastMessageCache.set(cacheKey, message);
41998
41974
  if (this.config.debug) {
41999
41975
  console.log(`[WebSocketManager] Cached message for ${cacheKey}`);
42000
41976
  }
42001
- }
42002
-
42003
- // Also handle array messages (for night session / blueocean data)
42004
- if (Array.isArray(message) && message.length > 0 && message[0].Symbol) {
42005
- const firstItem = message[0];
42006
- const symbol = firstItem.Symbol;
42007
- const messageType = this._extractMessageType(message);
42008
- if (messageType && symbol) {
42009
- const cacheKey = `${messageType}:${symbol}`;
42010
- this.lastMessageCache.set(cacheKey, message);
41977
+ } else {
41978
+ // If we can't extract symbol from message, try to cache by type only
41979
+ // This helps with data like ONBBO L2 which doesn't have symbol in the message
41980
+ if (messageType) {
42011
41981
  if (this.config.debug) {
42012
- console.log(`[WebSocketManager] Cached array message for ${cacheKey}`);
41982
+ console.log(`[WebSocketManager] No symbol found in message, attempting to infer from subscriptions for type: ${messageType}`);
42013
41983
  }
42014
- }
42015
- }
42016
41984
 
42017
- // Handle Data array format (for queryl1 data)
42018
- if (message.Data && Array.isArray(message.Data)) {
42019
- message.Data.forEach(dataItem => {
42020
- if (dataItem.Symbol) {
42021
- const cacheKey = `queryl1:${dataItem.Symbol}`;
42022
- // Cache the whole message, not just the item
42023
- this.lastMessageCache.set(cacheKey, message);
42024
- if (this.config.debug) {
42025
- console.log(`[WebSocketManager] Cached data item for ${cacheKey}`);
41985
+ // Find active subscriptions for this message type and cache for each
41986
+ this.activeSubscriptions.forEach(activeKey => {
41987
+ const [type, sym] = activeKey.split(':');
41988
+ if (type === messageType && sym) {
41989
+ const cacheKey = activeKey;
41990
+ this.lastMessageCache.set(cacheKey, message);
41991
+ if (this.config.debug) {
41992
+ console.log(`[WebSocketManager] Cached message for ${cacheKey} (inferred from active subscription)`);
41993
+ }
42026
41994
  }
42027
- }
42028
- });
41995
+ });
41996
+ }
42029
41997
  }
42030
41998
  } catch (error) {
42031
41999
  console.error('[WebSocketManager] Error caching message:', error);
@@ -42034,22 +42002,28 @@ class WebSocketManager {
42034
42002
  _getRelevantWidgets(message) {
42035
42003
  const relevantWidgets = new Set();
42036
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
+
42037
42010
  // Handle array messages
42038
- this._addRelevantWidgetsForItem(message, relevantWidgets);
42011
+ this._addRelevantWidgetsForItem(message, relevantWidgets, messageType);
42039
42012
  return relevantWidgets;
42040
42013
  }
42041
- _addRelevantWidgetsForItem(item, relevantWidgets) {
42014
+ _addRelevantWidgetsForItem(item, relevantWidgets, messageType) {
42042
42015
  // If item has Data array, process it
42043
42016
  if (item.Data && Array.isArray(item.Data)) {
42044
42017
  //console.log('Data array found with length:', item.Data.length);
42045
42018
  item.Data.forEach(dataItem => {
42046
- this._processDataItem(dataItem, relevantWidgets);
42019
+ // Pass messageType from parent message, don't extract from each data item
42020
+ this._processDataItem(dataItem, relevantWidgets, messageType);
42047
42021
  });
42048
42022
  } else if (item && item[0] && item[0].Strike !== undefined && item[0].Expire && !item[0].underlyingSymbol) {
42049
42023
  this._processOptionChainData(item, relevantWidgets);
42050
42024
  } else {
42051
- // Process single item
42052
- this._processDataItem(item, relevantWidgets);
42025
+ // Process single item - messageType already extracted from message
42026
+ this._processDataItem(item, relevantWidgets, messageType);
42053
42027
  }
42054
42028
  }
42055
42029
 
@@ -42093,12 +42067,11 @@ class WebSocketManager {
42093
42067
  }
42094
42068
  }
42095
42069
  }
42096
- _processDataItem(dataItem, relevantWidgets) {
42070
+ _processDataItem(dataItem, relevantWidgets, messageType) {
42097
42071
  // Process individual data items and route to appropriate widgets
42098
42072
  // Option chain arrays are handled separately by _processOptionChainData
42099
42073
 
42100
42074
  const symbol = this._extractSymbol(dataItem);
42101
- const messageType = this._extractMessageType(dataItem);
42102
42075
  if (this.config.debug) {
42103
42076
  console.log('[WebSocketManager] Processing data item:', {
42104
42077
  symbol,
@@ -42192,7 +42165,7 @@ class WebSocketManager {
42192
42165
  _extractSymbol(item) {
42193
42166
  // Handle new format where symbol might be in nested data structure
42194
42167
  // Check if this is an array with data that has _messageType (normalized new format)
42195
- if (Array.isArray(item) && item._messageType) {
42168
+ if (Array.isArray(item)) {
42196
42169
  // The data is an array, check first item for symbol
42197
42170
  if (item.length > 0 && item[0]) {
42198
42171
  return this._extractSymbolFromItem(item[0]);
@@ -42258,25 +42231,15 @@ class WebSocketManager {
42258
42231
  return symbolMappings[extracted] || extracted;
42259
42232
  }
42260
42233
  _extractMessageType(item) {
42261
- // Check for _messageType field added by normalization (from new format)
42262
- if (item._messageType) {
42263
- return item._messageType;
42264
- }
42265
-
42266
- // Determine message type based on content
42267
- if (item.type) return item.type;
42268
-
42269
- // Infer type from data structure
42234
+ // FALLBACK: Infer type from data structure (for legacy/malformed messages)
42270
42235
 
42271
42236
  // IMPORTANT: Check for MMID field FIRST to distinguish ONBBO L2 from L1
42272
42237
  // Level 2 ONBBO data - array of MMID objects or object with MMID field
42273
42238
  if (Array.isArray(item) && item.length > 0 && item[0].MMID !== undefined) {
42274
42239
  return 'queryonbbol2';
42275
42240
  }
42276
-
42277
- // Check for single MMID object
42278
- if (item.MMID !== undefined) {
42279
- return 'queryonbbol2';
42241
+ if (Array.isArray(item) && item.length > 0 && item[0].Strike !== undefined) {
42242
+ return 'queryoptionchain';
42280
42243
  }
42281
42244
 
42282
42245
  // Check for Source field AFTER MMID check