@trops/dash-core 0.1.158 → 0.1.160

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/index.js CHANGED
@@ -26713,7 +26713,7 @@ var useWidgetProviders = function useWidgetProviders() {
26713
26713
  var serverStates = new Map();
26714
26714
  // Map<serverName, { status, tools, resources, consumerCount }>
26715
26715
 
26716
- var pendingConnects = new Map();
26716
+ var pendingConnects$1 = new Map();
26717
26717
  // Map<serverName, Promise<result>>
26718
26718
 
26719
26719
  /**
@@ -26928,13 +26928,13 @@ var useMcpProvider = function useMcpProvider(providerType) {
26928
26928
  setError(null);
26929
26929
 
26930
26930
  // 2. Another hook instance already connecting? Piggyback on its promise
26931
- if (!pendingConnects.has(selectedProviderName)) {
26931
+ if (!pendingConnects$1.has(selectedProviderName)) {
26932
26932
  _context.next = 17;
26933
26933
  break;
26934
26934
  }
26935
26935
  _context.prev = 10;
26936
26936
  _context.next = 11;
26937
- return pendingConnects.get(selectedProviderName);
26937
+ return pendingConnects$1.get(selectedProviderName);
26938
26938
  case 11:
26939
26939
  result = _context.sent;
26940
26940
  if (mountedRef.current) {
@@ -26976,7 +26976,7 @@ var useMcpProvider = function useMcpProvider(providerType) {
26976
26976
  // 3. First caller — fire the IPC call and share the promise
26977
26977
  connectPromise = new Promise(function (resolve, reject) {
26978
26978
  dashApi.mcpStartServer(selectedProviderName, provider.mcpConfig, provider.credentials, function (event, result) {
26979
- pendingConnects["delete"](selectedProviderName);
26979
+ pendingConnects$1["delete"](selectedProviderName);
26980
26980
  if (result.error) {
26981
26981
  serverStates.set(selectedProviderName, {
26982
26982
  status: "error",
@@ -26997,7 +26997,7 @@ var useMcpProvider = function useMcpProvider(providerType) {
26997
26997
  });
26998
26998
  resolve(result);
26999
26999
  }, function (event, err) {
27000
- pendingConnects["delete"](selectedProviderName);
27000
+ pendingConnects$1["delete"](selectedProviderName);
27001
27001
  serverStates.set(selectedProviderName, {
27002
27002
  status: "error",
27003
27003
  tools: [],
@@ -27007,7 +27007,7 @@ var useMcpProvider = function useMcpProvider(providerType) {
27007
27007
  reject(err);
27008
27008
  });
27009
27009
  });
27010
- pendingConnects.set(selectedProviderName, connectPromise);
27010
+ pendingConnects$1.set(selectedProviderName, connectPromise);
27011
27011
  _context.prev = 18;
27012
27012
  _context.next = 19;
27013
27013
  return connectPromise;
@@ -27093,7 +27093,7 @@ var useMcpProvider = function useMcpProvider(providerType) {
27093
27093
  setResources([]);
27094
27094
  setStatus("disconnected");
27095
27095
  connectedRef.current = false;
27096
- pendingConnects["delete"](selectedProviderName);
27096
+ pendingConnects$1["delete"](selectedProviderName);
27097
27097
  return _context2.abrupt("return", new Promise(function (resolve) {
27098
27098
  dashApi.mcpStopServer(selectedProviderName, function () {
27099
27099
  return resolve();
@@ -27535,6 +27535,528 @@ var useScheduler = function useScheduler() {
27535
27535
  };
27536
27536
  };
27537
27537
 
27538
+ /**
27539
+ * Module-level shared state for WebSocket connections.
27540
+ * Prevents multiple hook instances (e.g., 4 widgets using the same WS provider)
27541
+ * from each firing their own IPC connect call.
27542
+ *
27543
+ * connectionStates: tracks connection status + consumer reference count per provider
27544
+ * pendingConnects: deduplicates in-flight IPC calls so only 1 fires per provider
27545
+ */
27546
+ var connectionStates = new Map();
27547
+ // Map<providerName, { status, consumerCount }>
27548
+
27549
+ var pendingConnects = new Map();
27550
+ // Map<providerName, Promise<result>>
27551
+
27552
+ var STATUS = {
27553
+ DISCONNECTED: "disconnected",
27554
+ CONNECTING: "connecting",
27555
+ CONNECTED: "connected",
27556
+ ERROR: "error"
27557
+ };
27558
+
27559
+ /**
27560
+ * useWebSocketProvider Hook
27561
+ *
27562
+ * Provides access to a WebSocket connection for a widget.
27563
+ * Handles connection lifecycle, shared-connection ref counting,
27564
+ * message buffering, and bidirectional communication.
27565
+ *
27566
+ * Mirrors useMcpProvider.js patterns exactly:
27567
+ * - Module-level Maps (not component state) for connection sharing
27568
+ * - consumerCount determines socket lifecycle
27569
+ * - pendingConnects prevents duplicate connect calls during mount storms
27570
+ *
27571
+ * @param {string} providerType - The WebSocket provider type (e.g., "crypto-ws")
27572
+ * @param {Object} options - Optional configuration
27573
+ * @param {boolean} options.autoConnect - Whether to auto-connect on mount (default: true)
27574
+ * @param {number} options.maxMessages - Max messages in buffer (default: 100)
27575
+ *
27576
+ * @returns {Object} WebSocket provider interface
27577
+ */
27578
+ var useWebSocketProvider = function useWebSocketProvider(providerType) {
27579
+ var _app$providers;
27580
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
27581
+ var _options$autoConnect = options.autoConnect,
27582
+ autoConnect = _options$autoConnect === void 0 ? true : _options$autoConnect,
27583
+ _options$maxMessages = options.maxMessages,
27584
+ maxMessages = _options$maxMessages === void 0 ? 100 : _options$maxMessages;
27585
+ var app = React.useContext(AppContext);
27586
+ var workspace = React.useContext(WorkspaceContext);
27587
+ var widgetContext = React.useContext(WidgetContext);
27588
+ var _useState = React.useState(false),
27589
+ _useState2 = _slicedToArray(_useState, 2),
27590
+ isConnected = _useState2[0],
27591
+ setIsConnected = _useState2[1];
27592
+ var _useState3 = React.useState(false),
27593
+ _useState4 = _slicedToArray(_useState3, 2),
27594
+ isConnecting = _useState4[0],
27595
+ setIsConnecting = _useState4[1];
27596
+ var _useState5 = React.useState(null),
27597
+ _useState6 = _slicedToArray(_useState5, 2),
27598
+ error = _useState6[0],
27599
+ setError = _useState6[1];
27600
+ var _useState7 = React.useState(null),
27601
+ _useState8 = _slicedToArray(_useState7, 2),
27602
+ lastMessage = _useState8[0],
27603
+ setLastMessage = _useState8[1];
27604
+ var _useState9 = React.useState([]),
27605
+ _useState0 = _slicedToArray(_useState9, 2),
27606
+ messages = _useState0[0],
27607
+ setMessages = _useState0[1];
27608
+ var _useState1 = React.useState(STATUS.DISCONNECTED),
27609
+ _useState10 = _slicedToArray(_useState1, 2),
27610
+ status = _useState10[0],
27611
+ setStatus = _useState10[1];
27612
+ var connectedRef = React.useRef(false);
27613
+ var mountedRef = React.useRef(true);
27614
+ var messagesRef = React.useRef([]);
27615
+ var maxMessagesRef = React.useRef(maxMessages);
27616
+ maxMessagesRef.current = maxMessages;
27617
+ var dashApi = app === null || app === void 0 ? void 0 : app.dashApi;
27618
+
27619
+ // Get the widget data
27620
+ var widgetData = widgetContext === null || widgetContext === void 0 ? void 0 : widgetContext.widgetData;
27621
+
27622
+ // Get the selected WebSocket provider for this widget
27623
+ // Same two-layer lookup as useMcpProvider:
27624
+ // 1. Widget-level: stored directly on the layout item
27625
+ // 2. Workspace-level: stored as workspace.selectedProviders[widgetId][providerType]
27626
+ var widgetId = widgetData === null || widgetData === void 0 ? void 0 : widgetData.uuidString;
27627
+ var selectedProviderName = function (_widgetData$selectedP, _workspace$workspaceD) {
27628
+ if (widgetData !== null && widgetData !== void 0 && (_widgetData$selectedP = widgetData.selectedProviders) !== null && _widgetData$selectedP !== void 0 && _widgetData$selectedP[providerType]) {
27629
+ return widgetData.selectedProviders[providerType];
27630
+ }
27631
+ if (widgetId && workspace !== null && workspace !== void 0 && (_workspace$workspaceD = workspace.workspaceData) !== null && _workspace$workspaceD !== void 0 && (_workspace$workspaceD = _workspace$workspaceD.selectedProviders) !== null && _workspace$workspaceD !== void 0 && (_workspace$workspaceD = _workspace$workspaceD[widgetId]) !== null && _workspace$workspaceD !== void 0 && _workspace$workspaceD[providerType]) {
27632
+ return workspace.workspaceData.selectedProviders[widgetId][providerType];
27633
+ }
27634
+ return null;
27635
+ }();
27636
+
27637
+ // Get the provider data (including credentials)
27638
+ // Read from AppContext.providers (not DashboardContext)
27639
+ var provider = selectedProviderName ? app === null || app === void 0 || (_app$providers = app.providers) === null || _app$providers === void 0 ? void 0 : _app$providers[selectedProviderName] : null;
27640
+
27641
+ /**
27642
+ * Apply connection result to this hook instance's local state.
27643
+ */
27644
+ var applyConnected = React.useCallback(function () {
27645
+ if (!mountedRef.current) return;
27646
+ setIsConnected(true);
27647
+ setIsConnecting(false);
27648
+ setStatus(STATUS.CONNECTED);
27649
+ connectedRef.current = true;
27650
+ }, []);
27651
+
27652
+ /**
27653
+ * Connect to the WebSocket server.
27654
+ * Uses module-level deduplication so only one IPC call fires per provider,
27655
+ * even when multiple hook instances call connect() simultaneously.
27656
+ */
27657
+ var connect = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
27658
+ var _provider$wsConfig;
27659
+ var cached, statusResult, result, state, connectPromise, _result2, _t3, _t4;
27660
+ return _regeneratorRuntime.wrap(function (_context2) {
27661
+ while (1) switch (_context2.prev = _context2.next) {
27662
+ case 0:
27663
+ if (!connectedRef.current) {
27664
+ _context2.next = 1;
27665
+ break;
27666
+ }
27667
+ return _context2.abrupt("return");
27668
+ case 1:
27669
+ if (!(!(dashApi !== null && dashApi !== void 0 && dashApi.webSocket) || !provider)) {
27670
+ _context2.next = 2;
27671
+ break;
27672
+ }
27673
+ setError(!provider ? "No ".concat(providerType, " WebSocket provider selected for this widget") : "Dashboard API not available");
27674
+ return _context2.abrupt("return");
27675
+ case 2:
27676
+ if (!(provider.providerClass !== "websocket")) {
27677
+ _context2.next = 3;
27678
+ break;
27679
+ }
27680
+ setError("Provider \"".concat(selectedProviderName, "\" is not a WebSocket provider"));
27681
+ return _context2.abrupt("return");
27682
+ case 3:
27683
+ if ((_provider$wsConfig = provider.wsConfig) !== null && _provider$wsConfig !== void 0 && _provider$wsConfig.url) {
27684
+ _context2.next = 4;
27685
+ break;
27686
+ }
27687
+ setError("Provider \"".concat(selectedProviderName, "\" has no WebSocket URL configured"));
27688
+ return _context2.abrupt("return");
27689
+ case 4:
27690
+ // 1. Already connected at module level? Verify with main process.
27691
+ cached = connectionStates.get(selectedProviderName);
27692
+ if (!(cached && cached.status === STATUS.CONNECTED)) {
27693
+ _context2.next = 9;
27694
+ break;
27695
+ }
27696
+ _context2.prev = 5;
27697
+ _context2.next = 6;
27698
+ return dashApi.webSocket.getStatus(selectedProviderName);
27699
+ case 6:
27700
+ statusResult = _context2.sent;
27701
+ if (!((statusResult === null || statusResult === void 0 ? void 0 : statusResult.status) === "connected")) {
27702
+ _context2.next = 7;
27703
+ break;
27704
+ }
27705
+ cached.consumerCount++;
27706
+ applyConnected();
27707
+ return _context2.abrupt("return");
27708
+ case 7:
27709
+ // Server was stopped externally — clear stale cache and reconnect
27710
+ connectionStates["delete"](selectedProviderName);
27711
+ _context2.next = 9;
27712
+ break;
27713
+ case 8:
27714
+ _context2.prev = 8;
27715
+ _context2["catch"](5);
27716
+ connectionStates["delete"](selectedProviderName);
27717
+ case 9:
27718
+ setIsConnecting(true);
27719
+ setError(null);
27720
+ setStatus(STATUS.CONNECTING);
27721
+
27722
+ // 2. Another hook instance already connecting? Piggyback on its promise
27723
+ if (!pendingConnects.has(selectedProviderName)) {
27724
+ _context2.next = 17;
27725
+ break;
27726
+ }
27727
+ _context2.prev = 10;
27728
+ _context2.next = 11;
27729
+ return pendingConnects.get(selectedProviderName);
27730
+ case 11:
27731
+ result = _context2.sent;
27732
+ if (mountedRef.current) {
27733
+ _context2.next = 12;
27734
+ break;
27735
+ }
27736
+ return _context2.abrupt("return");
27737
+ case 12:
27738
+ if (!result.error) {
27739
+ _context2.next = 13;
27740
+ break;
27741
+ }
27742
+ setError(result.message);
27743
+ setIsConnecting(false);
27744
+ setStatus(STATUS.ERROR);
27745
+ return _context2.abrupt("return");
27746
+ case 13:
27747
+ state = connectionStates.get(selectedProviderName);
27748
+ if (state) state.consumerCount++;
27749
+ applyConnected();
27750
+ _context2.next = 16;
27751
+ break;
27752
+ case 14:
27753
+ _context2.prev = 14;
27754
+ _t3 = _context2["catch"](10);
27755
+ if (mountedRef.current) {
27756
+ _context2.next = 15;
27757
+ break;
27758
+ }
27759
+ return _context2.abrupt("return");
27760
+ case 15:
27761
+ setError((_t3 === null || _t3 === void 0 ? void 0 : _t3.message) || "Failed to connect to WebSocket server");
27762
+ setIsConnecting(false);
27763
+ setStatus(STATUS.ERROR);
27764
+ case 16:
27765
+ return _context2.abrupt("return");
27766
+ case 17:
27767
+ // 3. First caller — fire the IPC call and share the promise
27768
+ connectPromise = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
27769
+ var _result, _t;
27770
+ return _regeneratorRuntime.wrap(function (_context) {
27771
+ while (1) switch (_context.prev = _context.next) {
27772
+ case 0:
27773
+ _context.prev = 0;
27774
+ _context.next = 1;
27775
+ return dashApi.webSocket.connect(selectedProviderName, {
27776
+ url: provider.wsConfig.url,
27777
+ headers: provider.wsConfig.headers || null,
27778
+ subprotocols: provider.wsConfig.subprotocols || null,
27779
+ credentials: provider.credentials || null
27780
+ });
27781
+ case 1:
27782
+ _result = _context.sent;
27783
+ pendingConnects["delete"](selectedProviderName);
27784
+ if (!_result.error) {
27785
+ _context.next = 2;
27786
+ break;
27787
+ }
27788
+ connectionStates.set(selectedProviderName, {
27789
+ status: STATUS.ERROR,
27790
+ consumerCount: 0
27791
+ });
27792
+ return _context.abrupt("return", _result);
27793
+ case 2:
27794
+ connectionStates.set(selectedProviderName, {
27795
+ status: STATUS.CONNECTED,
27796
+ consumerCount: 1
27797
+ });
27798
+ return _context.abrupt("return", _result);
27799
+ case 3:
27800
+ _context.prev = 3;
27801
+ _t = _context["catch"](0);
27802
+ pendingConnects["delete"](selectedProviderName);
27803
+ connectionStates.set(selectedProviderName, {
27804
+ status: STATUS.ERROR,
27805
+ consumerCount: 0
27806
+ });
27807
+ throw _t;
27808
+ case 4:
27809
+ case "end":
27810
+ return _context.stop();
27811
+ }
27812
+ }, _callee, null, [[0, 3]]);
27813
+ }))();
27814
+ pendingConnects.set(selectedProviderName, connectPromise);
27815
+ _context2.prev = 18;
27816
+ _context2.next = 19;
27817
+ return connectPromise;
27818
+ case 19:
27819
+ _result2 = _context2.sent;
27820
+ if (mountedRef.current) {
27821
+ _context2.next = 20;
27822
+ break;
27823
+ }
27824
+ return _context2.abrupt("return");
27825
+ case 20:
27826
+ if (!_result2.error) {
27827
+ _context2.next = 21;
27828
+ break;
27829
+ }
27830
+ setError(_result2.message);
27831
+ setIsConnecting(false);
27832
+ setStatus(STATUS.ERROR);
27833
+ return _context2.abrupt("return");
27834
+ case 21:
27835
+ applyConnected();
27836
+ _context2.next = 24;
27837
+ break;
27838
+ case 22:
27839
+ _context2.prev = 22;
27840
+ _t4 = _context2["catch"](18);
27841
+ if (mountedRef.current) {
27842
+ _context2.next = 23;
27843
+ break;
27844
+ }
27845
+ return _context2.abrupt("return");
27846
+ case 23:
27847
+ setError((_t4 === null || _t4 === void 0 ? void 0 : _t4.message) || "Failed to connect to WebSocket server");
27848
+ setIsConnecting(false);
27849
+ setStatus(STATUS.ERROR);
27850
+ case 24:
27851
+ case "end":
27852
+ return _context2.stop();
27853
+ }
27854
+ }, _callee2, null, [[5, 8], [10, 14], [18, 22]]);
27855
+ })), [dashApi, provider, providerType, selectedProviderName, applyConnected]);
27856
+
27857
+ /**
27858
+ * Disconnect from the WebSocket server.
27859
+ * Only sends the IPC disconnect call when this is the last consumer.
27860
+ */
27861
+ var disconnect = React.useCallback(/*#__PURE__*/_asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
27862
+ var state;
27863
+ return _regeneratorRuntime.wrap(function (_context3) {
27864
+ while (1) switch (_context3.prev = _context3.next) {
27865
+ case 0:
27866
+ if (!(!(dashApi !== null && dashApi !== void 0 && dashApi.webSocket) || !selectedProviderName)) {
27867
+ _context3.next = 1;
27868
+ break;
27869
+ }
27870
+ return _context3.abrupt("return");
27871
+ case 1:
27872
+ state = connectionStates.get(selectedProviderName);
27873
+ if (!state) {
27874
+ _context3.next = 3;
27875
+ break;
27876
+ }
27877
+ state.consumerCount = Math.max(0, state.consumerCount - 1);
27878
+ if (!(state.consumerCount > 0)) {
27879
+ _context3.next = 2;
27880
+ break;
27881
+ }
27882
+ // Other widgets still using this connection — just update local state
27883
+ setIsConnected(false);
27884
+ setLastMessage(null);
27885
+ setMessages([]);
27886
+ messagesRef.current = [];
27887
+ setStatus(STATUS.DISCONNECTED);
27888
+ connectedRef.current = false;
27889
+ return _context3.abrupt("return");
27890
+ case 2:
27891
+ // Last consumer — actually disconnect
27892
+ connectionStates["delete"](selectedProviderName);
27893
+ case 3:
27894
+ // Clear state synchronously before the IPC call
27895
+ setIsConnected(false);
27896
+ setLastMessage(null);
27897
+ setMessages([]);
27898
+ messagesRef.current = [];
27899
+ setStatus(STATUS.DISCONNECTED);
27900
+ connectedRef.current = false;
27901
+ pendingConnects["delete"](selectedProviderName);
27902
+ _context3.prev = 4;
27903
+ _context3.next = 5;
27904
+ return dashApi.webSocket.disconnect(selectedProviderName);
27905
+ case 5:
27906
+ _context3.next = 7;
27907
+ break;
27908
+ case 6:
27909
+ _context3.prev = 6;
27910
+ _context3["catch"](4);
27911
+ case 7:
27912
+ case "end":
27913
+ return _context3.stop();
27914
+ }
27915
+ }, _callee3, null, [[4, 6]]);
27916
+ })), [dashApi, selectedProviderName]);
27917
+
27918
+ /**
27919
+ * Send data through the WebSocket connection
27920
+ */
27921
+ var send = React.useCallback(/*#__PURE__*/function () {
27922
+ var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4(data) {
27923
+ var result;
27924
+ return _regeneratorRuntime.wrap(function (_context4) {
27925
+ while (1) switch (_context4.prev = _context4.next) {
27926
+ case 0:
27927
+ if (!(!(dashApi !== null && dashApi !== void 0 && dashApi.webSocket) || !selectedProviderName)) {
27928
+ _context4.next = 1;
27929
+ break;
27930
+ }
27931
+ throw new Error("WebSocket not connected");
27932
+ case 1:
27933
+ _context4.next = 2;
27934
+ return dashApi.webSocket.send(selectedProviderName, data);
27935
+ case 2:
27936
+ result = _context4.sent;
27937
+ if (!result.error) {
27938
+ _context4.next = 3;
27939
+ break;
27940
+ }
27941
+ throw new Error(result.message);
27942
+ case 3:
27943
+ return _context4.abrupt("return", result);
27944
+ case 4:
27945
+ case "end":
27946
+ return _context4.stop();
27947
+ }
27948
+ }, _callee4);
27949
+ }));
27950
+ return function (_x) {
27951
+ return _ref4.apply(this, arguments);
27952
+ };
27953
+ }(), [dashApi, selectedProviderName]);
27954
+
27955
+ // Keep a ref to connect so the auto-connect effect doesn't depend on it
27956
+ var connectRef = React.useRef(connect);
27957
+ connectRef.current = connect;
27958
+
27959
+ // Listen for incoming messages from main process
27960
+ React.useEffect(function () {
27961
+ if (!(dashApi !== null && dashApi !== void 0 && dashApi.webSocket) || !selectedProviderName) return;
27962
+ var handleMessage = function handleMessage(_event, payload) {
27963
+ if (payload.provider !== selectedProviderName) return;
27964
+ if (!mountedRef.current) return;
27965
+ var msg = payload.data;
27966
+
27967
+ // Update circular buffer
27968
+ var next = [].concat(_toConsumableArray(messagesRef.current), [msg]);
27969
+ if (next.length > maxMessagesRef.current) {
27970
+ next.splice(0, next.length - maxMessagesRef.current);
27971
+ }
27972
+ messagesRef.current = next;
27973
+ setMessages(next);
27974
+ setLastMessage(msg);
27975
+ };
27976
+ dashApi.webSocket.onMessage(handleMessage);
27977
+ return function () {
27978
+ return dashApi.webSocket.offMessage(handleMessage);
27979
+ };
27980
+ }, [dashApi, selectedProviderName]);
27981
+
27982
+ // Listen for status changes from main process
27983
+ React.useEffect(function () {
27984
+ if (!(dashApi !== null && dashApi !== void 0 && dashApi.webSocket) || !selectedProviderName) return;
27985
+ var handleStatusChange = function handleStatusChange(_event, payload) {
27986
+ if (payload.provider !== selectedProviderName) return;
27987
+ if (!mountedRef.current) return;
27988
+ var newStatus = payload.status;
27989
+ setStatus(newStatus);
27990
+ if (newStatus === STATUS.CONNECTED) {
27991
+ setIsConnected(true);
27992
+ setIsConnecting(false);
27993
+ setError(null);
27994
+ connectedRef.current = true;
27995
+ } else if (newStatus === STATUS.DISCONNECTED) {
27996
+ setIsConnected(false);
27997
+ setIsConnecting(false);
27998
+ connectedRef.current = false;
27999
+ } else if (newStatus === STATUS.ERROR) {
28000
+ setIsConnected(false);
28001
+ setIsConnecting(false);
28002
+ setError(payload.error || "WebSocket error");
28003
+ connectedRef.current = false;
28004
+ } else if (newStatus === STATUS.CONNECTING) {
28005
+ setIsConnecting(true);
28006
+ }
28007
+ };
28008
+ dashApi.webSocket.onStatusChange(handleStatusChange);
28009
+ return function () {
28010
+ return dashApi.webSocket.offStatusChange(handleStatusChange);
28011
+ };
28012
+ }, [dashApi, selectedProviderName]);
28013
+
28014
+ // Auto-connect on mount or when provider selection changes
28015
+ React.useEffect(function () {
28016
+ if (autoConnect && selectedProviderName && !connectedRef.current) {
28017
+ connectRef.current();
28018
+ }
28019
+ // eslint-disable-next-line react-hooks/exhaustive-deps
28020
+ }, [autoConnect, selectedProviderName]);
28021
+
28022
+ // Track mounted state and cleanup on unmount
28023
+ React.useEffect(function () {
28024
+ mountedRef.current = true;
28025
+ return function () {
28026
+ mountedRef.current = false;
28027
+
28028
+ // Decrement consumer count; only disconnect if last consumer
28029
+ if (connectedRef.current && dashApi !== null && dashApi !== void 0 && dashApi.webSocket && selectedProviderName) {
28030
+ var state = connectionStates.get(selectedProviderName);
28031
+ if (state) {
28032
+ state.consumerCount = Math.max(0, state.consumerCount - 1);
28033
+ if (state.consumerCount > 0) {
28034
+ // Other widgets still using this connection — don't disconnect
28035
+ return;
28036
+ }
28037
+
28038
+ // Last consumer — disconnect
28039
+ connectionStates["delete"](selectedProviderName);
28040
+ }
28041
+ dashApi.webSocket.disconnect(selectedProviderName)["catch"](function () {});
28042
+ }
28043
+ };
28044
+ }, [dashApi, selectedProviderName]);
28045
+ return {
28046
+ isConnected: isConnected,
28047
+ isConnecting: isConnecting,
28048
+ error: error,
28049
+ lastMessage: lastMessage,
28050
+ messages: messages,
28051
+ send: send,
28052
+ connect: connect,
28053
+ disconnect: disconnect,
28054
+ status: status,
28055
+ provider: provider,
28056
+ serverName: selectedProviderName
28057
+ };
28058
+ };
28059
+
27538
28060
  function ownKeys$h(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
27539
28061
  function _objectSpread$h(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$h(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$h(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
27540
28062
  var PreviewComponentsPane = function PreviewComponentsPane(_ref) {
@@ -46555,6 +47077,7 @@ exports.useNotifications = useNotifications;
46555
47077
  exports.useProvider = useProvider;
46556
47078
  exports.useProviderClient = useProviderClient;
46557
47079
  exports.useScheduler = useScheduler;
47080
+ exports.useWebSocketProvider = useWebSocketProvider;
46558
47081
  exports.useWidgetEvents = useWidgetEvents;
46559
47082
  exports.useWidgetProviders = useWidgetProviders;
46560
47083
  exports.useWidgetSchedulerStatus = useWidgetSchedulerStatus;