@memberjunction/ng-explorer-core 5.27.0 → 5.27.1

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.
@@ -2,43 +2,42 @@ import { OnDestroy } from '@angular/core';
2
2
  import { Observable } from 'rxjs';
3
3
  import * as i0 from "@angular/core";
4
4
  /**
5
- * Polls MJAPI's /healthcheck endpoint and exposes connectivity state.
5
+ * Monitors MJAPI connectivity.
6
6
  *
7
- * Start polling after workspace initialization with `Start(healthCheckUrl)`.
8
- * Subscribe to `IsConnected$` for reactive updates.
7
+ * Primary signal is the GraphQLDataProvider's WebSocket state (graphql-ws).
8
+ * When the socket emits 'disconnected' (retries already exhausted by graphql-ws),
9
+ * we fall back to polling /healthcheck until it returns 200, then force a socket
10
+ * reconnect so the next subscription picks up a fresh client.
9
11
  *
10
- * Rules:
11
- * - 2 consecutive failures -> disconnected
12
- * - 1 success -> connected
13
- * - Pauses when tab is hidden, checks immediately on focus
12
+ * 'unknown' (no active socket — either never opened or cleanly disposed) is
13
+ * treated as healthy: absence of signal is not a signal of failure.
14
14
  */
15
15
  export declare class ServerConnectivityService implements OnDestroy {
16
- private static readonly POLL_INTERVAL_CONNECTED_MS;
17
- private static readonly POLL_INTERVAL_DISCONNECTED_MS;
16
+ private static readonly POLL_INTERVAL_MS;
18
17
  private static readonly FETCH_TIMEOUT_MS;
19
- private static readonly FAILURES_BEFORE_DISCONNECT;
20
18
  private readonly isConnected;
21
19
  readonly IsConnected$: Observable<boolean>;
22
20
  private healthCheckUrl;
23
- private consecutiveFailures;
24
21
  private pollingTimerId;
22
+ private socketSubscription;
25
23
  private boundVisibilityHandler;
26
24
  /** Synchronous getter for the current connectivity state */
27
25
  get IsConnected(): boolean;
28
26
  /**
29
- * Begin polling the health check endpoint.
30
- * Call once after workspace initialization succeeds.
27
+ * Begin monitoring connectivity.
28
+ * Subscribes to the graphql-ws socket state and only polls /healthcheck
29
+ * while the socket reports 'disconnected'.
31
30
  */
32
31
  Start(healthCheckUrl: string): void;
33
- /** Stop polling and clean up resources */
32
+ /** Stop monitoring and clean up resources */
34
33
  Stop(): void;
35
34
  /** Force an immediate health check and return the result */
36
35
  CheckNow(): Promise<boolean>;
37
36
  ngOnDestroy(): void;
37
+ private subscribeToSocketState;
38
+ private onSocketStateChange;
38
39
  private ping;
39
- private applyPingResult;
40
- private onPingSuccess;
41
- private onPingFailure;
40
+ private runHealthCheck;
42
41
  private scheduleNextPoll;
43
42
  private clearPollTimer;
44
43
  private attachVisibilityListener;
@@ -1 +1 @@
1
- {"version":3,"file":"server-connectivity.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/server-connectivity.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,SAAS,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAmB,UAAU,EAAE,MAAM,MAAM,CAAC;;AAGnD;;;;;;;;;;GAUG;AACH,qBAGa,yBAA0B,YAAW,SAAS;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAU;IAC5D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,6BAA6B,CAAU;IAC/D,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IACjD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAK;IAEvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,SAAgB,YAAY,EAAE,UAAU,CAAC,OAAO,CAAC,CAAmC;IAEpF,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,sBAAsB,CAA6B;IAE3D,4DAA4D;IAC5D,IAAW,WAAW,IAAI,OAAO,CAEhC;IAED;;;OAGG;IACI,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAY1C,0CAA0C;IACnC,IAAI,IAAI,IAAI;IAMnB,4DAA4D;IAC/C,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IASzC,WAAW,IAAI,IAAI;YAML,IAAI;IAuBlB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,kBAAkB;yCAnJf,yBAAyB;6CAAzB,yBAAyB;CA6JrC"}
1
+ {"version":3,"file":"server-connectivity.service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/server-connectivity.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,SAAS,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAmB,UAAU,EAAgB,MAAM,MAAM,CAAC;;AAIjE;;;;;;;;;;GAUG;AACH,qBAGa,yBAA0B,YAAW,SAAS;IACzD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;IAClD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAEjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,SAAgB,YAAY,EAAE,UAAU,CAAC,OAAO,CAAC,CAAmC;IAEpF,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,sBAAsB,CAA6B;IAE3D,4DAA4D;IAC5D,IAAW,WAAW,IAAI,OAAO,CAEhC;IAED;;;;OAIG;IACI,KAAK,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;IAW1C,6CAA6C;IACtC,IAAI,IAAI,IAAI;IAUnB,4DAA4D;IAC/C,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAQzC,WAAW,IAAI,IAAI;IAMnB,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,mBAAmB;YAiBb,IAAI;YAuBJ,cAAc;IAgB5B,OAAO,CAAC,gBAAgB;IAcxB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,kBAAkB;yCAjKf,yBAAyB;6CAAzB,yBAAyB;CA2KrC"}
@@ -1,51 +1,54 @@
1
1
  import { Injectable } from '@angular/core';
2
2
  import { BehaviorSubject } from 'rxjs';
3
3
  import { LogError, LogStatus } from '@memberjunction/core';
4
+ import { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';
4
5
  import * as i0 from "@angular/core";
5
6
  /**
6
- * Polls MJAPI's /healthcheck endpoint and exposes connectivity state.
7
+ * Monitors MJAPI connectivity.
7
8
  *
8
- * Start polling after workspace initialization with `Start(healthCheckUrl)`.
9
- * Subscribe to `IsConnected$` for reactive updates.
9
+ * Primary signal is the GraphQLDataProvider's WebSocket state (graphql-ws).
10
+ * When the socket emits 'disconnected' (retries already exhausted by graphql-ws),
11
+ * we fall back to polling /healthcheck until it returns 200, then force a socket
12
+ * reconnect so the next subscription picks up a fresh client.
10
13
  *
11
- * Rules:
12
- * - 2 consecutive failures -> disconnected
13
- * - 1 success -> connected
14
- * - Pauses when tab is hidden, checks immediately on focus
14
+ * 'unknown' (no active socket — either never opened or cleanly disposed) is
15
+ * treated as healthy: absence of signal is not a signal of failure.
15
16
  */
16
17
  export class ServerConnectivityService {
17
- static POLL_INTERVAL_CONNECTED_MS = 30_000;
18
- static POLL_INTERVAL_DISCONNECTED_MS = 10_000;
18
+ static POLL_INTERVAL_MS = 30_000;
19
19
  static FETCH_TIMEOUT_MS = 5_000;
20
- static FAILURES_BEFORE_DISCONNECT = 2;
21
20
  isConnected = new BehaviorSubject(true);
22
21
  IsConnected$ = this.isConnected.asObservable();
23
22
  healthCheckUrl = null;
24
- consecutiveFailures = 0;
25
23
  pollingTimerId = null;
24
+ socketSubscription = null;
26
25
  boundVisibilityHandler = null;
27
26
  /** Synchronous getter for the current connectivity state */
28
27
  get IsConnected() {
29
28
  return this.isConnected.value;
30
29
  }
31
30
  /**
32
- * Begin polling the health check endpoint.
33
- * Call once after workspace initialization succeeds.
31
+ * Begin monitoring connectivity.
32
+ * Subscribes to the graphql-ws socket state and only polls /healthcheck
33
+ * while the socket reports 'disconnected'.
34
34
  */
35
35
  Start(healthCheckUrl) {
36
36
  if (this.healthCheckUrl) {
37
37
  this.Stop(); // idempotent restart
38
38
  }
39
39
  this.healthCheckUrl = healthCheckUrl;
40
- this.consecutiveFailures = 0;
41
40
  this.isConnected.next(true);
42
41
  this.attachVisibilityListener();
43
- this.scheduleNextPoll();
42
+ this.subscribeToSocketState();
44
43
  }
45
- /** Stop polling and clean up resources */
44
+ /** Stop monitoring and clean up resources */
46
45
  Stop() {
47
46
  this.clearPollTimer();
48
47
  this.detachVisibilityListener();
48
+ if (this.socketSubscription) {
49
+ this.socketSubscription.unsubscribe();
50
+ this.socketSubscription = null;
51
+ }
49
52
  this.healthCheckUrl = null;
50
53
  }
51
54
  /** Force an immediate health check and return the result */
@@ -53,14 +56,40 @@ export class ServerConnectivityService {
53
56
  if (!this.healthCheckUrl) {
54
57
  return this.isConnected.value;
55
58
  }
56
- const reachable = await this.ping();
57
- this.applyPingResult(reachable);
59
+ await this.runHealthCheck();
58
60
  return this.isConnected.value;
59
61
  }
60
62
  ngOnDestroy() {
61
63
  this.Stop();
62
64
  }
63
65
  // ── Private helpers ──────────────────────────────────────────────
66
+ subscribeToSocketState() {
67
+ const provider = GraphQLDataProvider.Instance;
68
+ if (!provider) {
69
+ LogError('ServerConnectivityService: GraphQLDataProvider instance not available');
70
+ return;
71
+ }
72
+ this.socketSubscription = provider.SocketConnectivity$.subscribe(state => {
73
+ this.onSocketStateChange(state);
74
+ });
75
+ }
76
+ onSocketStateChange(state) {
77
+ if (state === 'disconnected') {
78
+ if (this.isConnected.value) {
79
+ this.isConnected.next(false);
80
+ LogError('Server connectivity lost (WebSocket closed)');
81
+ }
82
+ this.scheduleNextPoll();
83
+ }
84
+ else {
85
+ // 'connected' or 'unknown' — treat as healthy
86
+ this.clearPollTimer();
87
+ if (!this.isConnected.value) {
88
+ this.isConnected.next(true);
89
+ LogStatus('Server connectivity restored (WebSocket reconnected)');
90
+ }
91
+ }
92
+ }
64
93
  async ping() {
65
94
  if (!this.healthCheckUrl)
66
95
  return false;
@@ -81,27 +110,20 @@ export class ServerConnectivityService {
81
110
  clearTimeout(timeoutId);
82
111
  }
83
112
  }
84
- applyPingResult(reachable) {
113
+ async runHealthCheck() {
114
+ const reachable = await this.ping();
85
115
  if (reachable) {
86
- this.onPingSuccess();
116
+ this.clearPollTimer();
117
+ if (!this.isConnected.value) {
118
+ this.isConnected.next(true);
119
+ LogStatus('Server connectivity restored (/healthcheck OK)');
120
+ }
121
+ // Dispose the stale socket so the next subscription creates a fresh client
122
+ GraphQLDataProvider.Instance?.ForceSocketReconnect();
87
123
  }
88
124
  else {
89
- this.onPingFailure();
90
- }
91
- }
92
- onPingSuccess() {
93
- this.consecutiveFailures = 0;
94
- if (!this.isConnected.value) {
95
- this.isConnected.next(true);
96
- LogStatus('Server connectivity restored');
97
- }
98
- }
99
- onPingFailure() {
100
- this.consecutiveFailures++;
101
- if (this.isConnected.value &&
102
- this.consecutiveFailures >= ServerConnectivityService.FAILURES_BEFORE_DISCONNECT) {
103
- this.isConnected.next(false);
104
- LogError(`Server connectivity lost after ${this.consecutiveFailures} consecutive failures`);
125
+ // Still unreachable — schedule another poll
126
+ this.scheduleNextPoll();
105
127
  }
106
128
  }
107
129
  scheduleNextPoll() {
@@ -110,13 +132,7 @@ export class ServerConnectivityService {
110
132
  if (typeof document !== 'undefined' && document.hidden) {
111
133
  return;
112
134
  }
113
- const interval = this.isConnected.value
114
- ? ServerConnectivityService.POLL_INTERVAL_CONNECTED_MS
115
- : ServerConnectivityService.POLL_INTERVAL_DISCONNECTED_MS;
116
- this.pollingTimerId = setTimeout(async () => {
117
- await this.CheckNow();
118
- this.scheduleNextPoll();
119
- }, interval);
135
+ this.pollingTimerId = setTimeout(() => this.runHealthCheck(), ServerConnectivityService.POLL_INTERVAL_MS);
120
136
  }
121
137
  clearPollTimer() {
122
138
  if (this.pollingTimerId != null) {
@@ -142,9 +158,9 @@ export class ServerConnectivityService {
142
158
  if (document.hidden) {
143
159
  this.clearPollTimer();
144
160
  }
145
- else {
146
- // Tab became visible — check immediately, then resume polling
147
- this.CheckNow().then(() => this.scheduleNextPoll());
161
+ else if (!this.isConnected.value) {
162
+ // Tab became visible while disconnected — check immediately
163
+ this.runHealthCheck();
148
164
  }
149
165
  }
150
166
  static ɵfac = function ServerConnectivityService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ServerConnectivityService)(); };
@@ -1 +1 @@
1
- {"version":3,"file":"server-connectivity.service.js","sourceRoot":"","sources":["../../../src/lib/services/server-connectivity.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, OnDestroy } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\nimport { LogError, LogStatus } from '@memberjunction/core';\n\n/**\n * Polls MJAPI's /healthcheck endpoint and exposes connectivity state.\n *\n * Start polling after workspace initialization with `Start(healthCheckUrl)`.\n * Subscribe to `IsConnected$` for reactive updates.\n *\n * Rules:\n * - 2 consecutive failures -> disconnected\n * - 1 success -> connected\n * - Pauses when tab is hidden, checks immediately on focus\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class ServerConnectivityService implements OnDestroy {\n private static readonly POLL_INTERVAL_CONNECTED_MS = 30_000;\n private static readonly POLL_INTERVAL_DISCONNECTED_MS = 10_000;\n private static readonly FETCH_TIMEOUT_MS = 5_000;\n private static readonly FAILURES_BEFORE_DISCONNECT = 2;\n\n private readonly isConnected = new BehaviorSubject<boolean>(true);\n public readonly IsConnected$: Observable<boolean> = this.isConnected.asObservable();\n\n private healthCheckUrl: string | null = null;\n private consecutiveFailures = 0;\n private pollingTimerId: ReturnType<typeof setTimeout> | null = null;\n private boundVisibilityHandler: (() => void) | null = null;\n\n /** Synchronous getter for the current connectivity state */\n public get IsConnected(): boolean {\n return this.isConnected.value;\n }\n\n /**\n * Begin polling the health check endpoint.\n * Call once after workspace initialization succeeds.\n */\n public Start(healthCheckUrl: string): void {\n if (this.healthCheckUrl) {\n this.Stop(); // idempotent restart\n }\n this.healthCheckUrl = healthCheckUrl;\n this.consecutiveFailures = 0;\n this.isConnected.next(true);\n\n this.attachVisibilityListener();\n this.scheduleNextPoll();\n }\n\n /** Stop polling and clean up resources */\n public Stop(): void {\n this.clearPollTimer();\n this.detachVisibilityListener();\n this.healthCheckUrl = null;\n }\n\n /** Force an immediate health check and return the result */\n public async CheckNow(): Promise<boolean> {\n if (!this.healthCheckUrl) {\n return this.isConnected.value;\n }\n const reachable = await this.ping();\n this.applyPingResult(reachable);\n return this.isConnected.value;\n }\n\n ngOnDestroy(): void {\n this.Stop();\n }\n\n // ── Private helpers ──────────────────────────────────────────────\n\n private async ping(): Promise<boolean> {\n if (!this.healthCheckUrl) return false;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n ServerConnectivityService.FETCH_TIMEOUT_MS\n );\n\n try {\n const response = await fetch(this.healthCheckUrl, {\n method: 'GET',\n signal: controller.signal,\n cache: 'no-store',\n });\n return response.ok;\n } catch {\n return false;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private applyPingResult(reachable: boolean): void {\n if (reachable) {\n this.onPingSuccess();\n } else {\n this.onPingFailure();\n }\n }\n\n private onPingSuccess(): void {\n this.consecutiveFailures = 0;\n if (!this.isConnected.value) {\n this.isConnected.next(true);\n LogStatus('Server connectivity restored');\n }\n }\n\n private onPingFailure(): void {\n this.consecutiveFailures++;\n if (\n this.isConnected.value &&\n this.consecutiveFailures >= ServerConnectivityService.FAILURES_BEFORE_DISCONNECT\n ) {\n this.isConnected.next(false);\n LogError(`Server connectivity lost after ${this.consecutiveFailures} consecutive failures`);\n }\n }\n\n private scheduleNextPoll(): void {\n this.clearPollTimer();\n\n // Don't poll while the tab is hidden — we'll check on focus\n if (typeof document !== 'undefined' && document.hidden) {\n return;\n }\n\n const interval = this.isConnected.value\n ? ServerConnectivityService.POLL_INTERVAL_CONNECTED_MS\n : ServerConnectivityService.POLL_INTERVAL_DISCONNECTED_MS;\n\n this.pollingTimerId = setTimeout(async () => {\n await this.CheckNow();\n this.scheduleNextPoll();\n }, interval);\n }\n\n private clearPollTimer(): void {\n if (this.pollingTimerId != null) {\n clearTimeout(this.pollingTimerId);\n this.pollingTimerId = null;\n }\n }\n\n private attachVisibilityListener(): void {\n if (typeof document === 'undefined') return;\n\n this.boundVisibilityHandler = () => this.onVisibilityChange();\n document.addEventListener('visibilitychange', this.boundVisibilityHandler);\n }\n\n private detachVisibilityListener(): void {\n if (this.boundVisibilityHandler && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.boundVisibilityHandler);\n this.boundVisibilityHandler = null;\n }\n }\n\n private onVisibilityChange(): void {\n if (!this.healthCheckUrl) return;\n\n if (document.hidden) {\n this.clearPollTimer();\n } else {\n // Tab became visible — check immediately, then resume polling\n this.CheckNow().then(() => this.scheduleNextPoll());\n }\n }\n}\n"]}
1
+ {"version":3,"file":"server-connectivity.service.js","sourceRoot":"","sources":["../../../src/lib/services/server-connectivity.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable, OnDestroy } from '@angular/core';\nimport { BehaviorSubject, Observable, Subscription } from 'rxjs';\nimport { LogError, LogStatus } from '@memberjunction/core';\nimport { GraphQLDataProvider, SocketConnectionState } from '@memberjunction/graphql-dataprovider';\n\n/**\n * Monitors MJAPI connectivity.\n *\n * Primary signal is the GraphQLDataProvider's WebSocket state (graphql-ws).\n * When the socket emits 'disconnected' (retries already exhausted by graphql-ws),\n * we fall back to polling /healthcheck until it returns 200, then force a socket\n * reconnect so the next subscription picks up a fresh client.\n *\n * 'unknown' (no active socket — either never opened or cleanly disposed) is\n * treated as healthy: absence of signal is not a signal of failure.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class ServerConnectivityService implements OnDestroy {\n private static readonly POLL_INTERVAL_MS = 30_000;\n private static readonly FETCH_TIMEOUT_MS = 5_000;\n\n private readonly isConnected = new BehaviorSubject<boolean>(true);\n public readonly IsConnected$: Observable<boolean> = this.isConnected.asObservable();\n\n private healthCheckUrl: string | null = null;\n private pollingTimerId: ReturnType<typeof setTimeout> | null = null;\n private socketSubscription: Subscription | null = null;\n private boundVisibilityHandler: (() => void) | null = null;\n\n /** Synchronous getter for the current connectivity state */\n public get IsConnected(): boolean {\n return this.isConnected.value;\n }\n\n /**\n * Begin monitoring connectivity.\n * Subscribes to the graphql-ws socket state and only polls /healthcheck\n * while the socket reports 'disconnected'.\n */\n public Start(healthCheckUrl: string): void {\n if (this.healthCheckUrl) {\n this.Stop(); // idempotent restart\n }\n this.healthCheckUrl = healthCheckUrl;\n this.isConnected.next(true);\n\n this.attachVisibilityListener();\n this.subscribeToSocketState();\n }\n\n /** Stop monitoring and clean up resources */\n public Stop(): void {\n this.clearPollTimer();\n this.detachVisibilityListener();\n if (this.socketSubscription) {\n this.socketSubscription.unsubscribe();\n this.socketSubscription = null;\n }\n this.healthCheckUrl = null;\n }\n\n /** Force an immediate health check and return the result */\n public async CheckNow(): Promise<boolean> {\n if (!this.healthCheckUrl) {\n return this.isConnected.value;\n }\n await this.runHealthCheck();\n return this.isConnected.value;\n }\n\n ngOnDestroy(): void {\n this.Stop();\n }\n\n // ── Private helpers ──────────────────────────────────────────────\n\n private subscribeToSocketState(): void {\n const provider = GraphQLDataProvider.Instance;\n if (!provider) {\n LogError('ServerConnectivityService: GraphQLDataProvider instance not available');\n return;\n }\n this.socketSubscription = provider.SocketConnectivity$.subscribe(state => {\n this.onSocketStateChange(state);\n });\n }\n\n private onSocketStateChange(state: SocketConnectionState): void {\n if (state === 'disconnected') {\n if (this.isConnected.value) {\n this.isConnected.next(false);\n LogError('Server connectivity lost (WebSocket closed)');\n }\n this.scheduleNextPoll();\n } else {\n // 'connected' or 'unknown' — treat as healthy\n this.clearPollTimer();\n if (!this.isConnected.value) {\n this.isConnected.next(true);\n LogStatus('Server connectivity restored (WebSocket reconnected)');\n }\n }\n }\n\n private async ping(): Promise<boolean> {\n if (!this.healthCheckUrl) return false;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(),\n ServerConnectivityService.FETCH_TIMEOUT_MS\n );\n\n try {\n const response = await fetch(this.healthCheckUrl, {\n method: 'GET',\n signal: controller.signal,\n cache: 'no-store',\n });\n return response.ok;\n } catch {\n return false;\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private async runHealthCheck(): Promise<void> {\n const reachable = await this.ping();\n if (reachable) {\n this.clearPollTimer();\n if (!this.isConnected.value) {\n this.isConnected.next(true);\n LogStatus('Server connectivity restored (/healthcheck OK)');\n }\n // Dispose the stale socket so the next subscription creates a fresh client\n GraphQLDataProvider.Instance?.ForceSocketReconnect();\n } else {\n // Still unreachable — schedule another poll\n this.scheduleNextPoll();\n }\n }\n\n private scheduleNextPoll(): void {\n this.clearPollTimer();\n\n // Don't poll while the tab is hidden — we'll check on focus\n if (typeof document !== 'undefined' && document.hidden) {\n return;\n }\n\n this.pollingTimerId = setTimeout(\n () => this.runHealthCheck(),\n ServerConnectivityService.POLL_INTERVAL_MS\n );\n }\n\n private clearPollTimer(): void {\n if (this.pollingTimerId != null) {\n clearTimeout(this.pollingTimerId);\n this.pollingTimerId = null;\n }\n }\n\n private attachVisibilityListener(): void {\n if (typeof document === 'undefined') return;\n\n this.boundVisibilityHandler = () => this.onVisibilityChange();\n document.addEventListener('visibilitychange', this.boundVisibilityHandler);\n }\n\n private detachVisibilityListener(): void {\n if (this.boundVisibilityHandler && typeof document !== 'undefined') {\n document.removeEventListener('visibilitychange', this.boundVisibilityHandler);\n this.boundVisibilityHandler = null;\n }\n }\n\n private onVisibilityChange(): void {\n if (!this.healthCheckUrl) return;\n\n if (document.hidden) {\n this.clearPollTimer();\n } else if (!this.isConnected.value) {\n // Tab became visible while disconnected — check immediately\n this.runHealthCheck();\n }\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memberjunction/ng-explorer-core",
3
- "version": "5.27.0",
3
+ "version": "5.27.1",
4
4
  "description": "MemberJunction Explorer: Core Angular Components",
5
5
  "main": "./dist/public-api.js",
6
6
  "typings": "./dist/public-api.d.ts",
@@ -32,45 +32,45 @@
32
32
  "@angular/cdk": "21.1.3",
33
33
  "@angular/platform-browser": "21.1.3",
34
34
  "@auth0/auth0-angular": "^2.6.0",
35
- "@memberjunction/ai-core-plus": "5.27.0",
36
- "@memberjunction/ai-engine-base": "5.27.0",
37
- "@memberjunction/communication-types": "5.27.0",
38
- "@memberjunction/core": "5.27.0",
39
- "@memberjunction/core-entities": "5.27.0",
40
- "@memberjunction/entity-communications-client": "5.27.0",
41
- "@memberjunction/export-engine": "5.27.0",
42
- "@memberjunction/global": "5.27.0",
43
- "@memberjunction/graphql-dataprovider": "5.27.0",
44
- "@memberjunction/ng-ai-test-harness": "5.27.0",
45
- "@memberjunction/ng-artifacts": "5.27.0",
46
- "@memberjunction/ng-auth-services": "5.27.0",
47
- "@memberjunction/ng-base-application": "5.27.0",
48
- "@memberjunction/ng-base-forms": "5.27.0",
49
- "@memberjunction/ng-container-directives": "5.27.0",
50
- "@memberjunction/ng-conversations": "5.27.0",
51
- "@memberjunction/ng-dashboard-viewer": "5.27.0",
52
- "@memberjunction/ng-dashboards": "5.27.0",
53
- "@memberjunction/ng-entity-form-dialog": "5.27.0",
54
- "@memberjunction/ng-entity-permissions": "5.27.0",
55
- "@memberjunction/ng-entity-viewer": "5.27.0",
56
- "@memberjunction/ng-explorer-settings": "5.27.0",
57
- "@memberjunction/ng-export-service": "5.27.0",
58
- "@memberjunction/ng-file-storage": "5.27.0",
59
- "@memberjunction/ng-generic-dialog": "5.27.0",
60
- "@memberjunction/ng-list-detail-grid": "5.27.0",
61
- "@memberjunction/ng-notifications": "5.27.0",
62
- "@memberjunction/ng-query-viewer": "5.27.0",
63
- "@memberjunction/ng-record-changes": "5.27.0",
64
- "@memberjunction/ng-record-selector": "5.27.0",
65
- "@memberjunction/ng-record-tags": "5.27.0",
66
- "@memberjunction/ng-resource-permissions": "5.27.0",
67
- "@memberjunction/ng-search": "5.27.0",
68
- "@memberjunction/ng-shared": "5.27.0",
69
- "@memberjunction/ng-shared-generic": "5.27.0",
70
- "@memberjunction/ng-ui-components": "5.27.0",
71
- "@memberjunction/ng-user-avatar": "5.27.0",
72
- "@memberjunction/ng-word-cloud": "5.27.0",
73
- "@memberjunction/templates-base-types": "5.27.0",
35
+ "@memberjunction/ai-core-plus": "5.27.1",
36
+ "@memberjunction/ai-engine-base": "5.27.1",
37
+ "@memberjunction/communication-types": "5.27.1",
38
+ "@memberjunction/core": "5.27.1",
39
+ "@memberjunction/core-entities": "5.27.1",
40
+ "@memberjunction/entity-communications-client": "5.27.1",
41
+ "@memberjunction/export-engine": "5.27.1",
42
+ "@memberjunction/global": "5.27.1",
43
+ "@memberjunction/graphql-dataprovider": "5.27.1",
44
+ "@memberjunction/ng-ai-test-harness": "5.27.1",
45
+ "@memberjunction/ng-artifacts": "5.27.1",
46
+ "@memberjunction/ng-auth-services": "5.27.1",
47
+ "@memberjunction/ng-base-application": "5.27.1",
48
+ "@memberjunction/ng-base-forms": "5.27.1",
49
+ "@memberjunction/ng-container-directives": "5.27.1",
50
+ "@memberjunction/ng-conversations": "5.27.1",
51
+ "@memberjunction/ng-dashboard-viewer": "5.27.1",
52
+ "@memberjunction/ng-dashboards": "5.27.1",
53
+ "@memberjunction/ng-entity-form-dialog": "5.27.1",
54
+ "@memberjunction/ng-entity-permissions": "5.27.1",
55
+ "@memberjunction/ng-entity-viewer": "5.27.1",
56
+ "@memberjunction/ng-explorer-settings": "5.27.1",
57
+ "@memberjunction/ng-export-service": "5.27.1",
58
+ "@memberjunction/ng-file-storage": "5.27.1",
59
+ "@memberjunction/ng-generic-dialog": "5.27.1",
60
+ "@memberjunction/ng-list-detail-grid": "5.27.1",
61
+ "@memberjunction/ng-notifications": "5.27.1",
62
+ "@memberjunction/ng-query-viewer": "5.27.1",
63
+ "@memberjunction/ng-record-changes": "5.27.1",
64
+ "@memberjunction/ng-record-selector": "5.27.1",
65
+ "@memberjunction/ng-record-tags": "5.27.1",
66
+ "@memberjunction/ng-resource-permissions": "5.27.1",
67
+ "@memberjunction/ng-search": "5.27.1",
68
+ "@memberjunction/ng-shared": "5.27.1",
69
+ "@memberjunction/ng-shared-generic": "5.27.1",
70
+ "@memberjunction/ng-ui-components": "5.27.1",
71
+ "@memberjunction/ng-user-avatar": "5.27.1",
72
+ "@memberjunction/ng-word-cloud": "5.27.1",
73
+ "@memberjunction/templates-base-types": "5.27.1",
74
74
  "golden-layout": "^2.6.0",
75
75
  "rxjs": "^7.8.2",
76
76
  "tslib": "^2.8.1"