lavva.exalushome
Version:
Library implementing communication and abstraction layers for ExalusHome system
680 lines • 32.7 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import * as signalR from "@microsoft/signalr";
import { DataFrame, Method, Status, } from "../DataFrame";
import { ConnectionResult, AuthorizationInfo, ConnectionState, StreamError, } from "./IExalusConnectionService";
import { TypedEvent } from "../TypedEvent";
import { Api } from "../Api";
import { LoggerService } from "./Logging/LoggerService";
import { Event } from "../Event";
import { ControllerConfigurationService } from "./Controller/ControllerConfigurationService";
import { WebApiCacheService } from "./WebApi/WebApiCacheService";
import { SessionService } from "./Session/SessionService";
import { LogLevel } from "./Logging/ILoggerService";
class FastRetryPolicy {
nextRetryDelayInMilliseconds() {
return 1000; // constant back‑off
}
}
const TRANSPORTS = [
signalR.HttpTransportType.WebSockets,
signalR.HttpTransportType.ServerSentEvents,
signalR.HttpTransportType.LongPolling,
];
export class SignalrLogger {
constructor() {
this._log = Api.Get(LoggerService.ServiceName);
}
log(level, message) {
if (level < ExalusConnectionService.SignalRLogLevel)
return;
message = `[SignalR] ${message}`;
switch (level) {
case signalR.LogLevel.Critical:
case signalR.LogLevel.Error:
this._log.Error(message);
break;
case signalR.LogLevel.Warning:
this._log.Warning(message);
break;
case signalR.LogLevel.Information:
this._log.Info(message);
break;
default:
this._log.Debug(message);
break;
}
}
}
export class ExalusConnectionService {
constructor() {
this._connectTask = null;
this._pingTimerId = null;
this._consecutivePingFailures = 0;
this._disconnectedOnPurpose = false;
this._address = "packets-broker1.tr7.pl";
this._serversBrokerAddress = "https://servers-broker.tr7.pl";
this._serversBrokerAddressList = [
"https://servers-broker.tr7.pl",
"https://broker.tr7.pl",
"https://dev-broker.tr7.pl",
];
this._allBrokersChecked = false;
this._packetsBrokerServers = [
"packets-broker1.tr7.pl",
"packets-broker2.tr7.pl"
];
this._lastReceivedPacket = Date.now();
this._everConnected = false;
this._pendingRequests = new Map();
this._log = Api.Get(LoggerService.ServiceName);
this._controllerConfiguration = null;
this._cache = null;
this._session = null;
this._dataReceivedEvent = new TypedEvent();
this._pongReceivedEvent = new Event();
this._authorizationReceivedEvent = new TypedEvent();
this._registrationReceivedEvent = new TypedEvent();
this._streamStartedEvent = new TypedEvent();
this._connectionStateChangedEvent = new TypedEvent();
this._errorOccuredEvent = new TypedEvent();
this._tryReconnect = () => {
if (navigator.onLine) {
this.validateConnectionToController().then((isConnected) => {
if (!isConnected) {
this._log.Debug(ExalusConnectionService.ServiceName, "Connection lost → reconnecting...");
void this.serialisedConnect();
}
});
}
};
this._onVisibility = () => {
if (document.visibilityState === "visible") {
this._log.Debug(ExalusConnectionService.ServiceName, "Page visible → connection check");
this.validateConnectionToController().then((isConnected) => {
if (!isConnected) {
this._log.Debug(ExalusConnectionService.ServiceName, "Connection lost → reconnecting...");
void this.serialisedConnect();
}
});
}
};
this._onHidden = () => { };
this._onVisible = () => {
this.validateConnectionToController().then((isConnected) => {
if (!isConnected) {
this._log.Debug(ExalusConnectionService.ServiceName, "Connection lost → reconnecting...");
void this.serialisedConnect();
}
});
};
}
GetServiceName() {
return ExalusConnectionService.ServiceName;
}
GetAuthorizationInfo() {
return this._serialId && this._PIN ? new AuthorizationInfo(this._serialId, this._PIN) : null;
}
GetControllerSerialNumber() {
return this._serialId;
}
GetControllerPin() {
return this._PIN;
}
SetServersBrokerAddress(a) {
this._serversBrokerAddress = a;
}
SetDefaultPacketsBrokerAddress(a) {
this._address = a;
}
EnablePacketsLogging() {
window["packets"] = true;
}
DisablePacketsLogging() {
window["packets"] = false;
}
SubscribeTo(resourceId, handler) {
const h = (f) => {
if (f.Resource === resourceId)
handler(f);
};
this._dataReceivedEvent.Subscribe(h);
return () => this._dataReceivedEvent.Unsubscribe(h);
}
GetServerAddressAsync() {
return __awaiter(this, void 0, void 0, function* () {
if (!this._serialId) {
this._log.Warning(ExalusConnectionService.ServiceName, "GetServerAddressAsync() – serialId nieustawiony");
return null;
}
const url = `${this._serversBrokerAddress}/api/connections/broker/whichserver/${this._serialId}`;
try {
const resp = yield fetch(url);
switch (resp.status) {
case 200: {
const addr = (yield resp.text()).trim();
if (addr) {
this._log.Debug(ExalusConnectionService.ServiceName, `Broker address resolved: ${addr}`);
return addr;
}
this._log.Warning(ExalusConnectionService.ServiceName, "Broker returned empty address");
return null;
}
case 204: {
if (!this._allBrokersChecked) {
this._log.Warning(ExalusConnectionService.ServiceName, `Broker controller ${this._serversBrokerAddress} returned 204 (no content) – controller not found on given server`);
yield this.rotateServersBroker();
return this.GetServerAddressAsync();
}
else {
this._log.Error(ExalusConnectionService.ServiceName, "All brokers checkt but controller not found! Controller probably offline!");
return null;
}
}
default: {
this._log.Warning(ExalusConnectionService.ServiceName, `GetServerAddressAsync() – HTTP ${resp.status}`);
return null;
}
}
}
catch (err) {
this._log.Error(ExalusConnectionService.ServiceName, String(err));
return null;
}
});
}
rotateServersBroker() {
return __awaiter(this, void 0, void 0, function* () {
const idx = this._serversBrokerAddressList.indexOf(this._serversBrokerAddress);
const next = idx + 1 < this._serversBrokerAddressList.length ? idx + 1 : 0;
this._serversBrokerAddress = this._serversBrokerAddressList[next];
this._allBrokersChecked = next === 0;
this._log.Info(ExalusConnectionService.ServiceName, `Switching servers‑broker to: ${this._serversBrokerAddress}`);
});
}
ConnectAsync(address) {
return __awaiter(this, void 0, void 0, function* () {
this._address = address;
return this.serialisedConnect();
});
}
ConnectAndAuthorizeAsync(info) {
return __awaiter(this, void 0, void 0, function* () {
this._serialId = info.serialNumber;
this._PIN = info.pin;
Api.WorksInContextOf = this._serialId;
let packetsBroker = yield this.GetServerAddressAsync();
if (packetsBroker) {
this.SetDefaultPacketsBrokerAddress(packetsBroker);
if ((yield this.serialisedConnect()) === ConnectionResult.Connected &&
(yield this.AuthorizeAsync(info))) {
return ConnectionResult.Connected;
}
}
for (const host of this._packetsBrokerServers) {
this._log.Info(ExalusConnectionService.ServiceName, `Fallback broker: ${host}`);
this.SetDefaultPacketsBrokerAddress(host);
if ((yield this.serialisedConnect()) === ConnectionResult.Connected &&
(yield this.AuthorizeAsync(info))) {
return ConnectionResult.Connected;
}
}
return ConnectionResult.FailedToConnect;
});
}
AuthorizeAsync(info) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
var _a;
const timeoutId = window.setTimeout(() => {
this._authorizationReceivedEvent.Unsubscribe(onAuth);
resolve(false);
}, 2000);
const onAuth = (ok) => {
clearTimeout(timeoutId);
this._authorizationReceivedEvent.Unsubscribe(onAuth);
this._log.Debug(ExalusConnectionService.ServiceName, `Authorization → ${ok}`);
if (ok)
this._connectionStateChangedEvent.Invoke(ConnectionState.ConnectedAndAuthorized);
resolve(ok);
};
this._authorizationReceivedEvent.Subscribe(onAuth);
yield ((_a = this._connection) === null || _a === void 0 ? void 0 : _a.send("AuthorizeTo", info.SerialNumber, info.PIN));
}));
});
}
IsConnected() {
var _a;
return ((_a = this._connection) === null || _a === void 0 ? void 0 : _a.state) === signalR.HubConnectionState.Connected;
}
DisconnectAsync() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
this._disconnectedOnPurpose = true;
this._log.Debug(ExalusConnectionService.ServiceName, "Disconnecting...");
yield ((_a = this._connection) === null || _a === void 0 ? void 0 : _a.stop());
this.cleanup();
});
}
SendAsync(dataFrame_1) {
return __awaiter(this, arguments, void 0, function* (dataFrame, logTx = false) {
if (!this.IsConnected())
throw new Error("Not connected");
const dump = window["packets"] === true;
if (dump || logTx) {
this._log.Debug(ExalusConnectionService.ServiceName, `⇢ ${dataFrame.Resource} ${dataFrame.Method} ${dataFrame.TransactionId}` +
(dump ? `\n${JSON.stringify(dataFrame, null, 2)}` : ""));
}
try {
yield this._connection.invoke("SendTo", this._serialId, dataFrame);
return true;
}
catch (err) {
this._log.Error(ExalusConnectionService.ServiceName, String(err));
return false;
}
});
}
SendAndWaitForResponseAsync(dataFrame_1, timeout_1, useCache_1) {
return __awaiter(this, arguments, void 0, function* (dataFrame, timeout, useCache, logTransmission = true) {
var _a, _b, _c;
if (dataFrame.Method === Method.Get && useCache) {
if (!(yield ((_a = this._controllerConfiguration) === null || _a === void 0 ? void 0 : _a.DidCofigurationChangeAsync()))) {
const cached = (_b = this._cache) === null || _b === void 0 ? void 0 : _b.GetCache(dataFrame);
if (cached)
return cached;
}
}
if (!this.IsConnected())
throw new Error("Connection is not established");
if (dataFrame.Resource !== "/users/user/login")
yield ((_c = this._session) === null || _c === void 0 ? void 0 : _c.WaitForSessionCreationAsync());
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
const timeoutId = window.setTimeout(() => {
this._dataReceivedEvent.Unsubscribe(onRx);
this._pendingRequests.delete(dataFrame.TransactionId);
reject(new signalR.TimeoutError("Response timeout"));
}, timeout);
const onRx = (frame) => {
var _a, _b;
if (frame.TransactionId !== dataFrame.TransactionId)
return;
clearTimeout(timeoutId);
this._dataReceivedEvent.Unsubscribe(onRx);
this._pendingRequests.delete(dataFrame.TransactionId);
if (dataFrame.Method === Method.Get && useCache)
(_a = this._cache) === null || _a === void 0 ? void 0 : _a.Cache(frame);
if (!useCache && frame.Status === Status.UserIsNotLoggedIn) {
void ((_b = this._session) === null || _b === void 0 ? void 0 : _b.RestoreSessionAsync().then(() => resolve(this.SendAndWaitForResponseAsync(dataFrame, timeout, useCache, logTransmission))));
return;
}
resolve(frame);
};
this._dataReceivedEvent.Subscribe(onRx);
this._pendingRequests.set(dataFrame.TransactionId, {
resolve: (v) => resolve(v),
reject,
timeoutId,
});
if (!(yield this.SendAsync(dataFrame, logTransmission))) {
clearTimeout(timeoutId);
this._dataReceivedEvent.Unsubscribe(onRx);
this._pendingRequests.delete(dataFrame.TransactionId);
reject(new Error("Failed to send request"));
}
}));
});
}
SendAndHandleResponseAsync(dataFrame_1, timeout_1, dataHandler_1) {
return __awaiter(this, arguments, void 0, function* (dataFrame, timeout, dataHandler, logTransmission = true) {
var _a;
if (!this.IsConnected())
throw new Error("Connection is not established");
if (dataFrame.Resource !== "/users/user/login")
yield ((_a = this._session) === null || _a === void 0 ? void 0 : _a.WaitForSessionCreationAsync());
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
const resetTimeout = () => window.setTimeout(() => {
cleanup();
reject(new signalR.TimeoutError("MultiDataResponse timeout"));
}, timeout);
let tid = resetTimeout();
const cleanup = () => {
clearTimeout(tid);
this._dataReceivedEvent.Unsubscribe(onRx);
this._pendingRequests.delete(dataFrame.TransactionId);
};
const onRx = (frame) => {
if (frame.TransactionId !== dataFrame.TransactionId)
return;
clearTimeout(tid);
switch (frame.Status) {
case Status.MultiDataResponseStart:
case Status.MultiDataResponse:
dataHandler(frame);
tid = resetTimeout();
break;
case Status.MultiDataResponseStop:
case Status.OK:
dataHandler(frame);
cleanup();
resolve();
break;
default:
cleanup();
reject(new Error(`Unexpected status ${frame.Status}`));
}
};
this._dataReceivedEvent.Subscribe(onRx);
this._pendingRequests.set(dataFrame.TransactionId, {
resolve: () => resolve(),
reject,
timeoutId: tid,
});
if (!(yield this.SendAsync(dataFrame, logTransmission))) {
cleanup();
reject(new Error("Failed to send request"));
}
}));
});
}
SendAndHandleStreamAsync(dataFrame_1, streamHandler_1) {
return __awaiter(this, arguments, void 0, function* (dataFrame, streamHandler, logTransmission = true) {
var _a;
if (!this.IsConnected())
throw new Error("Connection is not established");
if (dataFrame.Resource !== "/users/user/login")
yield ((_a = this._session) === null || _a === void 0 ? void 0 : _a.WaitForSessionCreationAsync());
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
const timeoutId = window.setTimeout(() => {
this._streamStartedEvent.Unsubscribe(onStreamStart);
reject(new signalR.TimeoutError("Stream registration timeout"));
}, 8000);
const onStreamStart = (streamId) => {
if (streamId !== dataFrame.TransactionId)
return;
clearTimeout(timeoutId);
this._streamStartedEvent.Unsubscribe(onStreamStart);
this._connection
.stream("WatchStream", streamId)
.subscribe({
next: (item) => streamHandler.Next(item),
complete: () => {
streamHandler.Complete();
resolve();
},
error: (err) => {
streamHandler.Error(err);
reject(new StreamError(String(err)));
},
});
};
this._streamStartedEvent.Subscribe(onStreamStart);
if (!(yield this.SendAsync(dataFrame, logTransmission))) {
clearTimeout(timeoutId);
this._streamStartedEvent.Unsubscribe(onStreamStart);
reject(new Error("Failed to send request"));
}
}));
});
}
PingControllerAsync() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.IsConnected())
return false;
if (Date.now() - this._lastReceivedPacket < ExalusConnectionService.PING_INTERVAL_MS)
return false;
const frame = new DataFrame();
frame.Resource = "/system/ping";
frame.Method = Method.Get;
try {
yield this.SendAndWaitForResponseAsync(frame, 2000, false, false);
return true;
}
catch (_a) {
return false;
}
});
}
OnDataReceivedEvent() {
return this._dataReceivedEvent;
}
OnConnectionStateChangedEvent() {
return this._connectionStateChangedEvent;
}
OnErrorOccuredEvent() {
return this._errorOccuredEvent;
}
serialisedConnect() {
if (this._connectTask)
return this._connectTask;
this._connectTask = this.connectCore().finally(() => (this._connectTask = null));
return this._connectTask;
}
connectCore() {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e;
if (!this._address)
return ConnectionResult.ControllerIsNotConnected;
if (((_a = this._connection) === null || _a === void 0 ? void 0 : _a.state) === signalR.HubConnectionState.Reconnecting) {
this._log.Info(ExalusConnectionService.ServiceName, "Connection is already in reconnecting state - letting automatic reconnect handle it");
try {
yield this.waitForReconnection();
this._log.Info(ExalusConnectionService.ServiceName, "Automatic reconnect successful");
return ConnectionResult.Connected;
}
catch (error) {
this._log.Warning(ExalusConnectionService.ServiceName, `Automatic reconnect failed: ${error} trying to reconnect by creting new connection`);
}
}
if (((_b = this._connection) === null || _b === void 0 ? void 0 : _b.state) != signalR.HubConnectionState.Disconnected) {
this._log.Info(ExalusConnectionService.ServiceName, "connectCore() was called, but connection curently exists! Disconnecting...");
yield ((_c = this._connection) === null || _c === void 0 ? void 0 : _c.stop());
}
this.initializeServices();
this.cleanup();
for (const t of TRANSPORTS) {
var builder = new signalR.HubConnectionBuilder()
.withAutomaticReconnect(new FastRetryPolicy())
.configureLogging(new SignalrLogger())
// https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-8.0#signalr-stateful-reconnect
.withStatefulReconnect({ bufferSize: 256000 })
.withServerTimeout(ExalusConnectionService.SERVER_TIMOUT_MS)
.withUrl(`https://${this._address}/broker`, {
// skipNegotiation tylko dla WebSocket
skipNegotiation: t === signalR.HttpTransportType.WebSockets,
transport: t,
});
if (this._log.LogLevel === LogLevel.Debug) {
builder.configureLogging(signalR.LogLevel.Debug);
}
this._connection = builder.build();
this.wireConnectionEvents();
try {
yield this._connection.start();
this.startPingLoop();
this._connectionStateChangedEvent.Invoke(ConnectionState.Connected);
return ConnectionResult.Connected;
}
catch (err) {
this._log.Warning(ExalusConnectionService.ServiceName, `Transport ${signalR.HttpTransportType[t]} failed (${err === null || err === void 0 ? void 0 : err.message}).`);
yield this._connection.stop().catch(() => { });
(_e = (_d = this._connection).off) === null || _e === void 0 ? void 0 : _e.call(_d);
}
}
this._connectionStateChangedEvent.Invoke(ConnectionState.Failed);
return ConnectionResult.FailedToConnect;
});
}
waitForReconnection(timeoutMs = 0) {
return new Promise((resolve, reject) => {
if (!this._connection || this._connection.state !== signalR.HubConnectionState.Reconnecting) {
reject(new Error("Connection is not in reconnecting state"));
return;
}
let timeoutId = undefined;
if (timeoutMs > 0) {
timeoutId = window.setTimeout(() => {
this._connectionStateChangedEvent.Unsubscribe(handler);
reject(new Error("Reconnection timeout"));
}, timeoutMs);
}
const handler = (state) => {
if (state === ConnectionState.Connected) {
if (timeoutId)
clearTimeout(timeoutId);
this._connectionStateChangedEvent.Unsubscribe(handler);
resolve();
}
else if (state === ConnectionState.Disconnected || state === ConnectionState.Failed) {
if (timeoutId)
clearTimeout(timeoutId);
this._connectionStateChangedEvent.Unsubscribe(handler);
reject(new Error("Connection closed during reconnection"));
}
};
this._connectionStateChangedEvent.Subscribe(handler);
if (this._connection.state !== signalR.HubConnectionState.Reconnecting) {
if (timeoutId)
clearTimeout(timeoutId);
this._connectionStateChangedEvent.Unsubscribe(handler);
if (this._connection.state === signalR.HubConnectionState.Connected) {
resolve();
}
else {
reject(new Error(`Connection is in unexpected state: ${this._connection.state}`));
}
}
});
}
initializeServices() {
this._controllerConfiguration = Api.Get(ControllerConfigurationService.ServiceName);
this._cache = Api.Get(WebApiCacheService.ServiceName);
this._session = Api.Get(SessionService.ServiceName);
}
wireConnectionEvents() {
if (!this._connection)
return;
this._connection.on("Pong", () => {
this._lastReceivedPacket = Date.now();
this._pongReceivedEvent.Invoke();
});
this._connection.on("Authorization", (ok) => this._authorizationReceivedEvent.Invoke(ok));
this._connection.on("Registration", (d) => this._registrationReceivedEvent.Invoke(d));
this._connection.on("SendError", (s, d) => {
if (s.startsWith("NotAuthorized:")) {
this.AuthorizeAsync(new AuthorizationInfo(this._serialId, this._PIN));
}
else {
this._errorOccuredEvent.Invoke([s, d]);
}
});
this._connection.on("Data", (_, json) => {
this._lastReceivedPacket = Date.now();
if (window["packets"] === true)
this._log.Debug(ExalusConnectionService.ServiceName, `Received: ${JSON.stringify(JSON.parse(json), null, 2)}`);
this._dataReceivedEvent.Invoke(JSON.parse(json));
});
this._connection.onclose(() => {
this.cleanup();
this._connectionStateChangedEvent.Invoke(ConnectionState.Disconnected);
});
this._connection.onreconnecting(() => this._connectionStateChangedEvent.Invoke(ConnectionState.Reconnecting));
this._connection.onreconnected(() => this._connectionStateChangedEvent.Invoke(ConnectionState.Connected));
}
startPingLoop() {
if (this._pingTimerId !== null)
clearInterval(this._pingTimerId);
this._pingTimerId = window.setInterval(() => void this.pingOnce(), ExalusConnectionService.PING_INTERVAL_MS);
window.addEventListener("online", this._tryReconnect);
window.addEventListener("offline", this._tryReconnect);
document.addEventListener("visibilitychange", this._onVisibility, true);
window.addEventListener("pagehide", this._onHidden, true);
window.addEventListener("pageshow", this._onVisible, true);
}
pingOnce() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.IsConnected())
return;
if (Date.now() - this._lastReceivedPacket < ExalusConnectionService.PING_INTERVAL_MS)
return;
const ok = yield this.PingControllerAsync();
if (ok) {
this._consecutivePingFailures = 0;
}
else if (++this._consecutivePingFailures >= ExalusConnectionService.MAX_CONSECUTIVE_PING_FAILURES) {
this._consecutivePingFailures = 0;
this._log.Warning(ExalusConnectionService.ServiceName, "Ping failed too many times → reconnecting...");
if (ExalusConnectionService.SERVER_TIMOUT_MS < ExalusConnectionService.PING_INTERVAL_MS * ExalusConnectionService.MAX_CONSECUTIVE_PING_FAILURES) {
this._log.Error("Server timeout is shorter than ping interval! SignalR automatic reconnect will not work!.");
}
void this.serialisedConnect();
}
});
}
validateConnectionToController() {
return __awaiter(this, void 0, void 0, function* () {
var _a;
if (this.IsConnected()) {
const frame = new DataFrame();
frame.Resource = "/system/ping";
frame.Method = Method.Get;
try {
var res = yield this.SendAndWaitForResponseAsync(frame, ExalusConnectionService.SERVER_TIMOUT_MS + 1000, false, false);
if ((res === null || res === void 0 ? void 0 : res.Status) !== Status.OK) {
this._log.Warning(ExalusConnectionService.ServiceName, `Connection check failed, ${res == null ? 'result is null' : `status: ${res.Status}`}`);
return false;
}
else if (((_a = this._connection) === null || _a === void 0 ? void 0 : _a.state) == signalR.HubConnectionState.Reconnecting) {
this._log.Warning(ExalusConnectionService.ServiceName, "Connection check failed, but connection is in reconnecting state!");
return false;
}
else {
this._log.Info(ExalusConnectionService.ServiceName, "Connection still active!");
return true;
}
}
catch (_b) {
return false;
}
}
else {
this._log.Warning(ExalusConnectionService.ServiceName, "Connection was lost.");
return false;
}
});
}
cleanup() {
var _a, _b;
if (this._pingTimerId !== null) {
clearInterval(this._pingTimerId);
this._pingTimerId = null;
}
// reject all outstanding waiters
for (const [, p] of this._pendingRequests) {
clearTimeout(p.timeoutId);
p.reject(new Error("Connection lost"));
}
this._pendingRequests.clear();
window.removeEventListener("online", this._tryReconnect);
window.removeEventListener("offline", this._tryReconnect);
document.removeEventListener("visibilitychange", this._onVisibility, true);
window.removeEventListener("pagehide", this._onHidden, true);
window.removeEventListener("pageshow", this._onVisible, true);
// Typing workaround: we want to detach *all* handlers
(_b = (_a = this._connection) === null || _a === void 0 ? void 0 : _a.off) === null || _b === void 0 ? void 0 : _b.call(_a);
this._connection = undefined;
}
} // end of class
ExalusConnectionService.PING_INTERVAL_MS = 5000;
ExalusConnectionService.MAX_CONSECUTIVE_PING_FAILURES = 6;
ExalusConnectionService.SERVER_TIMOUT_MS = 10000;
ExalusConnectionService.SignalRLogLevel = signalR.LogLevel.Warning;
ExalusConnectionService.ServiceName = "ExalusConnectionService";
//# sourceMappingURL=ExalusConnectionService.js.map