@xh/hoist 67.0.0-SNAPSHOT.1725151485260 → 67.0.0-SNAPSHOT.1725396437331

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.
@@ -1,4 +1,8 @@
1
1
  import { HoistService } from '@xh/hoist/core';
2
+ /**
3
+ * Load and report on the client and server environment, including software versions, timezones, and
4
+ * and other technical information.
5
+ */
2
6
  export declare class EnvironmentService extends HoistService {
3
7
  static instance: EnvironmentService;
4
8
  /**
@@ -16,7 +20,9 @@ export declare class EnvironmentService extends HoistService {
16
20
  * Unlike most other EnvironmentService state, this is refreshed on a timer and observable.
17
21
  */
18
22
  serverInstance: string;
19
- private _data;
23
+ private data;
24
+ private pollConfig;
25
+ private pollTimer;
20
26
  initAsync(): Promise<void>;
21
27
  get(key: string): any;
22
28
  get appEnvironment(): AppEnvironment;
@@ -25,9 +31,10 @@ export declare class EnvironmentService extends HoistService {
25
31
  isMinHoistCoreVersion(version: string): boolean;
26
32
  isMaxHoistCoreVersion(version: string): boolean;
27
33
  constructor();
28
- private startVersionChecking;
29
- private checkServerVersionAsync;
34
+ private startPolling;
35
+ private pollServerAsync;
30
36
  private setServerInfo;
37
+ private get pollIntervalMs();
31
38
  }
32
39
  /**
33
40
  * Available application deployment environments, as defined by Hoist Core `AppEnvironment.groovy`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "67.0.0-SNAPSHOT.1725151485260",
3
+ "version": "67.0.0-SNAPSHOT.1725396437331",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",
@@ -7,15 +7,19 @@
7
7
  import bpPkg from '@blueprintjs/core/package.json';
8
8
  import {HoistService, XH} from '@xh/hoist/core';
9
9
  import {agGridVersion} from '@xh/hoist/kit/ag-grid';
10
- import {observable, action, makeObservable} from '@xh/hoist/mobx';
10
+ import {action, makeObservable, observable} from '@xh/hoist/mobx';
11
11
  import hoistPkg from '@xh/hoist/package.json';
12
12
  import {Timer} from '@xh/hoist/utils/async';
13
13
  import {MINUTES, SECONDS} from '@xh/hoist/utils/datetime';
14
14
  import {checkMaxVersion, checkMinVersion, deepFreeze} from '@xh/hoist/utils/js';
15
- import {defaults} from 'lodash';
15
+ import {defaults, isFinite} from 'lodash';
16
16
  import mobxPkg from 'mobx/package.json';
17
17
  import {version as reactVersion} from 'react';
18
18
 
19
+ /**
20
+ * Load and report on the client and server environment, including software versions, timezones, and
21
+ * and other technical information.
22
+ */
19
23
  export class EnvironmentService extends HoistService {
20
24
  static instance: EnvironmentService;
21
25
 
@@ -40,47 +44,49 @@ export class EnvironmentService extends HoistService {
40
44
  @observable
41
45
  serverInstance: string;
42
46
 
43
- private _data = {};
47
+ private data = {};
48
+ private pollConfig: PollConfig;
49
+ private pollTimer: Timer;
44
50
 
45
51
  override async initAsync() {
46
- const serverEnv = await XH.fetchJson({url: 'xh/environment'}),
52
+ const {pollConfig, instanceName, ...serverEnv} = await XH.fetchJson({
53
+ url: 'xh/environment'
54
+ }),
47
55
  clientTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? 'Unknown',
48
56
  clientTimeZoneOffset = new Date().getTimezoneOffset() * -1 * MINUTES;
49
57
 
50
58
  // Favor client-side data injected via Webpack build or otherwise determined locally,
51
59
  // then apply all other env data sourced from the server.
52
- this._data = defaults(
53
- {
54
- appCode: XH.appCode,
55
- appName: XH.appName,
56
- clientVersion: XH.appVersion,
57
- clientBuild: XH.appBuild,
58
- reactVersion,
59
- hoistReactVersion: hoistPkg.version,
60
- agGridVersion,
61
- mobxVersion: mobxPkg.version,
62
- blueprintCoreVersion: bpPkg.version,
63
- clientTimeZone,
64
- clientTimeZoneOffset
65
- },
66
- serverEnv
60
+ this.data = deepFreeze(
61
+ defaults(
62
+ {
63
+ appCode: XH.appCode,
64
+ appName: XH.appName,
65
+ clientVersion: XH.appVersion,
66
+ clientBuild: XH.appBuild,
67
+ reactVersion,
68
+ hoistReactVersion: hoistPkg.version,
69
+ agGridVersion,
70
+ mobxVersion: mobxPkg.version,
71
+ blueprintCoreVersion: bpPkg.version,
72
+ clientTimeZone,
73
+ clientTimeZoneOffset
74
+ },
75
+ serverEnv
76
+ )
67
77
  );
68
78
 
69
- // This bit is considered transient. Maintain in 'serverInstance' mutable property only
70
- delete this._data['instanceName'];
71
-
72
- deepFreeze(this._data);
73
-
74
- this.setServerInfo(serverEnv.instanceName, serverEnv.appVersion, serverEnv.appBuild);
79
+ this.setServerInfo(instanceName, serverEnv.appVersion, serverEnv.appBuild);
75
80
 
81
+ this.pollConfig = pollConfig;
76
82
  this.addReaction({
77
83
  when: () => XH.appIsRunning,
78
- run: this.startVersionChecking
84
+ run: this.startPolling
79
85
  });
80
86
  }
81
87
 
82
88
  get(key: string): any {
83
- return this._data[key];
89
+ return this.data[key];
84
90
  }
85
91
 
86
92
  get appEnvironment(): AppEnvironment {
@@ -111,54 +117,64 @@ export class EnvironmentService extends HoistService {
111
117
  makeObservable(this);
112
118
  }
113
119
 
114
- private startVersionChecking() {
115
- const interval = XH.getConf('xhAppVersionCheck', {})?.interval ?? -1;
116
- Timer.create({
117
- runFn: this.checkServerVersionAsync,
118
- interval: interval * SECONDS
120
+ private startPolling() {
121
+ this.pollTimer = Timer.create({
122
+ runFn: () => this.pollServerAsync(),
123
+ interval: this.pollIntervalMs,
124
+ delay: true
119
125
  });
120
126
  }
121
127
 
122
- private checkServerVersionAsync = async () => {
123
- const data = await XH.fetchJson({url: 'xh/version'}),
124
- {instanceName, appVersion, appBuild, mode} = data;
125
-
126
- // Compare latest version/build info from server against the same info (also supplied by
127
- // server) when the app initialized. A change indicates an update to the app and will
128
- // force the user to refresh or prompt the user to refresh via the banner according to the
129
- // `mode` set in `xhAppVersionCheck`. Builds are checked here to trigger refresh prompts
130
- // across SNAPSHOT updates for projects with active dev/QA users.
131
- if (appVersion !== this.get('appVersion') || appBuild !== this.get('appBuild')) {
132
- if (mode === 'promptReload') {
133
- XH.appContainerModel.showUpdateBanner(appVersion, appBuild);
134
- } else if (mode === 'forceReload') {
135
- XH.suspendApp({
136
- reason: 'APP_UPDATE',
137
- message: `A new version of ${XH.clientAppName} is now available (${appVersion}) and requires an immediate update.`
138
- });
139
- }
140
- }
141
-
142
- // Note that the case of version mismatches across the client and server we do *not* show
143
- // the update bar to the user - that would indicate a deployment issue that a client reload
144
- // is unlikely to resolve, leaving the user in a frustrating state where they are endlessly
145
- // prompted to refresh.
146
- const clientVersion = this.get('clientVersion');
147
- if (appVersion !== clientVersion) {
148
- this.logWarn(
149
- `Version mismatch detected between client and server - ${clientVersion} vs ${appVersion}`
150
- );
128
+ private async pollServerAsync() {
129
+ let data;
130
+ try {
131
+ data = await XH.fetchJson({url: 'xh/environmentPoll'});
132
+ } catch (e) {
133
+ this.logError('Error polling server environment', e);
134
+ return;
151
135
  }
152
136
 
137
+ // Update config/interval, and server info
138
+ const {pollConfig, instanceName, appVersion, appBuild} = data;
139
+ this.pollConfig = pollConfig;
140
+ this.pollTimer.setInterval(this.pollIntervalMs);
153
141
  this.setServerInfo(instanceName, appVersion, appBuild);
154
- };
142
+
143
+ // Handle version change
144
+ if (appVersion != XH.getEnv('appVersion') || appBuild != XH.getEnv('appBuild')) {
145
+ // force the user to refresh or prompt the user to refresh via the banner according to config
146
+ // build checked to trigger refresh across SNAPSHOT updates in lower environments
147
+ const {onVersionChange} = pollConfig;
148
+ switch (onVersionChange) {
149
+ case 'promptReload':
150
+ XH.appContainerModel.showUpdateBanner(appVersion, appBuild);
151
+ return;
152
+ case 'forceReload':
153
+ XH.suspendApp({
154
+ reason: 'APP_UPDATE',
155
+ message: `A new version of ${XH.clientAppName} is now available (${appVersion}) and requires an immediate update.`
156
+ });
157
+ return;
158
+ default:
159
+ this.logWarn(
160
+ `New version ${appVersion} reported by server, onVersionChange is ${onVersionChange} - ignoring.`
161
+ );
162
+ }
163
+ }
164
+ }
155
165
 
156
166
  @action
157
- private setServerInfo(serverInstance, serverVersion, serverBuild) {
167
+ private setServerInfo(serverInstance: string, serverVersion: string, serverBuild: string) {
158
168
  this.serverInstance = serverInstance;
159
169
  this.serverVersion = serverVersion;
160
170
  this.serverBuild = serverBuild;
161
171
  }
172
+
173
+ private get pollIntervalMs(): number {
174
+ // Throttle to 5secs, disable if set to 0 or less.
175
+ const {interval} = this.pollConfig;
176
+ return isFinite(interval) && interval > 0 ? Math.max(interval, 5) * SECONDS : -1;
177
+ }
162
178
  }
163
179
 
164
180
  /**
@@ -175,3 +191,8 @@ export type AppEnvironment =
175
191
  | 'Test'
176
192
  | 'UAT'
177
193
  | 'BCP';
194
+
195
+ interface PollConfig {
196
+ interval: number;
197
+ onVersionChange: 'forceReload' | 'promptReload' | 'silent';
198
+ }