@singularsystems/neo-core 1.4.2 → 1.5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Neo core changelog
2
2
 
3
+ ## Version 1.5.1 (28 May 2026)
4
+
5
+ - Moved `exact` from `IRoute` to `IMenuRoute`.
6
+
7
+ ## Version 1.5.0 (20 May 2026)
8
+
9
+ - Better offline support (see wiki "PWA Offline" guide for more details).
10
+ - Added `supportsOffline` to ConfigModel.
11
+ - Config service will now cache config data if `supportsOffline` is true.
12
+ - Network services
13
+ - Added `NetworkUtils` class for helping to identify whether the app can communicate with the server or not. Extends basic `navigator.onLine` property.
14
+ - Added `AppConnectionMonitor` base class for monitoring app connection, and allowing the app to reconnect when back online.
15
+ - OIDC Authentication service
16
+ - Will now redirect back to the current path if no path is specified.
17
+ - Will cache user data if `supportsOffline` is true.
18
+ - Current user and isAuthenticated properties will use cached data if available and if `supportsOffline` is true.
19
+
3
20
  ## Version 1.4.2 (11 May 2026)
4
21
 
5
22
  - Authentication service
@@ -11,6 +11,11 @@ export interface IConfigModelData {
11
11
  }
12
12
  export interface IConfigModel extends IConfigModelData {
13
13
  readonly isTestOrDevelopment: boolean;
14
+ /**
15
+ * Indicates that this app supports offline usage.
16
+ * If true, config and user data will be cached on fetch, and used when offline.
17
+ */
18
+ readonly supportsOffline: boolean;
14
19
  initialise(): void;
15
20
  }
16
21
  export default class ConfigModel implements IConfigModel {
@@ -18,6 +23,7 @@ export default class ConfigModel implements IConfigModel {
18
23
  isDevelopment: boolean;
19
24
  isTest: boolean;
20
25
  get isTestOrDevelopment(): boolean;
26
+ get supportsOffline(): boolean;
21
27
  get baseUrl(): string;
22
28
  initialise(): void;
23
29
  }
@@ -10,6 +10,13 @@ var ConfigModel = /** @class */ (function () {
10
10
  enumerable: false,
11
11
  configurable: true
12
12
  });
13
+ Object.defineProperty(ConfigModel.prototype, "supportsOffline", {
14
+ get: function () {
15
+ return false;
16
+ },
17
+ enumerable: false,
18
+ configurable: true
19
+ });
13
20
  Object.defineProperty(ConfigModel.prototype, "baseUrl", {
14
21
  get: function () {
15
22
  if (!this._baseUrl) {
@@ -6,7 +6,7 @@ export interface IConfigService {
6
6
  * Load and process the config file from the specified path.
7
7
  * Path will default to [baseUrl]/config.json
8
8
  * @param path Optional path.
9
- * @param store Offline store to allow offline retrieval.
9
+ * @param store Offline store to allow offline retrieval. Not necessary if you have set `supportsOffline` to true in the config model.
10
10
  */
11
11
  loadConfig(path?: string, store?: IDbSet<IConfigModelData>): Promise<IConfigModel>;
12
12
  /** Fetch the raw config data from the server. */
@@ -3,40 +3,62 @@ import { injectable, inject } from 'inversify';
3
3
  import Axios from "axios";
4
4
  import NeoTypes from '../NeoTypes';
5
5
  import AxiosUtils from "../../AxiosUtils";
6
+ var storageKey = "neo-app-config-data";
6
7
  var ConfigService = /** @class */ (function () {
7
8
  function ConfigService(config) {
8
9
  this.config = config;
9
10
  }
10
11
  ConfigService.prototype.loadConfig = function (path, store) {
11
12
  return __awaiter(this, void 0, void 0, function () {
12
- var configData, e_1;
13
+ var configData, e_1, storedConfig;
13
14
  return __generator(this, function (_a) {
14
15
  switch (_a.label) {
15
16
  case 0:
16
- _a.trys.push([0, 4, , 7]);
17
- return [4 /*yield*/, this.fetchConfig(path)];
17
+ if (!(!this.config.supportsOffline || navigator.onLine)) return [3 /*break*/, 7];
18
+ _a.label = 1;
18
19
  case 1:
20
+ _a.trys.push([1, 6, , 7]);
21
+ return [4 /*yield*/, this.fetchConfig(path)];
22
+ case 2:
19
23
  configData = (_a.sent()).data;
20
- if (!store) return [3 /*break*/, 3];
24
+ if (!store) return [3 /*break*/, 4];
21
25
  return [4 /*yield*/, store.putAsync(configData, 1)];
22
- case 2:
26
+ case 3:
23
27
  _a.sent();
24
- _a.label = 3;
25
- case 3: return [3 /*break*/, 7];
28
+ return [3 /*break*/, 5];
26
29
  case 4:
30
+ if (this.config.supportsOffline) {
31
+ localStorage.setItem(storageKey, JSON.stringify(configData));
32
+ }
33
+ _a.label = 5;
34
+ case 5: return [3 /*break*/, 7];
35
+ case 6:
27
36
  e_1 = _a.sent();
28
- if (!(store && AxiosUtils.isNetworkError(e_1) && !window.navigator.onLine)) return [3 /*break*/, 6];
37
+ if (!AxiosUtils.isNetworkError(e_1) || !(this.config.supportsOffline || store)) {
38
+ throw new Error("ConfigService - Error loading configuration: ".concat(e_1));
39
+ }
40
+ return [3 /*break*/, 7];
41
+ case 7:
42
+ if (!!configData) return [3 /*break*/, 11];
43
+ if (!store) return [3 /*break*/, 9];
29
44
  return [4 /*yield*/, store.getAsync(1)];
30
- case 5:
31
- // get config from index db
45
+ case 8:
32
46
  configData = _a.sent();
33
- _a.label = 6;
34
- case 6:
47
+ return [3 /*break*/, 10];
48
+ case 9:
49
+ if (this.config.supportsOffline) {
50
+ storedConfig = localStorage.getItem(storageKey);
51
+ if (storedConfig) {
52
+ configData = JSON.parse(storedConfig);
53
+ }
54
+ }
55
+ _a.label = 10;
56
+ case 10:
35
57
  if (!configData) {
36
- throw new Error("ConfigService - Error loading configuration: ".concat(e_1));
58
+ throw new Error("ConfigService - Unable to load config data from offline storage.");
37
59
  }
38
- return [3 /*break*/, 7];
39
- case 7: return [2 /*return*/, this.setConfig(configData)];
60
+ _a.label = 11;
61
+ case 11: return [2 /*return*/, this.setConfig(configData)];
40
62
  }
41
63
  });
42
64
  });
@@ -6,7 +6,7 @@ import { TaskRunner, NotifyUtils } from '../';
6
6
  import ModalStore from '../Components/Modal/ModalStore';
7
7
  import { MenuItemProvider } from '../Routing/MenuItemProvider';
8
8
  import { ErrorHandler } from '../Components/ErrorHandler';
9
- import { ConnectionManager } from '../ConnectionManager';
9
+ import { ConnectionManager } from '../Network/ConnectionManager';
10
10
  import LocalisationService from '../Localisation/LocalisationService';
11
11
  import { NotificationHelper } from '../Components/Notifications/NotificationHelper';
12
12
  import { RouteSecurityService } from "../Routing/RouteSecurityService";
@@ -10,7 +10,7 @@ import { IMenuItemProvider } from '../Routing/IMenuItemProvider';
10
10
  import { IModalStore } from '../Components/Modal/ModalStore';
11
11
  import { INotificationStore, INotificationHelper } from '../Components';
12
12
  import { IErrorHandler } from '../Components/ErrorHandler';
13
- import { IConnectionManager } from '../ConnectionManager';
13
+ import { IConnectionManager } from '../Network/IConnectionManager';
14
14
  import { IAutoRunnerFactory } from '../Model/IAutoRunner';
15
15
  import { ILocalisationService } from '../Localisation/ILocalisationService';
16
16
  import { IRouteSecurityService } from '../Routing/IRouteSecurityService';
@@ -19,6 +19,7 @@ import { INavigationHelper } from '../Routing/INavigationHelper';
19
19
  import { ILoginErrorHandler } from '../Security/ILoginErrorHandler';
20
20
  import { IDecimalService } from "../Numeric";
21
21
  import { IServerMessageSubscriber } from "../SignalR/IServerMessageSubscriber";
22
+ import { IAppConnectionMonitor } from "../Network/IAppConnectionMonitor";
22
23
  declare const NeoTypes: {
23
24
  Axios: ServiceIdentifier<import("axios").AxiosStatic>;
24
25
  Config: {
@@ -27,7 +28,6 @@ declare const NeoTypes: {
27
28
  };
28
29
  Core: {
29
30
  AutoRunnerFactory: ServiceIdentifier<IAutoRunnerFactory>;
30
- /** @deprecated Use Messaging.ConnectionManager */
31
31
  ConnectionManager: ServiceIdentifier<IConnectionManager>;
32
32
  DecimalService: ServiceIdentifier<IDecimalService<import("../Numeric").IDecimal>>;
33
33
  ModelCreator: ServiceIdentifier<IModelCreatorBase>;
@@ -39,6 +39,10 @@ declare const NeoTypes: {
39
39
  ConnectionManager: ServiceIdentifier<IConnectionManager>;
40
40
  ServerMessageSubscriber: ServiceIdentifier<IServerMessageSubscriber>;
41
41
  };
42
+ Network: {
43
+ AppConnectionMonitor: ServiceIdentifier<IAppConnectionMonitor>;
44
+ ConnectionManager: ServiceIdentifier<IConnectionManager>;
45
+ };
42
46
  Routing: {
43
47
  MenuItemProvider: ServiceIdentifier<IMenuItemProvider<import("../Routing").IMenuRoute>>;
44
48
  NavigationHelper: ServiceIdentifier<INavigationHelper>;
@@ -9,7 +9,6 @@ var NeoTypes = {
9
9
  },
10
10
  Core: {
11
11
  AutoRunnerFactory: new ServiceIdentifier("Neo.AutoRunnerFactory"),
12
- /** @deprecated Use Messaging.ConnectionManager */
13
12
  ConnectionManager: connectionManagerType,
14
13
  DecimalService: new ServiceIdentifier("Neo.DecimalService"),
15
14
  ModelCreator: new ServiceIdentifier("Neo.ModelCreator")
@@ -21,6 +20,10 @@ var NeoTypes = {
21
20
  ConnectionManager: connectionManagerType,
22
21
  ServerMessageSubscriber: new ServiceIdentifier("Neo.ServerMessageSubscriber"),
23
22
  },
23
+ Network: {
24
+ AppConnectionMonitor: new ServiceIdentifier("Neo.AppConnectionMonitor"),
25
+ ConnectionManager: connectionManagerType,
26
+ },
24
27
  Routing: {
25
28
  MenuItemProvider: new ServiceIdentifier("Neo.MenuItemProvider"),
26
29
  NavigationHelper: new ServiceIdentifier("Neo.NavigationHelper"),
@@ -4,4 +4,4 @@ export { Module } from './Module';
4
4
  export { default as NeoModule } from './NeoModule';
5
5
  export { ServiceIdentifier } from './ServiceIdentifier';
6
6
  export { ConfigService } from './Config/ConfigService';
7
- export { default as ConfigModel, IConfigModel } from './Config/ConfigModel';
7
+ export { default as ConfigModel, IConfigModelData, IConfigModel } from './Config/ConfigModel';
@@ -3,6 +3,7 @@ import { AsyncDataSource } from './AsyncDataSource';
3
3
  import NeoModel from '../Model/NeoModel';
4
4
  import List from '../Model/List';
5
5
  import AxiosUtils from "../AxiosUtils";
6
+ import NetworkUtils from "../Network/NetworkUtils";
6
7
  /**
7
8
  * Allows an api endpoint to be used directly by a consumer such as a drop down element.
8
9
  * If you need the data to be deserialised into a specific lookup model, use ApiClientLookupDataSource.
@@ -46,7 +47,7 @@ var ConvertingApiClientDataSource = /** @class */ (function (_super) {
46
47
  return __generator(this, function (_a) {
47
48
  switch (_a.label) {
48
49
  case 0:
49
- if (!navigator.onLine) return [3 /*break*/, 2];
50
+ if (!(NetworkUtils.isOnline || !this.supportOffline)) return [3 /*break*/, 2];
50
51
  return [4 /*yield*/, AxiosUtils.catchErrors(this.getMethod())];
51
52
  case 1:
52
53
  result = _a.sent();
@@ -0,0 +1,21 @@
1
+ import { IAuthenticationService } from "../Security";
2
+ import { ConnectionState } from "./IConnectionManager";
3
+ import { IAppConnectionMonitor } from "./IAppConnectionMonitor";
4
+ export default abstract class AppConnectionMonitor implements IAppConnectionMonitor {
5
+ protected authenticationService: IAuthenticationService;
6
+ constructor(authenticationService: IAuthenticationService);
7
+ protected reconnectDelayMs: number;
8
+ private state;
9
+ get isOnline(): boolean;
10
+ get requiresSignInRedirect(): boolean;
11
+ get connectionState(): ConnectionState;
12
+ /** Determines if a redirect can be performed to sign the user in. Return false to prevent the redirect. */
13
+ protected abstract beforeSigninRedirect(): Promise<boolean> | boolean;
14
+ /**
15
+ * Called once reconnected, after authenticationService.afterUserLoaded() has completed.
16
+ */
17
+ protected onReconnected(): Promise<void> | void;
18
+ private updateOnlineStatus;
19
+ private markOffline;
20
+ tryConnect(forceAllowRedirect?: boolean): Promise<void>;
21
+ }
@@ -0,0 +1,145 @@
1
+ import { __awaiter, __decorate, __extends, __generator, __metadata, __param } from "tslib";
2
+ import { Utils } from '../Utils';
3
+ import NeoTypes from '../AppServices/NeoTypes';
4
+ import { inject } from 'inversify';
5
+ import { ConnectionState } from "./IConnectionManager";
6
+ import Globals from "../Globals";
7
+ import ModelBase from "../Model/ModelBase";
8
+ var AppConnectionMonitor = /** @class */ (function () {
9
+ function AppConnectionMonitor(authenticationService) {
10
+ var _this = this;
11
+ this.authenticationService = authenticationService;
12
+ this.reconnectDelayMs = 5000;
13
+ this.updateOnlineStatus = this.updateOnlineStatus.bind(this);
14
+ this.state = new State();
15
+ window.addEventListener("online", this.updateOnlineStatus);
16
+ window.addEventListener("offline", this.updateOnlineStatus);
17
+ var axiosInstance = Globals.appService.get(NeoTypes.Axios);
18
+ axiosInstance.interceptors.response.use(function (response) { return response; }, function (error) {
19
+ // Network error occurred
20
+ if (error.response === undefined && _this.state.connectionState === ConnectionState.Connected) {
21
+ // Server is unreachable but browser thinks we're online
22
+ _this.markOffline();
23
+ }
24
+ return Promise.reject(error);
25
+ });
26
+ }
27
+ Object.defineProperty(AppConnectionMonitor.prototype, "isOnline", {
28
+ get: function () {
29
+ return this.state.connectionState === ConnectionState.Connected;
30
+ },
31
+ enumerable: false,
32
+ configurable: true
33
+ });
34
+ Object.defineProperty(AppConnectionMonitor.prototype, "requiresSignInRedirect", {
35
+ get: function () {
36
+ return this.state.requiresSignInRedirect;
37
+ },
38
+ enumerable: false,
39
+ configurable: true
40
+ });
41
+ Object.defineProperty(AppConnectionMonitor.prototype, "connectionState", {
42
+ get: function () {
43
+ return this.state.connectionState;
44
+ },
45
+ enumerable: false,
46
+ configurable: true
47
+ });
48
+ /**
49
+ * Called once reconnected, after authenticationService.afterUserLoaded() has completed.
50
+ */
51
+ AppConnectionMonitor.prototype.onReconnected = function () {
52
+ };
53
+ AppConnectionMonitor.prototype.updateOnlineStatus = function () {
54
+ return __awaiter(this, void 0, void 0, function () {
55
+ var _this = this;
56
+ return __generator(this, function (_a) {
57
+ if (navigator.onLine) {
58
+ Utils.debounce(this, function () { return _this.tryConnect(false); }, this.reconnectDelayMs);
59
+ }
60
+ else {
61
+ this.markOffline();
62
+ }
63
+ return [2 /*return*/];
64
+ });
65
+ });
66
+ };
67
+ AppConnectionMonitor.prototype.markOffline = function () {
68
+ this.state.connectionState = ConnectionState.Disconnected;
69
+ };
70
+ AppConnectionMonitor.prototype.tryConnect = function () {
71
+ return __awaiter(this, arguments, void 0, function (forceAllowRedirect) {
72
+ var user, _a, canRedirect, _b, e_1;
73
+ if (forceAllowRedirect === void 0) { forceAllowRedirect = false; }
74
+ return __generator(this, function (_c) {
75
+ switch (_c.label) {
76
+ case 0:
77
+ Utils.clearDebounce(this);
78
+ if (this.state.connectionState === ConnectionState.Connected) {
79
+ return [2 /*return*/];
80
+ }
81
+ this.state.connectionState = ConnectionState.ReConnecting;
82
+ _c.label = 1;
83
+ case 1:
84
+ _c.trys.push([1, 13, , 14]);
85
+ if (!this.state.requiresSignInRedirect) return [3 /*break*/, 2];
86
+ _a = null;
87
+ return [3 /*break*/, 4];
88
+ case 2: return [4 /*yield*/, this.authenticationService.signinSilent()];
89
+ case 3:
90
+ _a = _c.sent();
91
+ _c.label = 4;
92
+ case 4:
93
+ user = _a;
94
+ if (!user) return [3 /*break*/, 6];
95
+ this.state.connectionState = ConnectionState.Connected;
96
+ return [4 /*yield*/, this.onReconnected()];
97
+ case 5:
98
+ _c.sent();
99
+ return [3 /*break*/, 12];
100
+ case 6:
101
+ if (!forceAllowRedirect) return [3 /*break*/, 7];
102
+ _b = true;
103
+ return [3 /*break*/, 9];
104
+ case 7: return [4 /*yield*/, this.beforeSigninRedirect()];
105
+ case 8:
106
+ _b = _c.sent();
107
+ _c.label = 9;
108
+ case 9:
109
+ canRedirect = _b;
110
+ if (!canRedirect) return [3 /*break*/, 11];
111
+ return [4 /*yield*/, this.authenticationService.beginSignIn()];
112
+ case 10:
113
+ _c.sent();
114
+ return [2 /*return*/];
115
+ case 11:
116
+ this.state.requiresSignInRedirect = true;
117
+ _c.label = 12;
118
+ case 12: return [3 /*break*/, 14];
119
+ case 13:
120
+ e_1 = _c.sent();
121
+ this.state.connectionState = ConnectionState.Disconnected;
122
+ return [3 /*break*/, 14];
123
+ case 14: return [2 /*return*/];
124
+ }
125
+ });
126
+ });
127
+ };
128
+ AppConnectionMonitor = __decorate([
129
+ __param(0, inject(NeoTypes.Security.AuthenticationService.symbol)),
130
+ __metadata("design:paramtypes", [Object])
131
+ ], AppConnectionMonitor);
132
+ return AppConnectionMonitor;
133
+ }());
134
+ export default AppConnectionMonitor;
135
+ var State = /** @class */ (function (_super) {
136
+ __extends(State, _super);
137
+ function State() {
138
+ var _this = _super.call(this) || this;
139
+ _this.connectionState = navigator.onLine ? ConnectionState.Connected : ConnectionState.Disconnected;
140
+ _this.requiresSignInRedirect = false;
141
+ _this.makeObservable();
142
+ return _this;
143
+ }
144
+ return State;
145
+ }(ModelBase));
@@ -0,0 +1,7 @@
1
+ import { ConnectionInfo, ConnectionState, ConnectionType, IConnection, IConnectionManager } from "./IConnectionManager";
2
+ export declare class ConnectionManager implements IConnectionManager {
3
+ private _connections;
4
+ registerConnection(connection: IConnection, connectionName: string, connectionType: ConnectionType): void;
5
+ get connectionState(): ConnectionState;
6
+ get connections(): ConnectionInfo[];
7
+ }
@@ -0,0 +1,38 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { Computed } from '../Model/Decorators';
3
+ import { injectable } from 'inversify';
4
+ import { ConnectionInfo, ConnectionState } from "./IConnectionManager";
5
+ var ConnectionManager = /** @class */ (function () {
6
+ function ConnectionManager() {
7
+ this._connections = new Map();
8
+ }
9
+ ConnectionManager.prototype.registerConnection = function (connection, connectionName, connectionType) {
10
+ if (!this._connections.has(connectionName)) {
11
+ this._connections.set(connectionName, new ConnectionInfo(connection, connectionName, connectionType));
12
+ }
13
+ };
14
+ Object.defineProperty(ConnectionManager.prototype, "connectionState", {
15
+ get: function () {
16
+ return this.connections.reduce(function (p, c) { return Math.max(p, c.connection.connectionState); }, ConnectionState.NotStarted);
17
+ },
18
+ enumerable: false,
19
+ configurable: true
20
+ });
21
+ Object.defineProperty(ConnectionManager.prototype, "connections", {
22
+ get: function () {
23
+ return Array.from(this._connections.values());
24
+ },
25
+ enumerable: false,
26
+ configurable: true
27
+ });
28
+ __decorate([
29
+ Computed(),
30
+ __metadata("design:type", Number),
31
+ __metadata("design:paramtypes", [])
32
+ ], ConnectionManager.prototype, "connectionState", null);
33
+ ConnectionManager = __decorate([
34
+ injectable()
35
+ ], ConnectionManager);
36
+ return ConnectionManager;
37
+ }());
38
+ export { ConnectionManager };
@@ -0,0 +1,6 @@
1
+ import { ConnectionState } from "./IConnectionManager";
2
+ export interface IAppConnectionMonitor {
3
+ readonly isOnline: boolean;
4
+ readonly requiresSignInRedirect: boolean;
5
+ readonly connectionState: ConnectionState;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ export declare enum ConnectionState {
2
+ /** Has not tried to connect yet. */
3
+ NotStarted = 0,
4
+ /** Is connected. */
5
+ Connected = 1,
6
+ /** Is reconnecting after failing to connect, or being disconnected. */
7
+ ReConnecting = 2,
8
+ /** Is disconnected and not trying to reconnect. */
9
+ Disconnected = 3
10
+ }
11
+ export declare enum ConnectionType {
12
+ SignalR = 0
13
+ }
14
+ export interface IConnection {
15
+ readonly connectionState: ConnectionState;
16
+ }
17
+ export declare class ConnectionInfo {
18
+ readonly connection: IConnection;
19
+ readonly connectionName: string;
20
+ readonly connectionType: ConnectionType;
21
+ constructor(connection: IConnection, connectionName: string, connectionType: ConnectionType);
22
+ }
23
+ /** A connection manager is used to display the connection state of multiple connected services to the user. */
24
+ export interface IConnectionManager {
25
+ /**
26
+ * Returns the max state of all the connections.
27
+ */
28
+ readonly connectionState: ConnectionState;
29
+ /**
30
+ * Gets a list of connections that have been registered.
31
+ */
32
+ readonly connections: ConnectionInfo[];
33
+ /**
34
+ * Registers a connection to be monitored.
35
+ * @param connection The connection to monitor.
36
+ * @param connectionName The display name of the connection.
37
+ */
38
+ registerConnection(connection: IConnection, connectionName: string, connectionType: ConnectionType): void;
39
+ }
@@ -0,0 +1,24 @@
1
+ export var ConnectionState;
2
+ (function (ConnectionState) {
3
+ /** Has not tried to connect yet. */
4
+ ConnectionState[ConnectionState["NotStarted"] = 0] = "NotStarted";
5
+ /** Is connected. */
6
+ ConnectionState[ConnectionState["Connected"] = 1] = "Connected";
7
+ /** Is reconnecting after failing to connect, or being disconnected. */
8
+ ConnectionState[ConnectionState["ReConnecting"] = 2] = "ReConnecting";
9
+ /** Is disconnected and not trying to reconnect. */
10
+ ConnectionState[ConnectionState["Disconnected"] = 3] = "Disconnected";
11
+ })(ConnectionState || (ConnectionState = {}));
12
+ export var ConnectionType;
13
+ (function (ConnectionType) {
14
+ ConnectionType[ConnectionType["SignalR"] = 0] = "SignalR";
15
+ })(ConnectionType || (ConnectionType = {}));
16
+ var ConnectionInfo = /** @class */ (function () {
17
+ function ConnectionInfo(connection, connectionName, connectionType) {
18
+ this.connection = connection;
19
+ this.connectionName = connectionName;
20
+ this.connectionType = connectionType;
21
+ }
22
+ return ConnectionInfo;
23
+ }());
24
+ export { ConnectionInfo };
@@ -0,0 +1,11 @@
1
+ import { IConfigModel } from "../AppServices";
2
+ export default class NetworkUtils {
3
+ private static _monitorInstance?;
4
+ /**
5
+ * Indicates whether the app is online and ready to send requests to the server.
6
+ * Even if `navigator.onLine` reports true, the app may not have re-authenticated after a connection loss.
7
+ */
8
+ static get isOnline(): boolean;
9
+ static canFetch(config: IConfigModel): boolean;
10
+ static useCachedData(config: IConfigModel): boolean;
11
+ }
@@ -0,0 +1,29 @@
1
+ import Globals from "../Globals";
2
+ import NeoTypes from "../AppServices/NeoTypes";
3
+ var NetworkUtils = /** @class */ (function () {
4
+ function NetworkUtils() {
5
+ }
6
+ Object.defineProperty(NetworkUtils, "isOnline", {
7
+ /**
8
+ * Indicates whether the app is online and ready to send requests to the server.
9
+ * Even if `navigator.onLine` reports true, the app may not have re-authenticated after a connection loss.
10
+ */
11
+ get: function () {
12
+ var _a, _b;
13
+ if (this._monitorInstance === undefined) {
14
+ this._monitorInstance = Globals.appService.tryGet(NeoTypes.Network.AppConnectionMonitor);
15
+ }
16
+ return (_b = (_a = this._monitorInstance) === null || _a === void 0 ? void 0 : _a.isOnline) !== null && _b !== void 0 ? _b : navigator.onLine;
17
+ },
18
+ enumerable: false,
19
+ configurable: true
20
+ });
21
+ NetworkUtils.canFetch = function (config) {
22
+ return !config.supportsOffline || this.isOnline;
23
+ };
24
+ NetworkUtils.useCachedData = function (config) {
25
+ return config.supportsOffline && !this.isOnline;
26
+ };
27
+ return NetworkUtils;
28
+ }());
29
+ export default NetworkUtils;
@@ -0,0 +1,5 @@
1
+ import { ConnectionInfo, ConnectionState, ConnectionType, IConnection, IConnectionManager } from "./IConnectionManager";
2
+ import AppConnectionMonitor from "./AppConnectionMonitor";
3
+ import NetworkUtils from "./NetworkUtils";
4
+ import { ConnectionManager } from "./ConnectionManager";
5
+ export { ConnectionInfo, ConnectionState, ConnectionType, IConnection, IConnectionManager, ConnectionManager, AppConnectionMonitor, NetworkUtils as Utils };
@@ -0,0 +1,5 @@
1
+ import { ConnectionInfo, ConnectionState, ConnectionType } from "./IConnectionManager";
2
+ import AppConnectionMonitor from "./AppConnectionMonitor";
3
+ import NetworkUtils from "./NetworkUtils";
4
+ import { ConnectionManager } from "./ConnectionManager";
5
+ export { ConnectionInfo, ConnectionState, ConnectionType, ConnectionManager, AppConnectionMonitor, NetworkUtils as Utils };
@@ -15,4 +15,9 @@ export interface IMenuRoute extends IRoute {
15
15
  children?: IMenuRoute[];
16
16
  /** Icon to show on the menu. */
17
17
  icon?: any;
18
+ /**
19
+ * Should this menu item appear active only on an exact path match (instead of a startsWith match)?
20
+ * Set this to true if this is a parent menu item which has sub menu items.
21
+ */
22
+ exact?: boolean;
18
23
  }
@@ -10,8 +10,6 @@ export interface IRoute {
10
10
  role?: string;
11
11
  /** User must have 1 of these roles to view this route. */
12
12
  anyRoles?: string[];
13
- /** Match on exact path? Set this to true if this is a parent route which has sub routes. */
14
- exact?: boolean;
15
13
  /**
16
14
  * Path which will be used for links on a menu component.
17
15
  * If you specify parameters here, you must also specify `basePath`.
@@ -9,11 +9,12 @@ export default interface IAuthenticationService {
9
9
  /**
10
10
  * Checks if the user is signed in, and if not, redirects to login.
11
11
  * Returns true if the user is logged in.
12
+ * @param redirectLocal Redirect path. If this is not supplied, the current path will be used.
12
13
  */
13
14
  ensureSignedIn(redirectLocal?: string): boolean;
14
15
  /**
15
16
  * Starts the sign in process
16
- * @param redirectLocal The path to redirect to after calling endSignIn
17
+ * @param redirectLocal The path to redirect to after calling endSignIn. If this is not supplied, the current path will be used.
17
18
  */
18
19
  beginSignIn(redirectLocal?: string): Promise<any>;
19
20
  /**
@@ -25,7 +26,7 @@ export default interface IAuthenticationService {
25
26
  * Result.success will be false if an error occurs. Errors will be handled by the service bound to NeoTypes.Security.LoginErrorHandler.
26
27
  */
27
28
  tryCompleteSignIn(): Promise<ISignInResult>;
28
- signinSilent(): Promise<any>;
29
+ signinSilent(): Promise<IUser | null>;
29
30
  endSignInSilent(): Promise<any>;
30
31
  beginSignOut(): void;
31
32
  endSignOut(): Promise<any>;
@@ -10,6 +10,7 @@ import { SignInResult } from '../SignInResult';
10
10
  export default abstract class OidcAuthService<TUser extends OidcUser = OidcUser> implements IAuthenticationService {
11
11
  protected userManager: IUserManager;
12
12
  private axios;
13
+ private config;
13
14
  private _user;
14
15
  private isRedirecting;
15
16
  private afterUserLoadedPromise;
@@ -18,14 +19,10 @@ export default abstract class OidcAuthService<TUser extends OidcUser = OidcUser>
18
19
  * @param userManager Oidc-client user manager instance
19
20
  * @param user User which existed in storage at startup
20
21
  */
21
- constructor(userManager: IUserManager, axios: AxiosInstance);
22
+ constructor(userManager: IUserManager, axios: AxiosInstance, config?: import("../../AppServices").IConfigModel);
22
23
  isSigningOut: boolean;
23
24
  loadUserFromSession(): Promise<TUser | null>;
24
25
  ensureSignedIn(redirectLocal?: string): boolean;
25
- /**
26
- * Redirect to identity server to get the auth token.
27
- * @param redirectPath Local path to redirect to once login is complete.
28
- */
29
26
  beginSignIn(redirectPath?: string): Promise<any>;
30
27
  /**
31
28
  * Reads the access token from the query string and returns the redirect path.
@@ -37,13 +34,14 @@ export default abstract class OidcAuthService<TUser extends OidcUser = OidcUser>
37
34
  */
38
35
  tryCompleteSignIn(): Promise<SignInResult>;
39
36
  endSignInSilent(): Promise<any>;
37
+ protected setUserDataAsync(userData: IOidcUser): Promise<void>;
40
38
  private onUserLoaded;
41
39
  protected afterUserLoaded(): Promise<void> | void;
42
- private completeAfterUserLoaded;
40
+ private awaitAfterUserLoadedAsync;
43
41
  protected createUser(user: IOidcUser): OidcUser;
44
42
  private onUserUnloaded;
45
43
  private setUser;
46
- signinSilent(): Promise<IOidcUser | null>;
44
+ signinSilent(): Promise<TUser | null>;
47
45
  beginSignOut(): void;
48
46
  endSignOut(): Promise<any>;
49
47
  private signOutLocal;
@@ -4,6 +4,8 @@ import OidcUser from './OidcUser';
4
4
  import { SignInResult } from '../SignInResult';
5
5
  import { Misc } from '../../';
6
6
  import { NeoTypes } from '../../AppServices';
7
+ import NetworkUtils from "../../Network/NetworkUtils";
8
+ var storageKey = "neo-app-user-data";
7
9
  /**
8
10
  * Oidc authentication service base class.
9
11
  * You must override this class and provide the user manager implementation from the oidc library.
@@ -14,9 +16,12 @@ var OidcAuthService = /** @class */ (function () {
14
16
  * @param userManager Oidc-client user manager instance
15
17
  * @param user User which existed in storage at startup
16
18
  */
17
- function OidcAuthService(userManager, axios) {
19
+ function OidcAuthService(userManager, axios, config) {
20
+ if (config === void 0) { config = Misc.Globals.appService.get(NeoTypes.Config.ConfigModel); }
21
+ var _this = this;
18
22
  this.userManager = userManager;
19
23
  this.axios = axios;
24
+ this.config = config;
20
25
  this._user = null;
21
26
  this.isRedirecting = false;
22
27
  this.isSigningOut = false;
@@ -25,31 +30,38 @@ var OidcAuthService = /** @class */ (function () {
25
30
  this.userManager.events.addUserUnloaded(this.onUserUnloaded.bind(this));
26
31
  if (!this.userManager.settings.automaticSilentRenew) {
27
32
  // If automaticSilentRenew is set to true, then the user manager will handle automatic renewal.
28
- this.userManager.events.addAccessTokenExpiring(this.signinSilent.bind(this));
33
+ this.userManager.events.addAccessTokenExpiring(function () { return _this.userManager.signinSilent(); });
29
34
  }
30
35
  }
31
36
  OidcAuthService.prototype.loadUserFromSession = function () {
32
37
  return __awaiter(this, void 0, void 0, function () {
33
- var user;
38
+ var userData, user;
34
39
  return __generator(this, function (_a) {
35
40
  switch (_a.label) {
36
- case 0: return [4 /*yield*/, this.userManager.getUser()];
41
+ case 0:
42
+ if (!NetworkUtils.useCachedData(this.config)) return [3 /*break*/, 3];
43
+ userData = localStorage.getItem(storageKey);
44
+ if (!userData) return [3 /*break*/, 2];
45
+ return [4 /*yield*/, this.setUserDataAsync(JSON.parse(userData))];
37
46
  case 1:
38
- user = _a.sent();
39
- if (!(user && !user.expired)) return [3 /*break*/, 3];
40
- // Get user does not raise the onUserLoaded event, so we call it manually.
41
- this.onUserLoaded(user);
42
- return [4 /*yield*/, this.completeAfterUserLoaded()];
43
- case 2:
44
47
  _a.sent();
45
- return [2 /*return*/, this.user];
46
- case 3:
47
- if (!!!window.opener) return [3 /*break*/, 5];
48
- return [4 /*yield*/, this.userManager.signinSilent()];
48
+ _a.label = 2;
49
+ case 2: return [2 /*return*/, this.user];
50
+ case 3: return [4 /*yield*/, this.userManager.getUser()];
49
51
  case 4:
52
+ user = _a.sent();
53
+ if (!(user && !user.expired)) return [3 /*break*/, 6];
54
+ // Get user does not raise the onUserLoaded event, so we call it manually.
55
+ return [4 /*yield*/, this.setUserDataAsync(user)];
56
+ case 5:
57
+ // Get user does not raise the onUserLoaded event, so we call it manually.
50
58
  _a.sent();
51
59
  return [2 /*return*/, this.user];
52
- case 5: return [2 /*return*/, null];
60
+ case 6:
61
+ if (!!!window.opener) return [3 /*break*/, 8];
62
+ return [4 /*yield*/, this.signinSilent()];
63
+ case 7: return [2 /*return*/, _a.sent()];
64
+ case 8: return [2 /*return*/, null];
53
65
  }
54
66
  });
55
67
  });
@@ -64,11 +76,10 @@ var OidcAuthService = /** @class */ (function () {
64
76
  }
65
77
  return true;
66
78
  };
67
- /**
68
- * Redirect to identity server to get the auth token.
69
- * @param redirectPath Local path to redirect to once login is complete.
70
- */
71
79
  OidcAuthService.prototype.beginSignIn = function (redirectPath) {
80
+ if (redirectPath === undefined) {
81
+ redirectPath = (window.location.pathname + window.location.search).substring(this.config.baseUrl.length);
82
+ }
72
83
  if (!this.isRedirecting) {
73
84
  this.isRedirecting = true;
74
85
  return this.userManager.signinRedirect({ state: redirectPath });
@@ -121,7 +132,7 @@ var OidcAuthService = /** @class */ (function () {
121
132
  return [4 /*yield*/, this.endSignIn()];
122
133
  case 2:
123
134
  redirectPath = _a.sent();
124
- return [4 /*yield*/, this.completeAfterUserLoaded()];
135
+ return [4 /*yield*/, this.awaitAfterUserLoadedAsync()];
125
136
  case 3:
126
137
  _a.sent();
127
138
  return [2 /*return*/, new SignInResult(true, redirectPath)];
@@ -137,13 +148,31 @@ var OidcAuthService = /** @class */ (function () {
137
148
  OidcAuthService.prototype.endSignInSilent = function () {
138
149
  return this.userManager.signinSilentCallback();
139
150
  };
140
- OidcAuthService.prototype.onUserLoaded = function (user) {
141
- this.setUser(this.createUser(user));
151
+ OidcAuthService.prototype.setUserDataAsync = function (userData) {
152
+ return __awaiter(this, void 0, void 0, function () {
153
+ return __generator(this, function (_a) {
154
+ switch (_a.label) {
155
+ case 0:
156
+ this.onUserLoaded(userData, false);
157
+ return [4 /*yield*/, this.awaitAfterUserLoadedAsync()];
158
+ case 1:
159
+ _a.sent();
160
+ return [2 /*return*/];
161
+ }
162
+ });
163
+ });
164
+ };
165
+ OidcAuthService.prototype.onUserLoaded = function (userData, store) {
166
+ if (store === void 0) { store = true; }
167
+ this.setUser(this.createUser(userData));
168
+ if (store && this.config.supportsOffline) {
169
+ localStorage.setItem(storageKey, JSON.stringify(userData));
170
+ }
142
171
  this.afterUserLoadedPromise = this.afterUserLoaded();
143
172
  };
144
173
  OidcAuthService.prototype.afterUserLoaded = function () {
145
174
  };
146
- OidcAuthService.prototype.completeAfterUserLoaded = function () {
175
+ OidcAuthService.prototype.awaitAfterUserLoadedAsync = function () {
147
176
  return __awaiter(this, void 0, void 0, function () {
148
177
  return __generator(this, function (_a) {
149
178
  switch (_a.label) {
@@ -175,7 +204,29 @@ var OidcAuthService = /** @class */ (function () {
175
204
  this._user = user;
176
205
  };
177
206
  OidcAuthService.prototype.signinSilent = function () {
178
- return this.userManager.signinSilent();
207
+ return __awaiter(this, void 0, void 0, function () {
208
+ var e_3;
209
+ return __generator(this, function (_a) {
210
+ switch (_a.label) {
211
+ case 0:
212
+ _a.trys.push([0, 2, , 3]);
213
+ return [4 /*yield*/, this.userManager.signinSilent()];
214
+ case 1:
215
+ _a.sent();
216
+ return [3 /*break*/, 3];
217
+ case 2:
218
+ e_3 = _a.sent();
219
+ if (e_3.error === "login_required" || e_3.error === "consent_required" || e_3.error === "interaction_required") {
220
+ return [2 /*return*/, null];
221
+ }
222
+ return [3 /*break*/, 3];
223
+ case 3: return [4 /*yield*/, this.awaitAfterUserLoadedAsync()];
224
+ case 4:
225
+ _a.sent();
226
+ return [2 /*return*/, this.user];
227
+ }
228
+ });
229
+ });
179
230
  };
180
231
  OidcAuthService.prototype.beginSignOut = function () {
181
232
  if (!this.isRedirecting) {
@@ -207,6 +258,10 @@ var OidcAuthService = /** @class */ (function () {
207
258
  });
208
259
  Object.defineProperty(OidcAuthService.prototype, "isAuthenticated", {
209
260
  get: function () {
261
+ if (NetworkUtils.useCachedData(this.config)) {
262
+ // Ignore expiry when offline.
263
+ return this._user !== null;
264
+ }
210
265
  return this.user !== null && !this.user.hasExpired;
211
266
  },
212
267
  enumerable: false,
@@ -1,7 +1,7 @@
1
1
  import * as signalR from '@microsoft/signalr';
2
2
  import { IHubReceiver } from "./HubReceiver";
3
3
  import { AppServices } from '../';
4
- import { IConnection, ConnectionState } from '../ConnectionManager';
4
+ import { IConnection, ConnectionState } from '../Network/IConnectionManager';
5
5
  import { IDisposable } from '../Misc';
6
6
  export interface IHubBase extends IConnection, IDisposable {
7
7
  /**
@@ -2,7 +2,7 @@ import { __awaiter, __generator, __spreadArray } from "tslib";
2
2
  import * as signalR from '@microsoft/signalr';
3
3
  import HubReceiver from "./HubReceiver";
4
4
  import { AppServices, Misc, TaskRunner } from '../';
5
- import { ConnectionState } from '../ConnectionManager';
5
+ import { ConnectionState } from '../Network/IConnectionManager';
6
6
  import { ObservableValue } from '../Model';
7
7
  import { Utils } from '../Utils';
8
8
  var HubBase = /** @class */ (function () {
package/dist/index.d.ts CHANGED
@@ -23,7 +23,7 @@ import * as JsonSchema from './JsonSchema';
23
23
  import * as Localisation from './Localisation';
24
24
  import * as Misc from './Misc';
25
25
  import * as Model from './Model';
26
- import * as Network from './ConnectionManager';
26
+ import * as Network from './Network';
27
27
  import * as Numeric from './Numeric';
28
28
  import * as Routing from './Routing';
29
29
  import * as Security from './Security';
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import * as JsonSchema from './JsonSchema';
26
26
  import * as Localisation from './Localisation';
27
27
  import * as Misc from './Misc';
28
28
  import * as Model from './Model';
29
- import * as Network from './ConnectionManager';
29
+ import * as Network from './Network';
30
30
  import * as Numeric from './Numeric';
31
31
  import * as Routing from './Routing';
32
32
  import * as Security from './Security';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singularsystems/neo-core",
3
- "version": "1.4.2",
3
+ "version": "1.5.1",
4
4
  "description": "Core functionality for the Neo client library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",