@ancon/wildcat-utils 1.53.0 → 1.53.2

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.
@@ -4,6 +4,13 @@ import type { Logger } from '../logger';
4
4
  declare type HangfirePollerConfig = {
5
5
  /** Poller interval in milliseconds */
6
6
  pollerInterval: number;
7
+ /**
8
+ * Timeout in milliseconds of a single status request. Kept separate from
9
+ * `pollerInterval` so that a final response that takes longer than one poll
10
+ * interval to arrive is not aborted and re-requested forever.
11
+ * Defaults to 15 s, and is never lower than `pollerInterval`.
12
+ */
13
+ requestTimeout?: number;
7
14
  /** Max number of simultaneous groups */
8
15
  maxGroupsSize: number;
9
16
  /** Max number of simultaneous workers per group */
@@ -31,8 +38,10 @@ declare type HangfirePollerConfig = {
31
38
  logger?: Logger;
32
39
  };
33
40
  declare class HangfirePoller {
34
- /** Workers currently executing a request */
35
- private busyWorkers;
41
+ /** Number of status requests in flight, by worker ID */
42
+ private activeRequests;
43
+ /** Workers with a force-triggered request in flight */
44
+ private forcedRequests;
36
45
  /** Groups with enabled workers */
37
46
  private enabledGroups;
38
47
  /** Workers are grouped by entity ID (check ID or order ID) */
@@ -46,6 +55,19 @@ declare class HangfirePoller {
46
55
  private stopTimer;
47
56
  private startTimer;
48
57
  private waitForNetworkOnline;
58
+ private isWorkerExpired;
59
+ private logCallbackError;
60
+ /**
61
+ * Resolve or reject a worker exactly once, call its `onFinished` callback
62
+ * and remove it from the poller
63
+ */
64
+ private settleWorker;
65
+ private rejectExpiredWorker;
66
+ /**
67
+ * Request the worker status. Never throws: every failure either settles the
68
+ * worker or is logged, and the worker is polled again later.
69
+ */
70
+ private requestWorkerStatus;
49
71
  private executeWorkerRequest;
50
72
  private deleteWorker;
51
73
  createWorker<TPartialContent = unknown>(groupId: string, { workerId, url, headers, onFinished, onPartialContent, maxLifeTimeMillis, }: {
@@ -59,11 +81,23 @@ declare class HangfirePoller {
59
81
  }): Promise<unknown>;
60
82
  /** Trigger a worker to execute immediately, ignoring busy state */
61
83
  triggerWorker(workerId: string): void;
62
- /** Trigger all workers to execute immediately */
63
- triggerAllWorkers(): void;
84
+ /**
85
+ * Trigger all workers to execute immediately.
86
+ * Without `force`, workers with a request in flight are skipped. With
87
+ * `force` (e.g. after a reconnect or when the app returns to the
88
+ * foreground), each worker gets one extra request even if another one is
89
+ * still in flight, for example stuck in a retry backoff.
90
+ */
91
+ triggerAllWorkers(options?: {
92
+ force?: boolean;
93
+ }): void;
64
94
  /** May be called either before or after `createWorker()` */
65
95
  enableWorkers(groupId: string): void;
66
96
  disableWorkers(groupId: string): void;
97
+ /**
98
+ * Stop polling and reject every pending worker, so that callers awaiting a
99
+ * worker get an error instead of waiting forever.
100
+ */
67
101
  destroy(): void;
68
102
  }
69
103
  declare function createHangfirePoller(options?: Partial<HangfirePollerConfig>): HangfirePoller;
@@ -1 +1 @@
1
- "use strict";var I=Object.defineProperty;var k=(s,e,r)=>e in s?I(s,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):s[e]=r;var o=(s,e,r)=>(k(s,typeof e!="symbol"?e+"":e,r),r);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const q=require("axios"),h=require("@ancon/wildcat-types"),g=require("../error/createCodedError.js"),G=require("../error/isNotFoundError.js"),x=require("../error/serializeError.js"),z=require("../error/isErrorWithStatusCode.js"),R=require("../api/isNoContentResponse.js"),M=require("../api/isPartialContentResponse.js"),E=require("../shared/wait.js");require("../error/isCodedError.js");require("../error/isAPIError.js");class y{constructor(e){o(this,"busyWorkers",new Set);o(this,"enabledGroups",new Set);o(this,"groups",new Map);o(this,"workerByIdMap",new Map);o(this,"timer",null);o(this,"config",{pollerInterval:5e3,maxGroupsSize:50,maxWorkersSize:20,responseInterceptor:void 0,responseRejectionInterceptor:void 0,getIsNetworkOnline(){return!0},async getRequestHeaders(){return{}},debug(...e){console.log(...e)}});o(this,"axiosInstance");this.config={...this.config,...e??null},this.axiosInstance=q.create(),(this.config.responseInterceptor||this.config.responseRejectionInterceptor)&&this.axiosInstance.interceptors.response.use(this.config.responseInterceptor,this.config.responseRejectionInterceptor)}stopTimer(){this.timer!=null&&(this.config.debug("*stop timer*"),clearInterval(this.timer),this.timer=null)}startTimer(){if(this.timer!=null)throw g(new Error("Timer already started"),h.ErrorCode.GenericUnexpectedHangfireError);this.config.debug("*start timer*"),this.timer=setInterval(()=>{this.config.debug("*tick*",{busyWorkers:this.busyWorkers.size,enabledGroups:this.enabledGroups.size,groups:this.groups}),this.enabledGroups.forEach(e=>{const r=this.groups.get(e);r&&r.forEach(i=>this.executeWorkerRequest(i))}),this.groups.forEach((e,r)=>{this.enabledGroups.has(r)||e.forEach(i=>{i.maxLifeTimeMillis!=null&&Date.now()-i.createdAt>i.maxLifeTimeMillis&&this.executeWorkerRequest(i)})}),this.groups.size<1&&this.stopTimer()},this.config.pollerInterval)}async waitForNetworkOnline(e=5){let r=0;for(;!this.config.getIsNetworkOnline()&&r<e+1;)r+=1,await E(1e3)}async executeWorkerRequest(e,r=!1){var u,d,f,p,b,W;const i=e.maxLifeTimeMillis!=null&&Date.now()-e.createdAt>e.maxLifeTimeMillis,n=!r&&!i&&this.busyWorkers.has(e.workerId);if(this.config.debug("*executeWorkerRequest*",{workerId:e.workerId,isBusy:n,force:r}),n){this.config.debug("*executeWorkerRequest* worker is busy",e.workerId);return}this.busyWorkers.add(e.workerId),await this.waitForNetworkOnline(),this.config.debug("*executeWorkerRequest* request",{isNetworkOnline:this.config.getIsNetworkOnline()});const c={...await this.config.getRequestHeaders(),...e.headers},a=2;let l=0;for(;l<=a;)try{const t=await this.axiosInstance({url:e.url,method:"get",timeout:this.config.pollerInterval,headers:c});if(!R(t))M(t)?(await((u=e.onPartialContent)==null?void 0:u.call(e,t)),this.busyWorkers.delete(e.workerId)):(e.resolve(t),(d=e.onFinished)==null||d.call(e,r),this.deleteWorker(e.groupId,e.workerId));else if(i){this.config.debug("*executeWorkerRequest* worker lifetime exceeded, forcing exit",e.workerId);const m=x(g(new Error("The Hangfire worker lifetime has been exceeded"),h.ErrorCode.GenericUnexpectedHangfireError));e.reject(m),(f=e.onFinished)==null||f.call(e,!0),this.deleteWorker(e.groupId,e.workerId)}else this.busyWorkers.delete(e.workerId);break}catch(t){if(z(t)){if(G(t)){this.config.debug("*executeWorkerRequest* worker is expired or invalid",e.workerId);const m=x(g(new Error("The Hangfire worker is expired or invalid"),h.ErrorCode.HangfireWorkerExpiredOrInvalid));e.reject(m),(p=e.onFinished)==null||p.call(e,r)}else e.reject(t),(b=e.onFinished)==null||b.call(e,r);this.deleteWorker(e.groupId,e.workerId);break}else if(this.config.debug("*executeWorkerRequest* not an api error"),(W=this.config.logger)==null||W.warn(`Hangfire worker request error: ${JSON.stringify({attempt:l,groupId:e.groupId,workerId:e.workerId,error:x(t)})}`,{CorrelationId:c["x-correlation-id"]}),r&&l<a)l+=1,this.config.debug(`*executeWorkerRequest* retrying non-API error (attempt ${l})`),await E(500);else{this.busyWorkers.delete(e.workerId);break}}}deleteWorker(e,r){const i=this.groups.get(e);i&&(this.config.debug("*delete worker*",i.size,i),i.delete(r),this.workerByIdMap.delete(r),this.busyWorkers.delete(r),i.size<1&&(this.groups.delete(e),this.enabledGroups.delete(e),this.enabledGroups.size<1&&this.stopTimer()))}createWorker(e,{workerId:r,url:i,headers:n,onFinished:c,onPartialContent:a,maxLifeTimeMillis:l}){if(!this.groups.has(e)){if(this.groups.size>=this.config.maxGroupsSize){const d=g(new Error("Max number of Hangfire groups exceeded"),h.ErrorCode.GenericUnexpectedHangfireError,{count:this.groups.size});throw this.destroy(),d}this.groups.set(e,new Map)}const u=this.groups.get(e);return new Promise((d,f)=>{if(u.has(r))throw g(new Error("Duplicate Hangfire worker ID"),h.ErrorCode.GenericUnexpectedHangfireError);if(u.size>=this.config.maxWorkersSize){const b=g(new Error("Max number of Hangfire workers in group exceeded"),h.ErrorCode.GenericUnexpectedHangfireError,{groupId:e,count:u.size});throw f(b),this.destroy(),b}const p={workerId:r,groupId:e,url:i,resolve:d,reject:f,headers:n,onFinished:c,onPartialContent:a,createdAt:Date.now(),maxLifeTimeMillis:l};u.set(r,p),this.workerByIdMap.set(r,p),this.enabledGroups.has(e)||this.enabledGroups.add(e),this.timer==null&&this.enabledGroups.size>0&&this.startTimer()})}triggerWorker(e){this.config.debug("*triggerWorker*");const r=this.workerByIdMap.get(e);r?this.executeWorkerRequest(r,!0):this.config.debug("*triggerWorker* worker not found",e)}triggerAllWorkers(){this.config.debug("*triggerAllWorkers*");for(const e of this.workerByIdMap.values())this.executeWorkerRequest(e)}enableWorkers(e){this.config.debug("*enable*",e),this.enabledGroups.add(e),this.groups.has(e)&&this.timer==null&&this.startTimer()}disableWorkers(e){if(this.config.debug("*disable*",e),this.config.maxParallelWorkers!=null){const i=[...this.enabledGroups].reduce((n,c)=>{var a;return n+(((a=this.groups.get(c))==null?void 0:a.size)??0)},0);if(i<=this.config.maxParallelWorkers){this.config.debug("*disable* skipped, parallel workers within limit",{totalEnabledWorkers:i,maxParallelWorkers:this.config.maxParallelWorkers});return}}this.enabledGroups.delete(e);const r=this.groups.size>0&&[...this.workerByIdMap.values()].some(i=>i.maxLifeTimeMillis!=null);this.enabledGroups.size<1&&!r&&this.stopTimer()}destroy(){this.config.debug("*destroy*"),this.groups.clear(),this.stopTimer()}}function T(s){return new y(s)}exports.HangfirePoller=y;exports.default=T;
1
+ "use strict";var k=Object.defineProperty;var W=(c,e,r)=>e in c?k(c,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):c[e]=r;var l=(c,e,r)=>(W(c,typeof e!="symbol"?e+"":e,r),r);Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const E=require("axios"),u=require("@ancon/wildcat-types"),d=require("../error/createCodedError.js"),R=require("../error/isNotFoundError.js"),g=require("../error/serializeError.js"),w=require("../error/isErrorWithStatusCode.js"),y=require("../api/isNoContentResponse.js"),I=require("../api/isPartialContentResponse.js"),m=require("../shared/wait.js");require("../error/isCodedError.js");require("../error/isAPIError.js");const v=15e3,M=500,b=2;class q{constructor(e){l(this,"activeRequests",new Map);l(this,"forcedRequests",new Set);l(this,"enabledGroups",new Set);l(this,"groups",new Map);l(this,"workerByIdMap",new Map);l(this,"timer",null);l(this,"config",{pollerInterval:5e3,maxGroupsSize:50,maxWorkersSize:20,responseInterceptor:void 0,responseRejectionInterceptor:void 0,getIsNetworkOnline(){return!0},async getRequestHeaders(){return{}},debug(...e){console.log(...e)}});l(this,"axiosInstance");this.config={...this.config,...e??null},this.config.requestTimeout=Math.max(this.config.requestTimeout??v,this.config.pollerInterval),this.axiosInstance=E.create(),(this.config.responseInterceptor||this.config.responseRejectionInterceptor)&&this.axiosInstance.interceptors.response.use(this.config.responseInterceptor,this.config.responseRejectionInterceptor)}stopTimer(){this.timer!=null&&(this.config.debug("*stop timer*"),clearInterval(this.timer),this.timer=null)}startTimer(){if(this.timer!=null)throw d(new Error("Timer already started"),u.ErrorCode.GenericUnexpectedHangfireError);this.config.debug("*start timer*"),this.timer=setInterval(()=>{this.config.debug("*tick*",{activeRequests:this.activeRequests.size,enabledGroups:this.enabledGroups.size,groups:this.groups}),this.enabledGroups.forEach(e=>{const r=this.groups.get(e);r&&r.forEach(t=>{this.executeWorkerRequest(t)})}),this.groups.forEach((e,r)=>{this.enabledGroups.has(r)||e.forEach(t=>{this.isWorkerExpired(t)&&this.executeWorkerRequest(t)})}),this.groups.size<1&&this.stopTimer()},this.config.pollerInterval)}async waitForNetworkOnline(e=5){let r=0;for(;!this.config.getIsNetworkOnline()&&r<e+1;)r+=1,await m(1e3)}isWorkerExpired(e){return e.maxLifeTimeMillis!=null&&Date.now()-e.createdAt>e.maxLifeTimeMillis}logCallbackError(e,r){var t,i;(i=this.config.logger)==null||i.warn(`Hangfire worker onFinished callback error: ${JSON.stringify({groupId:e.groupId,workerId:e.workerId,error:g(r)})}`,{CorrelationId:(t=e.headers)==null?void 0:t["x-correlation-id"]})}settleWorker(e,r,t,i){var o;if(!e.isSettled){e.isSettled=!0,r==="resolve"?e.resolve(t):e.reject(t);try{const s=(o=e.onFinished)==null?void 0:o.call(e,i);s instanceof Promise&&s.catch(h=>this.logCallbackError(e,h))}catch(s){this.logCallbackError(e,s)}this.deleteWorker(e.groupId,e.workerId)}}rejectExpiredWorker(e){var t,i;this.config.debug("*executeWorkerRequest* worker lifetime exceeded, forcing exit",e.workerId),(i=this.config.logger)==null||i.warn(`Hangfire worker lifetime exceeded: ${JSON.stringify({groupId:e.groupId,workerId:e.workerId,maxLifeTimeMillis:e.maxLifeTimeMillis})}`,{CorrelationId:(t=e.headers)==null?void 0:t["x-correlation-id"]});const r=g(d(new Error("The Hangfire worker lifetime has been exceeded"),u.ErrorCode.GenericUnexpectedHangfireError));this.settleWorker(e,"reject",r,!0)}async requestWorkerStatus(e,r){var i,o,s;let t=0;for(;t<=b;){let h=(i=e.headers)==null?void 0:i["x-correlation-id"];try{await this.waitForNetworkOnline(),this.config.debug("*executeWorkerRequest* request",{isNetworkOnline:this.config.getIsNetworkOnline()});const n={...await this.config.getRequestHeaders(),...e.headers};h=n["x-correlation-id"];const a=await this.axiosInstance({url:e.url,method:"get",timeout:this.config.requestTimeout,headers:n});if(e.isSettled)return;y(a)?this.isWorkerExpired(e)&&this.rejectExpiredWorker(e):I(a)?await((o=e.onPartialContent)==null?void 0:o.call(e,a)):this.settleWorker(e,"resolve",a,r);return}catch(n){if(e.isSettled)return;if(w(n)){if(R(n)){this.config.debug("*executeWorkerRequest* worker is expired or invalid",e.workerId);const a=g(d(new Error("The Hangfire worker is expired or invalid"),u.ErrorCode.HangfireWorkerExpiredOrInvalid));this.settleWorker(e,"reject",a,r)}else this.settleWorker(e,"reject",n,r);return}if(this.config.debug("*executeWorkerRequest* not an api error"),(s=this.config.logger)==null||s.warn(`Hangfire worker request error: ${JSON.stringify({attempt:t,groupId:e.groupId,workerId:e.workerId,error:g(n)})}`,{CorrelationId:h}),r&&t<b)t+=1,this.config.debug(`*executeWorkerRequest* retrying non-API error (attempt ${t})`),await m(M);else{this.isWorkerExpired(e)&&this.rejectExpiredWorker(e);return}}}}async executeWorkerRequest(e,r=!1){const{workerId:t}=e;if(e.isSettled)return;const i=this.activeRequests.get(t)??0,o=i>0&&(!r||this.forcedRequests.has(t));if(this.config.debug("*executeWorkerRequest*",{workerId:t,isBusy:o,force:r}),o){this.config.debug("*executeWorkerRequest* worker is busy",t);return}this.activeRequests.set(t,i+1),r&&this.forcedRequests.add(t);try{await this.requestWorkerStatus(e,r)}finally{const s=(this.activeRequests.get(t)??1)-1;s>0?this.activeRequests.set(t,s):this.activeRequests.delete(t),r&&this.forcedRequests.delete(t)}}deleteWorker(e,r){this.workerByIdMap.delete(r),this.activeRequests.delete(r),this.forcedRequests.delete(r);const t=this.groups.get(e);t&&(this.config.debug("*delete worker*",t.size,t),t.delete(r),t.size<1&&(this.groups.delete(e),this.enabledGroups.delete(e),this.enabledGroups.size<1&&this.stopTimer()))}createWorker(e,{workerId:r,url:t,headers:i,onFinished:o,onPartialContent:s,maxLifeTimeMillis:h}){if(!this.groups.has(e)){if(this.groups.size>=this.config.maxGroupsSize){const a=d(new Error("Max number of Hangfire groups exceeded"),u.ErrorCode.GenericUnexpectedHangfireError,{count:this.groups.size});throw this.destroy(),a}this.groups.set(e,new Map)}const n=this.groups.get(e);return new Promise((a,f)=>{if(n.has(r))throw d(new Error("Duplicate Hangfire worker ID"),u.ErrorCode.GenericUnexpectedHangfireError);if(n.size>=this.config.maxWorkersSize){const x=d(new Error("Max number of Hangfire workers in group exceeded"),u.ErrorCode.GenericUnexpectedHangfireError,{groupId:e,count:n.size});throw f(x),this.destroy(),x}const p={workerId:r,groupId:e,url:t,resolve:a,reject:f,headers:i,onFinished:o,onPartialContent:s,createdAt:Date.now(),maxLifeTimeMillis:h};n.set(r,p),this.workerByIdMap.set(r,p),this.enabledGroups.has(e)||this.enabledGroups.add(e),this.timer==null&&this.enabledGroups.size>0&&this.startTimer()})}triggerWorker(e){this.config.debug("*triggerWorker*");const r=this.workerByIdMap.get(e);r?this.executeWorkerRequest(r,!0):this.config.debug("*triggerWorker* worker not found",e)}triggerAllWorkers(e){const r=(e==null?void 0:e.force)??!1;this.config.debug("*triggerAllWorkers*",{force:r});for(const t of this.workerByIdMap.values())this.executeWorkerRequest(t,r)}enableWorkers(e){this.config.debug("*enable*",e),this.enabledGroups.add(e),this.groups.has(e)&&this.timer==null&&this.startTimer()}disableWorkers(e){if(this.config.debug("*disable*",e),this.config.maxParallelWorkers!=null){const t=[...this.enabledGroups].reduce((i,o)=>{var s;return i+(((s=this.groups.get(o))==null?void 0:s.size)??0)},0);if(t<=this.config.maxParallelWorkers){this.config.debug("*disable* skipped, parallel workers within limit",{totalEnabledWorkers:t,maxParallelWorkers:this.config.maxParallelWorkers});return}}this.enabledGroups.delete(e);const r=this.groups.size>0&&[...this.workerByIdMap.values()].some(t=>t.maxLifeTimeMillis!=null);this.enabledGroups.size<1&&!r&&this.stopTimer()}destroy(){var r;this.config.debug("*destroy*");const e=[...this.workerByIdMap.values()];this.groups.clear(),this.workerByIdMap.clear(),this.enabledGroups.clear(),this.activeRequests.clear(),this.forcedRequests.clear(),this.stopTimer(),e.length>0&&((r=this.config.logger)==null||r.warn(`Hangfire poller destroyed with pending workers: ${JSON.stringify({workerIds:e.map(t=>t.workerId)})}`)),e.forEach(t=>{this.settleWorker(t,"reject",g(d(new Error("The Hangfire poller was destroyed"),u.ErrorCode.HangfireWorkerExpiredOrInvalid)),!0)})}}function T(c){return new q(c)}exports.HangfirePoller=q;exports.default=T;
@@ -1,29 +1,32 @@
1
1
  var k = Object.defineProperty;
2
- var E = (s, e, i) => e in s ? k(s, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : s[e] = i;
3
- var o = (s, e, i) => (E(s, typeof e != "symbol" ? e + "" : e, i), i);
4
- import y from "axios";
5
- import { ErrorCode as d } from "@ancon/wildcat-types";
6
- import g from "../error/createCodedError.mjs";
7
- import G from "../error/isNotFoundError.mjs";
8
- import x from "../error/serializeError.mjs";
9
- import z from "../error/isErrorWithStatusCode.mjs";
10
- import R from "../api/isNoContentResponse.mjs";
11
- import q from "../api/isPartialContentResponse.mjs";
12
- import I from "../shared/wait.mjs";
2
+ var W = (c, e, r) => e in c ? k(c, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : c[e] = r;
3
+ var l = (c, e, r) => (W(c, typeof e != "symbol" ? e + "" : e, r), r);
4
+ import E from "axios";
5
+ import { ErrorCode as h } from "@ancon/wildcat-types";
6
+ import u from "../error/createCodedError.mjs";
7
+ import q from "../error/isNotFoundError.mjs";
8
+ import g from "../error/serializeError.mjs";
9
+ import R from "../error/isErrorWithStatusCode.mjs";
10
+ import I from "../api/isNoContentResponse.mjs";
11
+ import w from "../api/isPartialContentResponse.mjs";
12
+ import x from "../shared/wait.mjs";
13
13
  import "../error/isCodedError.mjs";
14
14
  import "../error/isAPIError.mjs";
15
- class w {
15
+ const y = 15e3, v = 500, b = 2;
16
+ class G {
16
17
  constructor(e) {
17
- /** Workers currently executing a request */
18
- o(this, "busyWorkers", /* @__PURE__ */ new Set());
18
+ /** Number of status requests in flight, by worker ID */
19
+ l(this, "activeRequests", /* @__PURE__ */ new Map());
20
+ /** Workers with a force-triggered request in flight */
21
+ l(this, "forcedRequests", /* @__PURE__ */ new Set());
19
22
  /** Groups with enabled workers */
20
- o(this, "enabledGroups", /* @__PURE__ */ new Set());
23
+ l(this, "enabledGroups", /* @__PURE__ */ new Set());
21
24
  /** Workers are grouped by entity ID (check ID or order ID) */
22
- o(this, "groups", /* @__PURE__ */ new Map());
25
+ l(this, "groups", /* @__PURE__ */ new Map());
23
26
  /** Workers mapped by worker ID */
24
- o(this, "workerByIdMap", /* @__PURE__ */ new Map());
25
- o(this, "timer", null);
26
- o(this, "config", {
27
+ l(this, "workerByIdMap", /* @__PURE__ */ new Map());
28
+ l(this, "timer", null);
29
+ l(this, "config", {
27
30
  pollerInterval: 5e3,
28
31
  maxGroupsSize: 50,
29
32
  maxWorkersSize: 20,
@@ -39,11 +42,14 @@ class w {
39
42
  console.log(...e);
40
43
  }
41
44
  });
42
- o(this, "axiosInstance");
45
+ l(this, "axiosInstance");
43
46
  this.config = {
44
47
  ...this.config,
45
48
  ...e ?? null
46
- }, this.axiosInstance = y.create(), (this.config.responseInterceptor || this.config.responseRejectionInterceptor) && this.axiosInstance.interceptors.response.use(
49
+ }, this.config.requestTimeout = Math.max(
50
+ this.config.requestTimeout ?? y,
51
+ this.config.pollerInterval
52
+ ), this.axiosInstance = E.create(), (this.config.responseInterceptor || this.config.responseRejectionInterceptor) && this.axiosInstance.interceptors.response.use(
47
53
  this.config.responseInterceptor,
48
54
  this.config.responseRejectionInterceptor
49
55
  );
@@ -53,179 +59,250 @@ class w {
53
59
  }
54
60
  startTimer() {
55
61
  if (this.timer != null)
56
- throw g(
62
+ throw u(
57
63
  new Error("Timer already started"),
58
- d.GenericUnexpectedHangfireError
64
+ h.GenericUnexpectedHangfireError
59
65
  );
60
66
  this.config.debug("*start timer*"), this.timer = setInterval(() => {
61
67
  this.config.debug("*tick*", {
62
- busyWorkers: this.busyWorkers.size,
68
+ activeRequests: this.activeRequests.size,
63
69
  enabledGroups: this.enabledGroups.size,
64
70
  groups: this.groups
65
71
  }), this.enabledGroups.forEach((e) => {
66
- const i = this.groups.get(e);
67
- i && i.forEach((t) => this.executeWorkerRequest(t));
68
- }), this.groups.forEach((e, i) => {
69
- this.enabledGroups.has(i) || e.forEach((t) => {
70
- t.maxLifeTimeMillis != null && Date.now() - t.createdAt > t.maxLifeTimeMillis && this.executeWorkerRequest(t);
72
+ const r = this.groups.get(e);
73
+ r && r.forEach((t) => {
74
+ this.executeWorkerRequest(t);
75
+ });
76
+ }), this.groups.forEach((e, r) => {
77
+ this.enabledGroups.has(r) || e.forEach((t) => {
78
+ this.isWorkerExpired(t) && this.executeWorkerRequest(t);
71
79
  });
72
80
  }), this.groups.size < 1 && this.stopTimer();
73
81
  }, this.config.pollerInterval);
74
82
  }
75
83
  async waitForNetworkOnline(e = 5) {
76
- let i = 0;
77
- for (; !this.config.getIsNetworkOnline() && i < e + 1; )
78
- i += 1, await I(1e3);
84
+ let r = 0;
85
+ for (; !this.config.getIsNetworkOnline() && r < e + 1; )
86
+ r += 1, await x(1e3);
79
87
  }
80
- async executeWorkerRequest(e, i = !1) {
81
- var h, c, f, p, m, W;
82
- const t = e.maxLifeTimeMillis != null && Date.now() - e.createdAt > e.maxLifeTimeMillis, n = !i && !t && this.busyWorkers.has(e.workerId);
83
- if (this.config.debug("*executeWorkerRequest*", {
84
- workerId: e.workerId,
85
- isBusy: n,
86
- force: i
87
- }), n) {
88
- this.config.debug(
89
- "*executeWorkerRequest* worker is busy",
90
- e.workerId
91
- );
92
- return;
88
+ isWorkerExpired(e) {
89
+ return e.maxLifeTimeMillis != null && Date.now() - e.createdAt > e.maxLifeTimeMillis;
90
+ }
91
+ logCallbackError(e, r) {
92
+ var t, i;
93
+ (i = this.config.logger) == null || i.warn(
94
+ `Hangfire worker onFinished callback error: ${JSON.stringify({
95
+ groupId: e.groupId,
96
+ workerId: e.workerId,
97
+ error: g(r)
98
+ })}`,
99
+ {
100
+ CorrelationId: (t = e.headers) == null ? void 0 : t["x-correlation-id"]
101
+ }
102
+ );
103
+ }
104
+ /**
105
+ * Resolve or reject a worker exactly once, call its `onFinished` callback
106
+ * and remove it from the poller
107
+ */
108
+ settleWorker(e, r, t, i) {
109
+ var o;
110
+ if (!e.isSettled) {
111
+ e.isSettled = !0, r === "resolve" ? e.resolve(t) : e.reject(t);
112
+ try {
113
+ const s = (o = e.onFinished) == null ? void 0 : o.call(e, i);
114
+ s instanceof Promise && s.catch((d) => this.logCallbackError(e, d));
115
+ } catch (s) {
116
+ this.logCallbackError(e, s);
117
+ }
118
+ this.deleteWorker(e.groupId, e.workerId);
93
119
  }
94
- this.busyWorkers.add(e.workerId), await this.waitForNetworkOnline(), this.config.debug("*executeWorkerRequest* request", {
95
- isNetworkOnline: this.config.getIsNetworkOnline()
96
- });
97
- const u = {
98
- ...await this.config.getRequestHeaders(),
99
- ...e.headers
100
- }, a = 2;
101
- let l = 0;
102
- for (; l <= a; )
120
+ }
121
+ rejectExpiredWorker(e) {
122
+ var t, i;
123
+ this.config.debug(
124
+ "*executeWorkerRequest* worker lifetime exceeded, forcing exit",
125
+ e.workerId
126
+ ), (i = this.config.logger) == null || i.warn(
127
+ `Hangfire worker lifetime exceeded: ${JSON.stringify({
128
+ groupId: e.groupId,
129
+ workerId: e.workerId,
130
+ maxLifeTimeMillis: e.maxLifeTimeMillis
131
+ })}`,
132
+ {
133
+ CorrelationId: (t = e.headers) == null ? void 0 : t["x-correlation-id"]
134
+ }
135
+ );
136
+ const r = g(
137
+ u(
138
+ new Error("The Hangfire worker lifetime has been exceeded"),
139
+ h.GenericUnexpectedHangfireError
140
+ )
141
+ );
142
+ this.settleWorker(e, "reject", r, !0);
143
+ }
144
+ /**
145
+ * Request the worker status. Never throws: every failure either settles the
146
+ * worker or is logged, and the worker is polled again later.
147
+ */
148
+ async requestWorkerStatus(e, r) {
149
+ var i, o, s;
150
+ let t = 0;
151
+ for (; t <= b; ) {
152
+ let d = (i = e.headers) == null ? void 0 : i["x-correlation-id"];
103
153
  try {
104
- const r = await this.axiosInstance({
154
+ await this.waitForNetworkOnline(), this.config.debug("*executeWorkerRequest* request", {
155
+ isNetworkOnline: this.config.getIsNetworkOnline()
156
+ });
157
+ const n = {
158
+ ...await this.config.getRequestHeaders(),
159
+ ...e.headers
160
+ };
161
+ d = n["x-correlation-id"];
162
+ const a = await this.axiosInstance({
105
163
  url: e.url,
106
164
  method: "get",
107
- timeout: this.config.pollerInterval,
108
- headers: u
165
+ timeout: this.config.requestTimeout,
166
+ headers: n
109
167
  });
110
- if (!R(r))
111
- q(r) ? (await ((h = e.onPartialContent) == null ? void 0 : h.call(e, r)), this.busyWorkers.delete(e.workerId)) : (e.resolve(r), (c = e.onFinished) == null || c.call(e, i), this.deleteWorker(e.groupId, e.workerId));
112
- else if (t) {
113
- this.config.debug(
114
- "*executeWorkerRequest* worker lifetime exceeded, forcing exit",
115
- e.workerId
116
- );
117
- const b = x(
118
- g(
119
- new Error("The Hangfire worker lifetime has been exceeded"),
120
- d.GenericUnexpectedHangfireError
121
- )
122
- );
123
- e.reject(b), (f = e.onFinished) == null || f.call(e, !0), this.deleteWorker(e.groupId, e.workerId);
124
- } else
125
- this.busyWorkers.delete(e.workerId);
126
- break;
127
- } catch (r) {
128
- if (z(r)) {
129
- if (G(r)) {
168
+ if (e.isSettled)
169
+ return;
170
+ I(a) ? this.isWorkerExpired(e) && this.rejectExpiredWorker(e) : w(a) ? await ((o = e.onPartialContent) == null ? void 0 : o.call(e, a)) : this.settleWorker(e, "resolve", a, r);
171
+ return;
172
+ } catch (n) {
173
+ if (e.isSettled)
174
+ return;
175
+ if (R(n)) {
176
+ if (q(n)) {
130
177
  this.config.debug(
131
178
  "*executeWorkerRequest* worker is expired or invalid",
132
179
  e.workerId
133
180
  );
134
- const b = x(
135
- g(
181
+ const a = g(
182
+ u(
136
183
  new Error("The Hangfire worker is expired or invalid"),
137
- d.HangfireWorkerExpiredOrInvalid
184
+ h.HangfireWorkerExpiredOrInvalid
138
185
  )
139
186
  );
140
- e.reject(b), (p = e.onFinished) == null || p.call(e, i);
187
+ this.settleWorker(e, "reject", a, r);
141
188
  } else
142
- e.reject(r), (m = e.onFinished) == null || m.call(e, i);
143
- this.deleteWorker(e.groupId, e.workerId);
144
- break;
145
- } else if (this.config.debug("*executeWorkerRequest* not an api error"), (W = this.config.logger) == null || W.warn(
189
+ this.settleWorker(e, "reject", n, r);
190
+ return;
191
+ }
192
+ if (this.config.debug("*executeWorkerRequest* not an api error"), (s = this.config.logger) == null || s.warn(
146
193
  `Hangfire worker request error: ${JSON.stringify({
147
- attempt: l,
194
+ attempt: t,
148
195
  groupId: e.groupId,
149
196
  workerId: e.workerId,
150
- error: x(r)
197
+ error: g(n)
151
198
  })}`,
152
199
  {
153
- CorrelationId: u["x-correlation-id"]
200
+ CorrelationId: d
154
201
  }
155
- ), i && l < a)
156
- l += 1, this.config.debug(
157
- `*executeWorkerRequest* retrying non-API error (attempt ${l})`
158
- ), await I(500);
202
+ ), r && t < b)
203
+ t += 1, this.config.debug(
204
+ `*executeWorkerRequest* retrying non-API error (attempt ${t})`
205
+ ), await x(v);
159
206
  else {
160
- this.busyWorkers.delete(e.workerId);
161
- break;
207
+ this.isWorkerExpired(e) && this.rejectExpiredWorker(e);
208
+ return;
162
209
  }
163
210
  }
211
+ }
212
+ }
213
+ async executeWorkerRequest(e, r = !1) {
214
+ const { workerId: t } = e;
215
+ if (e.isSettled)
216
+ return;
217
+ const i = this.activeRequests.get(t) ?? 0, o = i > 0 && (!r || this.forcedRequests.has(t));
218
+ if (this.config.debug("*executeWorkerRequest*", {
219
+ workerId: t,
220
+ isBusy: o,
221
+ force: r
222
+ }), o) {
223
+ this.config.debug("*executeWorkerRequest* worker is busy", t);
224
+ return;
225
+ }
226
+ this.activeRequests.set(t, i + 1), r && this.forcedRequests.add(t);
227
+ try {
228
+ await this.requestWorkerStatus(e, r);
229
+ } finally {
230
+ const s = (this.activeRequests.get(t) ?? 1) - 1;
231
+ s > 0 ? this.activeRequests.set(t, s) : this.activeRequests.delete(t), r && this.forcedRequests.delete(t);
232
+ }
164
233
  }
165
- deleteWorker(e, i) {
234
+ deleteWorker(e, r) {
235
+ this.workerByIdMap.delete(r), this.activeRequests.delete(r), this.forcedRequests.delete(r);
166
236
  const t = this.groups.get(e);
167
- t && (this.config.debug("*delete worker*", t.size, t), t.delete(i), this.workerByIdMap.delete(i), this.busyWorkers.delete(i), t.size < 1 && (this.groups.delete(e), this.enabledGroups.delete(e), this.enabledGroups.size < 1 && this.stopTimer()));
237
+ t && (this.config.debug("*delete worker*", t.size, t), t.delete(r), t.size < 1 && (this.groups.delete(e), this.enabledGroups.delete(e), this.enabledGroups.size < 1 && this.stopTimer()));
168
238
  }
169
239
  createWorker(e, {
170
- workerId: i,
240
+ workerId: r,
171
241
  url: t,
172
- headers: n,
173
- onFinished: u,
174
- onPartialContent: a,
175
- maxLifeTimeMillis: l
242
+ headers: i,
243
+ onFinished: o,
244
+ onPartialContent: s,
245
+ maxLifeTimeMillis: d
176
246
  }) {
177
247
  if (!this.groups.has(e)) {
178
248
  if (this.groups.size >= this.config.maxGroupsSize) {
179
- const c = g(
249
+ const a = u(
180
250
  new Error("Max number of Hangfire groups exceeded"),
181
- d.GenericUnexpectedHangfireError,
251
+ h.GenericUnexpectedHangfireError,
182
252
  { count: this.groups.size }
183
253
  );
184
- throw this.destroy(), c;
254
+ throw this.destroy(), a;
185
255
  }
186
256
  this.groups.set(e, /* @__PURE__ */ new Map());
187
257
  }
188
- const h = this.groups.get(e);
189
- return new Promise((c, f) => {
190
- if (h.has(i))
191
- throw g(
258
+ const n = this.groups.get(e);
259
+ return new Promise((a, f) => {
260
+ if (n.has(r))
261
+ throw u(
192
262
  new Error("Duplicate Hangfire worker ID"),
193
- d.GenericUnexpectedHangfireError
263
+ h.GenericUnexpectedHangfireError
194
264
  );
195
- if (h.size >= this.config.maxWorkersSize) {
196
- const m = g(
265
+ if (n.size >= this.config.maxWorkersSize) {
266
+ const m = u(
197
267
  new Error("Max number of Hangfire workers in group exceeded"),
198
- d.GenericUnexpectedHangfireError,
199
- { groupId: e, count: h.size }
268
+ h.GenericUnexpectedHangfireError,
269
+ { groupId: e, count: n.size }
200
270
  );
201
271
  throw f(m), this.destroy(), m;
202
272
  }
203
273
  const p = {
204
- workerId: i,
274
+ workerId: r,
205
275
  groupId: e,
206
276
  url: t,
207
- resolve: c,
277
+ resolve: a,
208
278
  reject: f,
209
- headers: n,
210
- onFinished: u,
211
- onPartialContent: a,
279
+ headers: i,
280
+ onFinished: o,
281
+ onPartialContent: s,
212
282
  createdAt: Date.now(),
213
- maxLifeTimeMillis: l
283
+ maxLifeTimeMillis: d
214
284
  };
215
- h.set(i, p), this.workerByIdMap.set(i, p), this.enabledGroups.has(e) || this.enabledGroups.add(e), this.timer == null && this.enabledGroups.size > 0 && this.startTimer();
285
+ n.set(r, p), this.workerByIdMap.set(r, p), this.enabledGroups.has(e) || this.enabledGroups.add(e), this.timer == null && this.enabledGroups.size > 0 && this.startTimer();
216
286
  });
217
287
  }
218
288
  /** Trigger a worker to execute immediately, ignoring busy state */
219
289
  triggerWorker(e) {
220
290
  this.config.debug("*triggerWorker*");
221
- const i = this.workerByIdMap.get(e);
222
- i ? this.executeWorkerRequest(i, !0) : this.config.debug("*triggerWorker* worker not found", e);
291
+ const r = this.workerByIdMap.get(e);
292
+ r ? this.executeWorkerRequest(r, !0) : this.config.debug("*triggerWorker* worker not found", e);
223
293
  }
224
- /** Trigger all workers to execute immediately */
225
- triggerAllWorkers() {
226
- this.config.debug("*triggerAllWorkers*");
227
- for (const e of this.workerByIdMap.values())
228
- this.executeWorkerRequest(e);
294
+ /**
295
+ * Trigger all workers to execute immediately.
296
+ * Without `force`, workers with a request in flight are skipped. With
297
+ * `force` (e.g. after a reconnect or when the app returns to the
298
+ * foreground), each worker gets one extra request even if another one is
299
+ * still in flight, for example stuck in a retry backoff.
300
+ */
301
+ triggerAllWorkers(e) {
302
+ const r = (e == null ? void 0 : e.force) ?? !1;
303
+ this.config.debug("*triggerAllWorkers*", { force: r });
304
+ for (const t of this.workerByIdMap.values())
305
+ this.executeWorkerRequest(t, r);
229
306
  }
230
307
  /** May be called either before or after `createWorker()` */
231
308
  enableWorkers(e) {
@@ -234,9 +311,9 @@ class w {
234
311
  disableWorkers(e) {
235
312
  if (this.config.debug("*disable*", e), this.config.maxParallelWorkers != null) {
236
313
  const t = [...this.enabledGroups].reduce(
237
- (n, u) => {
238
- var a;
239
- return n + (((a = this.groups.get(u)) == null ? void 0 : a.size) ?? 0);
314
+ (i, o) => {
315
+ var s;
316
+ return i + (((s = this.groups.get(o)) == null ? void 0 : s.size) ?? 0);
240
317
  },
241
318
  0
242
319
  );
@@ -249,17 +326,42 @@ class w {
249
326
  }
250
327
  }
251
328
  this.enabledGroups.delete(e);
252
- const i = this.groups.size > 0 && [...this.workerByIdMap.values()].some((t) => t.maxLifeTimeMillis != null);
253
- this.enabledGroups.size < 1 && !i && this.stopTimer();
329
+ const r = this.groups.size > 0 && [...this.workerByIdMap.values()].some((t) => t.maxLifeTimeMillis != null);
330
+ this.enabledGroups.size < 1 && !r && this.stopTimer();
254
331
  }
332
+ /**
333
+ * Stop polling and reject every pending worker, so that callers awaiting a
334
+ * worker get an error instead of waiting forever.
335
+ */
255
336
  destroy() {
256
- this.config.debug("*destroy*"), this.groups.clear(), this.stopTimer();
337
+ var r;
338
+ this.config.debug("*destroy*");
339
+ const e = [...this.workerByIdMap.values()];
340
+ this.groups.clear(), this.workerByIdMap.clear(), this.enabledGroups.clear(), this.activeRequests.clear(), this.forcedRequests.clear(), this.stopTimer(), e.length > 0 && ((r = this.config.logger) == null || r.warn(
341
+ `Hangfire poller destroyed with pending workers: ${JSON.stringify({
342
+ workerIds: e.map((t) => t.workerId)
343
+ })}`
344
+ )), e.forEach((t) => {
345
+ this.settleWorker(
346
+ t,
347
+ "reject",
348
+ // Reuse the expired-worker code: callers already respond to it by fetching
349
+ // the actual result rather than treating the request as failed.
350
+ g(
351
+ u(
352
+ new Error("The Hangfire poller was destroyed"),
353
+ h.HangfireWorkerExpiredOrInvalid
354
+ )
355
+ ),
356
+ !0
357
+ );
358
+ });
257
359
  }
258
360
  }
259
- function j(s) {
260
- return new w(s);
361
+ function D(c) {
362
+ return new G(c);
261
363
  }
262
364
  export {
263
- w as HangfirePoller,
264
- j as default
365
+ G as HangfirePoller,
366
+ D as default
265
367
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ancon/wildcat-utils",
3
- "version": "1.53.0",
3
+ "version": "1.53.2",
4
4
  "main": "index.js",
5
5
  "module": "index.mjs",
6
6
  "types": "index.d.ts",
@@ -11,10 +11,17 @@ declare class SignalR {
11
11
  private backoff;
12
12
  private reconnectAttempt;
13
13
  private shouldReconnect;
14
+ private isReconnecting;
15
+ private pendingJoins;
14
16
  groups: string[];
15
17
  constructor(options: SignalRConfig);
16
18
  info: (message: string, data?: any) => void;
17
19
  private generateBackoff;
20
+ private getNextBackoffMs;
21
+ /**
22
+ * Keep trying to connect, with backoff, until connected or `disconnect()` is called.
23
+ * Only one reconnect loop runs at a time.
24
+ */
18
25
  private reconnect;
19
26
  private buildInitialListeners;
20
27
  /**
@@ -26,6 +33,14 @@ declare class SignalR {
26
33
  private skipSignal;
27
34
  private enhanceListener;
28
35
  setClientId: (clientId: string | undefined) => void;
36
+ /**
37
+ * Start the hub connection.
38
+ *
39
+ * @param throwError Rethrow a failed start and do nothing else.
40
+ * Without it, a failed start emits `disconnected` and keeps retrying with backoff
41
+ * until it connects or `disconnect()` is called; the returned promise settles
42
+ * when that happens.
43
+ */
29
44
  connect: (throwError?: boolean) => Promise<void>;
30
45
  disconnect: () => Promise<void>;
31
46
  /**
@@ -52,7 +67,12 @@ declare class SignalR {
52
67
  * @param listener Signal listener function
53
68
  */
54
69
  removeSignalListener: (signal: SignalRSignalName, listener: SignalREventListener) => void;
55
- joinGroup: (group: string) => Promise<void>;
70
+ /**
71
+ * Join a group. A group that is already joined, or being joined, is not joined again.
72
+ * @returns Whether the group is joined
73
+ */
74
+ joinGroup: (group: string) => Promise<boolean>;
75
+ private invokeJoinGroup;
56
76
  leaveGroup: (group: string) => Promise<void>;
57
77
  leaveAllGroups: () => Promise<void>;
58
78
  getHubConnectionId: () => string | null;
@@ -1 +1 @@
1
- "use strict";var f=Object.defineProperty;var d=(r,e,t)=>e in r?f(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var n=(r,e,t)=>(d(r,typeof e!="symbol"?e+"":e,t),t);const h=require("@microsoft/signalr"),a=require("@ancon/wildcat-types"),l=require("../shared/wait.js"),u=require("../api/generateBackoffWithEqualJitter.js");require("axios");require("../number/randomIntFromInterval.js");class g{constructor(e){n(this,"internalEvents");n(this,"signalREvents");n(this,"hubConnection");n(this,"clientId");n(this,"logger");n(this,"backoff",[]);n(this,"reconnectAttempt",0);n(this,"shouldReconnect",!0);n(this,"groups");n(this,"info",(e,t)=>{var i;(i=this.logger)==null||i.info(e,t)});n(this,"generateBackoff",()=>{this.backoff=u(50,500,5e3),this.info("Generated new backoff")});n(this,"reconnect",async()=>{if(this.shouldReconnect)try{this.info("Attempting to reconnect...",{attempt:this.reconnectAttempt+1}),await this.connect(!0),this.info("Reconnected"),this.reconnectAttempt=0}catch{if(this.backoff[this.reconnectAttempt]!=null){const t=this.backoff[this.reconnectAttempt];if(t!=null)await l(t);else throw new Error("Could not resolve ms from backoff")}else{this.reconnectAttempt>0&&this.info("Ran out of backoff intervals, restarting backoff..."),this.reconnectAttempt=0,this.generateBackoff();const t=this.backoff[this.reconnectAttempt];if(t!=null)await l(t);else throw new Error("Could not resolve ms from backoff")}this.reconnectAttempt+=1,await this.reconnect()}});n(this,"buildInitialListeners",()=>{this.hubConnection.onclose(()=>{this.groups=[],this.info("Hub connection closed"),this.emitEvent("disconnected"),this.reconnect()}),this.hubConnection.onreconnected(()=>this.emitEvent("reconnected"))});n(this,"emitEvent",(e,...t)=>{if(this.internalEvents.has(e)){this.info(`Emitting event "${e}"`);const i=this.internalEvents.get(e);i&&i.forEach(s=>s(...t))}else this.info(`Failed to emit event "${e}". There were no listeners.`)});n(this,"skipSignal",(e,t)=>{this.info(`Skipped signal "${e}"`,t)});n(this,"enhanceListener",(e,t,i)=>s=>{const o=JSON.parse(s),c=i&&o.clientId&&o.clientId===this.clientId;return c||this.info(`Received signal "${e}"`,s),c?this.skipSignal(e,s):t(o)});n(this,"setClientId",e=>{this.clientId=e});n(this,"connect",async(e=!1)=>{try{this.info("Connecting..."),await this.hubConnection.start(),this.info("Connected"),this.emitEvent("connected")}catch(t){if(this.info("Failed to connect"),e)throw t}});n(this,"disconnect",async()=>{try{this.shouldReconnect=!1,this.info("Disconnecting..."),await this.hubConnection.stop(),this.info("Disconnected"),this.emitEvent("disconnected")}catch{this.info("Failed to disconnect")}});n(this,"addEventListener",(e,t)=>{this.internalEvents.has(e)||this.internalEvents.set(e,[]);const i=this.internalEvents.get(e);return i&&i.push(t),()=>this.removeEventListener(e,t)});n(this,"removeEventListener",(e,t)=>{var i;if(this.internalEvents.has(e)){const s=this.internalEvents.get(e),o=((i=s==null?void 0:s.indexOf)==null?void 0:i.call(s,t))??-1;o>=0?(s.splice(o,1),this.info(`Removed event listener for event "${e}"`)):this.info(`Failed to remove event listener for event "${e}". Specified listener was not found.`)}else this.info(`Failed to remove event listener for event "${e}". Event doesn't have any listeners.`)});n(this,"addSignalListener",(e,t,i=!0)=>{this.signalREvents.has(e)||this.signalREvents.set(e,[]);const s=this.signalREvents.get(e),o=this.enhanceListener(e,t,i);return s&&s.push(o),this.hubConnection.on(e,o),this.info(`Added signal listener for signal "${e}"`),()=>this.removeSignalListener(e,o)});n(this,"removeSignalListener",(e,t)=>{var i;if(this.signalREvents.has(e)){const s=this.signalREvents.get(e),o=((i=s==null?void 0:s.indexOf)==null?void 0:i.call(s,t))??-1;o>=0?(this.hubConnection.off(e,t),s.splice(o,1),this.info(`Removed signal listener for signal "${e}"`)):this.info(`Failed to remove signal listener for signal "${e}". Specified listener was not found.`)}else this.info(`Failed to remove signal listener for signal "${e}". Signal doesn't have any listeners.`)});n(this,"joinGroup",async e=>{this.info(`Joining group "${e}"...`);const t=this.groups.push(e)-1;try{await this.hubConnection.invoke(a.SignalRMethodName.JoinGroup,e),this.info(`Joined group "${e}"`)}catch(i){this.info(`Failed to join group "${e}"`,i),t>=0&&this.groups.splice(t,1)}});n(this,"leaveGroup",async e=>{this.info(`Leaving group "${e}"...`);const t=this.groups.indexOf(e);t>=0&&this.groups.splice(t,1);try{await this.hubConnection.invoke(a.SignalRMethodName.LeaveGroup,e),this.info(`Left group "${e}"`)}catch(i){this.groups.indexOf(e)<0&&this.groups.push(e),this.info(`Failed to leave group "${e}"`,i)}});n(this,"leaveAllGroups",async()=>{this.info(`Leaving all (${this.groups.length}) groups...`);const e=[...this.groups].map(t=>this.leaveGroup(t));await Promise.all(e),this.info("Left all groups")});n(this,"getHubConnectionId",()=>this.hubConnection.connectionId);this.internalEvents=new Map,this.signalREvents=new Map,this.groups=[],e.enableLogging&&(this.logger=e.logger),this.hubConnection=new h.HubConnectionBuilder().withUrl(`${e.signalRApiUrl}/${e.hub}`).configureLogging(h.LogLevel.None).build(),e.serverTimeoutInMilliseconds&&(this.hubConnection.serverTimeoutInMilliseconds=e.serverTimeoutInMilliseconds),this.buildInitialListeners()}}module.exports=g;
1
+ "use strict";var l=Object.defineProperty;var f=(r,e,n)=>e in r?l(r,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):r[e]=n;var t=(r,e,n)=>(f(r,typeof e!="symbol"?e+"":e,n),n);const h=require("@microsoft/signalr"),a=require("@ancon/wildcat-types"),d=require("../shared/wait.js"),u=require("../api/generateBackoffWithEqualJitter.js");require("axios");require("../number/randomIntFromInterval.js");class g{constructor(e){t(this,"internalEvents");t(this,"signalREvents");t(this,"hubConnection");t(this,"clientId");t(this,"logger");t(this,"backoff",[]);t(this,"reconnectAttempt",0);t(this,"shouldReconnect",!0);t(this,"isReconnecting",!1);t(this,"pendingJoins",new Map);t(this,"groups");t(this,"info",(e,n)=>{var i;(i=this.logger)==null||i.info(e,n)});t(this,"generateBackoff",()=>{this.backoff=u(50,500,5e3),this.info("Generated new backoff")});t(this,"getNextBackoffMs",()=>{this.backoff[this.reconnectAttempt]==null&&(this.reconnectAttempt>0&&this.info("Ran out of backoff intervals, restarting backoff..."),this.reconnectAttempt=0,this.generateBackoff());const e=this.backoff[this.reconnectAttempt];if(e==null)throw new Error("Could not resolve ms from backoff");return e});t(this,"reconnect",async()=>{if(!this.isReconnecting){this.isReconnecting=!0;try{for(;this.shouldReconnect;)try{this.info("Attempting to reconnect...",{attempt:this.reconnectAttempt+1}),await this.connect(!0),this.info("Reconnected"),this.reconnectAttempt=0;return}catch{if(!this.shouldReconnect)break;await d(this.getNextBackoffMs()),this.reconnectAttempt+=1}}finally{this.isReconnecting=!1}}});t(this,"buildInitialListeners",()=>{this.hubConnection.onclose(()=>{this.groups=[],this.info("Hub connection closed"),this.emitEvent("disconnected"),this.reconnect()}),this.hubConnection.onreconnected(()=>this.emitEvent("reconnected"))});t(this,"emitEvent",(e,...n)=>{if(this.internalEvents.has(e)){this.info(`Emitting event "${e}"`);const i=this.internalEvents.get(e);i&&i.forEach(s=>s(...n))}else this.info(`Failed to emit event "${e}". There were no listeners.`)});t(this,"skipSignal",(e,n)=>{this.info(`Skipped signal "${e}"`,n)});t(this,"enhanceListener",(e,n,i)=>s=>{const o=JSON.parse(s),c=i&&o.clientId&&o.clientId===this.clientId;return c||this.info(`Received signal "${e}"`,s),c?this.skipSignal(e,s):n(o)});t(this,"setClientId",e=>{this.clientId=e});t(this,"connect",async(e=!1)=>{try{this.info("Connecting..."),await this.hubConnection.start(),this.info("Connected"),this.emitEvent("connected")}catch(n){if(this.info("Failed to connect"),e)throw n;this.shouldReconnect&&(this.emitEvent("disconnected"),await this.reconnect())}});t(this,"disconnect",async()=>{try{this.shouldReconnect=!1,this.info("Disconnecting..."),await this.hubConnection.stop(),this.info("Disconnected"),this.emitEvent("disconnected")}catch{this.info("Failed to disconnect")}});t(this,"addEventListener",(e,n)=>{this.internalEvents.has(e)||this.internalEvents.set(e,[]);const i=this.internalEvents.get(e);return i&&i.push(n),()=>this.removeEventListener(e,n)});t(this,"removeEventListener",(e,n)=>{var i;if(this.internalEvents.has(e)){const s=this.internalEvents.get(e),o=((i=s==null?void 0:s.indexOf)==null?void 0:i.call(s,n))??-1;o>=0?(s.splice(o,1),this.info(`Removed event listener for event "${e}"`)):this.info(`Failed to remove event listener for event "${e}". Specified listener was not found.`)}else this.info(`Failed to remove event listener for event "${e}". Event doesn't have any listeners.`)});t(this,"addSignalListener",(e,n,i=!0)=>{this.signalREvents.has(e)||this.signalREvents.set(e,[]);const s=this.signalREvents.get(e),o=this.enhanceListener(e,n,i);return s&&s.push(o),this.hubConnection.on(e,o),this.info(`Added signal listener for signal "${e}"`),()=>this.removeSignalListener(e,o)});t(this,"removeSignalListener",(e,n)=>{var i;if(this.signalREvents.has(e)){const s=this.signalREvents.get(e),o=((i=s==null?void 0:s.indexOf)==null?void 0:i.call(s,n))??-1;o>=0?(this.hubConnection.off(e,n),s.splice(o,1),this.info(`Removed signal listener for signal "${e}"`)):this.info(`Failed to remove signal listener for signal "${e}". Specified listener was not found.`)}else this.info(`Failed to remove signal listener for signal "${e}". Signal doesn't have any listeners.`)});t(this,"joinGroup",async e=>{const n=this.pendingJoins.get(e);if(n)return n;if(this.groups.includes(e))return!0;const i=this.invokeJoinGroup(e).finally(()=>{this.pendingJoins.delete(e)});return this.pendingJoins.set(e,i),i});t(this,"invokeJoinGroup",async e=>{this.info(`Joining group "${e}"...`),this.groups.push(e);try{return await this.hubConnection.invoke(a.SignalRMethodName.JoinGroup,e),this.info(`Joined group "${e}"`),!0}catch(n){this.info(`Failed to join group "${e}"`,n);const i=this.groups.indexOf(e);return i>=0&&this.groups.splice(i,1),!1}});t(this,"leaveGroup",async e=>{this.info(`Leaving group "${e}"...`);const n=this.groups.indexOf(e);n>=0&&this.groups.splice(n,1);try{await this.hubConnection.invoke(a.SignalRMethodName.LeaveGroup,e),this.info(`Left group "${e}"`)}catch(i){this.groups.indexOf(e)<0&&this.groups.push(e),this.info(`Failed to leave group "${e}"`,i)}});t(this,"leaveAllGroups",async()=>{this.info(`Leaving all (${this.groups.length}) groups...`);const e=[...this.groups].map(n=>this.leaveGroup(n));await Promise.all(e),this.info("Left all groups")});t(this,"getHubConnectionId",()=>this.hubConnection.connectionId);this.internalEvents=new Map,this.signalREvents=new Map,this.groups=[],e.enableLogging&&(this.logger=e.logger),this.hubConnection=new h.HubConnectionBuilder().withUrl(`${e.signalRApiUrl}/${e.hub}`).configureLogging(h.LogLevel.None).build(),e.serverTimeoutInMilliseconds!=null&&(this.hubConnection.serverTimeoutInMilliseconds=e.serverTimeoutInMilliseconds),e.keepAliveIntervalInMilliseconds!=null&&(this.hubConnection.keepAliveIntervalInMilliseconds=e.keepAliveIntervalInMilliseconds),this.buildInitialListeners()}}module.exports=g;
@@ -1,55 +1,64 @@
1
- var f = Object.defineProperty;
2
- var l = (r, e, t) => e in r ? f(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
3
- var n = (r, e, t) => (l(r, typeof e != "symbol" ? e + "" : e, t), t);
4
- import { HubConnectionBuilder as d, LogLevel as u } from "@microsoft/signalr";
1
+ var l = Object.defineProperty;
2
+ var a = (r, e, n) => e in r ? l(r, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : r[e] = n;
3
+ var t = (r, e, n) => (a(r, typeof e != "symbol" ? e + "" : e, n), n);
4
+ import { HubConnectionBuilder as f, LogLevel as d } from "@microsoft/signalr";
5
5
  import { SignalRMethodName as h } from "@ancon/wildcat-types";
6
- import a from "../shared/wait.mjs";
6
+ import u from "../shared/wait.mjs";
7
7
  import g from "../api/generateBackoffWithEqualJitter.mjs";
8
8
  import "axios";
9
9
  import "../number/randomIntFromInterval.mjs";
10
- class $ {
10
+ class w {
11
11
  constructor(e) {
12
- n(this, "internalEvents");
13
- n(this, "signalREvents");
14
- n(this, "hubConnection");
15
- n(this, "clientId");
16
- n(this, "logger");
17
- n(this, "backoff", []);
18
- n(this, "reconnectAttempt", 0);
19
- n(this, "shouldReconnect", !0);
20
- n(this, "groups");
21
- n(this, "info", (e, t) => {
12
+ t(this, "internalEvents");
13
+ t(this, "signalREvents");
14
+ t(this, "hubConnection");
15
+ t(this, "clientId");
16
+ t(this, "logger");
17
+ t(this, "backoff", []);
18
+ t(this, "reconnectAttempt", 0);
19
+ t(this, "shouldReconnect", !0);
20
+ t(this, "isReconnecting", !1);
21
+ t(this, "pendingJoins", /* @__PURE__ */ new Map());
22
+ t(this, "groups");
23
+ t(this, "info", (e, n) => {
22
24
  var i;
23
- (i = this.logger) == null || i.info(e, t);
25
+ (i = this.logger) == null || i.info(e, n);
24
26
  });
25
- n(this, "generateBackoff", () => {
27
+ t(this, "generateBackoff", () => {
26
28
  this.backoff = g(50, 500, 5e3), this.info("Generated new backoff");
27
29
  });
28
- n(this, "reconnect", async () => {
29
- if (this.shouldReconnect)
30
+ t(this, "getNextBackoffMs", () => {
31
+ this.backoff[this.reconnectAttempt] == null && (this.reconnectAttempt > 0 && this.info("Ran out of backoff intervals, restarting backoff..."), this.reconnectAttempt = 0, this.generateBackoff());
32
+ const e = this.backoff[this.reconnectAttempt];
33
+ if (e == null)
34
+ throw new Error("Could not resolve ms from backoff");
35
+ return e;
36
+ });
37
+ /**
38
+ * Keep trying to connect, with backoff, until connected or `disconnect()` is called.
39
+ * Only one reconnect loop runs at a time.
40
+ */
41
+ t(this, "reconnect", async () => {
42
+ if (!this.isReconnecting) {
43
+ this.isReconnecting = !0;
30
44
  try {
31
- this.info("Attempting to reconnect...", {
32
- attempt: this.reconnectAttempt + 1
33
- }), await this.connect(!0), this.info("Reconnected"), this.reconnectAttempt = 0;
34
- } catch {
35
- if (this.backoff[this.reconnectAttempt] != null) {
36
- const t = this.backoff[this.reconnectAttempt];
37
- if (t != null)
38
- await a(t);
39
- else
40
- throw new Error("Could not resolve ms from backoff");
41
- } else {
42
- this.reconnectAttempt > 0 && this.info("Ran out of backoff intervals, restarting backoff..."), this.reconnectAttempt = 0, this.generateBackoff();
43
- const t = this.backoff[this.reconnectAttempt];
44
- if (t != null)
45
- await a(t);
46
- else
47
- throw new Error("Could not resolve ms from backoff");
48
- }
49
- this.reconnectAttempt += 1, await this.reconnect();
45
+ for (; this.shouldReconnect; )
46
+ try {
47
+ this.info("Attempting to reconnect...", {
48
+ attempt: this.reconnectAttempt + 1
49
+ }), await this.connect(!0), this.info("Reconnected"), this.reconnectAttempt = 0;
50
+ return;
51
+ } catch {
52
+ if (!this.shouldReconnect)
53
+ break;
54
+ await u(this.getNextBackoffMs()), this.reconnectAttempt += 1;
55
+ }
56
+ } finally {
57
+ this.isReconnecting = !1;
50
58
  }
59
+ }
51
60
  });
52
- n(this, "buildInitialListeners", () => {
61
+ t(this, "buildInitialListeners", () => {
53
62
  this.hubConnection.onclose(() => {
54
63
  this.groups = [], this.info("Hub connection closed"), this.emitEvent("disconnected"), this.reconnect();
55
64
  }), this.hubConnection.onreconnected(() => this.emitEvent("reconnected"));
@@ -59,33 +68,42 @@ class $ {
59
68
  * @param event Event to emit
60
69
  * @param args Arguments to send to the listeners
61
70
  */
62
- n(this, "emitEvent", (e, ...t) => {
71
+ t(this, "emitEvent", (e, ...n) => {
63
72
  if (this.internalEvents.has(e)) {
64
73
  this.info(`Emitting event "${e}"`);
65
74
  const i = this.internalEvents.get(e);
66
- i && i.forEach((s) => s(...t));
75
+ i && i.forEach((s) => s(...n));
67
76
  } else
68
77
  this.info(`Failed to emit event "${e}". There were no listeners.`);
69
78
  });
70
- n(this, "skipSignal", (e, t) => {
71
- this.info(`Skipped signal "${e}"`, t);
79
+ t(this, "skipSignal", (e, n) => {
80
+ this.info(`Skipped signal "${e}"`, n);
72
81
  });
73
- n(this, "enhanceListener", (e, t, i) => (s) => {
82
+ t(this, "enhanceListener", (e, n, i) => (s) => {
74
83
  const o = JSON.parse(s), c = i && o.clientId && o.clientId === this.clientId;
75
- return c || this.info(`Received signal "${e}"`, s), c ? this.skipSignal(e, s) : t(o);
84
+ return c || this.info(`Received signal "${e}"`, s), c ? this.skipSignal(e, s) : n(o);
76
85
  });
77
- n(this, "setClientId", (e) => {
86
+ t(this, "setClientId", (e) => {
78
87
  this.clientId = e;
79
88
  });
80
- n(this, "connect", async (e = !1) => {
89
+ /**
90
+ * Start the hub connection.
91
+ *
92
+ * @param throwError Rethrow a failed start and do nothing else.
93
+ * Without it, a failed start emits `disconnected` and keeps retrying with backoff
94
+ * until it connects or `disconnect()` is called; the returned promise settles
95
+ * when that happens.
96
+ */
97
+ t(this, "connect", async (e = !1) => {
81
98
  try {
82
99
  this.info("Connecting..."), await this.hubConnection.start(), this.info("Connected"), this.emitEvent("connected");
83
- } catch (t) {
100
+ } catch (n) {
84
101
  if (this.info("Failed to connect"), e)
85
- throw t;
102
+ throw n;
103
+ this.shouldReconnect && (this.emitEvent("disconnected"), await this.reconnect());
86
104
  }
87
105
  });
88
- n(this, "disconnect", async () => {
106
+ t(this, "disconnect", async () => {
89
107
  try {
90
108
  this.shouldReconnect = !1, this.info("Disconnecting..."), await this.hubConnection.stop(), this.info("Disconnected"), this.emitEvent("disconnected");
91
109
  } catch {
@@ -97,20 +115,20 @@ class $ {
97
115
  * @param event Event to listen on
98
116
  * @param listener Event listener function
99
117
  */
100
- n(this, "addEventListener", (e, t) => {
118
+ t(this, "addEventListener", (e, n) => {
101
119
  this.internalEvents.has(e) || this.internalEvents.set(e, []);
102
120
  const i = this.internalEvents.get(e);
103
- return i && i.push(t), () => this.removeEventListener(e, t);
121
+ return i && i.push(n), () => this.removeEventListener(e, n);
104
122
  });
105
123
  /**
106
124
  * Remove internal event listener
107
125
  * @param event Event to stop listening to
108
126
  * @param listener Event listener function
109
127
  */
110
- n(this, "removeEventListener", (e, t) => {
128
+ t(this, "removeEventListener", (e, n) => {
111
129
  var i;
112
130
  if (this.internalEvents.has(e)) {
113
- const s = this.internalEvents.get(e), o = ((i = s == null ? void 0 : s.indexOf) == null ? void 0 : i.call(s, t)) ?? -1;
131
+ const s = this.internalEvents.get(e), o = ((i = s == null ? void 0 : s.indexOf) == null ? void 0 : i.call(s, n)) ?? -1;
114
132
  o >= 0 ? (s.splice(o, 1), this.info(`Removed event listener for event "${e}"`)) : this.info(
115
133
  `Failed to remove event listener for event "${e}". Specified listener was not found.`
116
134
  );
@@ -124,11 +142,11 @@ class $ {
124
142
  * @param signal Signal name
125
143
  * @param listener Signal listener function
126
144
  */
127
- n(this, "addSignalListener", (e, t, i = !0) => {
145
+ t(this, "addSignalListener", (e, n, i = !0) => {
128
146
  this.signalREvents.has(e) || this.signalREvents.set(e, []);
129
147
  const s = this.signalREvents.get(e), o = this.enhanceListener(
130
148
  e,
131
- t,
149
+ n,
132
150
  i
133
151
  );
134
152
  return s && s.push(o), this.hubConnection.on(e, o), this.info(`Added signal listener for signal "${e}"`), () => this.removeSignalListener(e, o);
@@ -138,11 +156,11 @@ class $ {
138
156
  * @param signal Signal name
139
157
  * @param listener Signal listener function
140
158
  */
141
- n(this, "removeSignalListener", (e, t) => {
159
+ t(this, "removeSignalListener", (e, n) => {
142
160
  var i;
143
161
  if (this.signalREvents.has(e)) {
144
- const s = this.signalREvents.get(e), o = ((i = s == null ? void 0 : s.indexOf) == null ? void 0 : i.call(s, t)) ?? -1;
145
- o >= 0 ? (this.hubConnection.off(e, t), s.splice(o, 1), this.info(`Removed signal listener for signal "${e}"`)) : this.info(
162
+ const s = this.signalREvents.get(e), o = ((i = s == null ? void 0 : s.indexOf) == null ? void 0 : i.call(s, n)) ?? -1;
163
+ o >= 0 ? (this.hubConnection.off(e, n), s.splice(o, 1), this.info(`Removed signal listener for signal "${e}"`)) : this.info(
146
164
  `Failed to remove signal listener for signal "${e}". Specified listener was not found.`
147
165
  );
148
166
  } else
@@ -150,34 +168,50 @@ class $ {
150
168
  `Failed to remove signal listener for signal "${e}". Signal doesn't have any listeners.`
151
169
  );
152
170
  });
153
- n(this, "joinGroup", async (e) => {
154
- this.info(`Joining group "${e}"...`);
155
- const t = this.groups.push(e) - 1;
171
+ /**
172
+ * Join a group. A group that is already joined, or being joined, is not joined again.
173
+ * @returns Whether the group is joined
174
+ */
175
+ t(this, "joinGroup", async (e) => {
176
+ const n = this.pendingJoins.get(e);
177
+ if (n)
178
+ return n;
179
+ if (this.groups.includes(e))
180
+ return !0;
181
+ const i = this.invokeJoinGroup(e).finally(() => {
182
+ this.pendingJoins.delete(e);
183
+ });
184
+ return this.pendingJoins.set(e, i), i;
185
+ });
186
+ t(this, "invokeJoinGroup", async (e) => {
187
+ this.info(`Joining group "${e}"...`), this.groups.push(e);
156
188
  try {
157
- await this.hubConnection.invoke(h.JoinGroup, e), this.info(`Joined group "${e}"`);
158
- } catch (i) {
159
- this.info(`Failed to join group "${e}"`, i), t >= 0 && this.groups.splice(t, 1);
189
+ return await this.hubConnection.invoke(h.JoinGroup, e), this.info(`Joined group "${e}"`), !0;
190
+ } catch (n) {
191
+ this.info(`Failed to join group "${e}"`, n);
192
+ const i = this.groups.indexOf(e);
193
+ return i >= 0 && this.groups.splice(i, 1), !1;
160
194
  }
161
195
  });
162
- n(this, "leaveGroup", async (e) => {
196
+ t(this, "leaveGroup", async (e) => {
163
197
  this.info(`Leaving group "${e}"...`);
164
- const t = this.groups.indexOf(e);
165
- t >= 0 && this.groups.splice(t, 1);
198
+ const n = this.groups.indexOf(e);
199
+ n >= 0 && this.groups.splice(n, 1);
166
200
  try {
167
201
  await this.hubConnection.invoke(h.LeaveGroup, e), this.info(`Left group "${e}"`);
168
202
  } catch (i) {
169
203
  this.groups.indexOf(e) < 0 && this.groups.push(e), this.info(`Failed to leave group "${e}"`, i);
170
204
  }
171
205
  });
172
- n(this, "leaveAllGroups", async () => {
206
+ t(this, "leaveAllGroups", async () => {
173
207
  this.info(`Leaving all (${this.groups.length}) groups...`);
174
- const e = [...this.groups].map((t) => this.leaveGroup(t));
208
+ const e = [...this.groups].map((n) => this.leaveGroup(n));
175
209
  await Promise.all(e), this.info("Left all groups");
176
210
  });
177
- n(this, "getHubConnectionId", () => this.hubConnection.connectionId);
178
- this.internalEvents = /* @__PURE__ */ new Map(), this.signalREvents = /* @__PURE__ */ new Map(), this.groups = [], e.enableLogging && (this.logger = e.logger), this.hubConnection = new d().withUrl(`${e.signalRApiUrl}/${e.hub}`).configureLogging(u.None).build(), e.serverTimeoutInMilliseconds && (this.hubConnection.serverTimeoutInMilliseconds = e.serverTimeoutInMilliseconds), this.buildInitialListeners();
211
+ t(this, "getHubConnectionId", () => this.hubConnection.connectionId);
212
+ this.internalEvents = /* @__PURE__ */ new Map(), this.signalREvents = /* @__PURE__ */ new Map(), this.groups = [], e.enableLogging && (this.logger = e.logger), this.hubConnection = new f().withUrl(`${e.signalRApiUrl}/${e.hub}`).configureLogging(d.None).build(), e.serverTimeoutInMilliseconds != null && (this.hubConnection.serverTimeoutInMilliseconds = e.serverTimeoutInMilliseconds), e.keepAliveIntervalInMilliseconds != null && (this.hubConnection.keepAliveIntervalInMilliseconds = e.keepAliveIntervalInMilliseconds), this.buildInitialListeners();
179
213
  }
180
214
  }
181
215
  export {
182
- $ as default
216
+ w as default
183
217
  };
@@ -19,7 +19,7 @@ declare type ReduxHooksArg = Partial<{
19
19
  declare function createUseSignalRHook<RootState = unknown>(signalRConfig: SignalRHookConfig, selectors?: ReduxSelectorsArg<RootState>, actions?: ReduxActionsArg, hooks?: ReduxHooksArg): {
20
20
  useSignalR: (hub: SignalRHubName) => {
21
21
  isHubConnected: boolean;
22
- join: (group: string) => Promise<void>;
22
+ join: (group: string) => Promise<boolean>;
23
23
  leave: (group: string) => Promise<void>;
24
24
  subscribe: (signal: SignalRSignalName, listener: SignalREventListener, shouldSkipOwnClient?: boolean) => (() => void);
25
25
  unsubscribe: (signal: SignalRSignalName, listener: SignalREventListener) => void;
@@ -1 +1 @@
1
- "use strict";const n=require("react"),h=require("react-redux"),C=require("../noop-7ea3b37b.js"),B=a=>Object.freeze(Object.defineProperty({__proto__:null,default:a},Symbol.toStringTag,{value:"Module"}));function F(a,_,g,j){const{logger:q,enableLogging:x}=a;let{signalRApiUrl:I}=a;const{clientIdSelector:D,isSignalROnlineSelector:s}=_??{},{setIsSignalROnlineAction:l}=g??{},{useAppStore:G=h.useStore,useAppDispatch:m=h.useDispatch,useAppSelector:M=h.useSelector}=j??{},b=new Set;function P(o){const[f,z]=n.useState(I),[e,N]=n.useState(null),d=G(),p=m(),E=M(D??(()=>{})),[u,T]=n.useState(!1),[r,v]=n.useState(!1),O=n.useRef(u),S=n.useCallback(()=>{(s==null?void 0:s(d.getState()))===!1&&l&&p(l({isSignalROnline:!0})),r||v(!0)},[p,r,d]),k=S,H=n.useCallback(()=>{(s==null?void 0:s(d.getState()))===!0&&l&&p(l({isSignalROnline:!1})),r&&v(!1)},[p,r,d]),y=n.useCallback(async t=>{e&&e.groups.indexOf(t)<0&&await(e==null?void 0:e.joinGroup(t))},[e]),A=n.useCallback(async t=>{e&&e.groups.indexOf(t)>=0&&await(e==null?void 0:e.leaveGroup(t))},[e]),L=n.useCallback((t,c,i)=>e?e==null?void 0:e.addSignalListener(t,c,i):C.noop,[e]),U=n.useCallback((t,c)=>{e&&e.removeSignalListener(t,c)},[e]),w=n.useCallback(()=>e?e.getHubConnectionId():null,[e]);return n.useEffect(()=>{let t=!1;return Promise.resolve().then(()=>B(require("./SignalR.js"))).then(({default:c})=>{t||N(new c({signalRApiUrl:f,hub:o,logger:q,enableLogging:x}))}),()=>{t=!0}},[o,f]),n.useEffect(()=>{function t(c){z(c)}return b.add(t),()=>{b.delete(t)}},[]),n.useEffect(()=>{O.current=u},[u]),n.useEffect(()=>{const t=e==null?void 0:e.addEventListener("connected",S),c=e==null?void 0:e.addEventListener("reconnected",k),i=e==null?void 0:e.addEventListener("disconnected",H);return e&&!O.current&&T(!0),()=>{t==null||t(),c==null||c(),i==null||i()}},[e,S,H,k]),n.useEffect(()=>{e==null||e.setClientId(E||void 0)},[e,E]),n.useEffect(()=>{async function t(){await(e==null?void 0:e.connect())}u&&t()},[e,u]),n.useEffect(()=>()=>{e==null||e.leaveAllGroups().then(()=>{e==null||e.disconnect()}).catch(C.noop).finally(()=>{e==null||e.disconnect().catch(C.noop)})},[e]),n.useMemo(()=>({isHubConnected:r,join:y,leave:A,subscribe:L,unsubscribe:U,getHubConnectionId:w}),[r,y,A,L,U,w])}return{useSignalR:P,setUrl(o){I=o,b.forEach(f=>{f(o)})}}}module.exports=F;
1
+ "use strict";const n=require("react"),I=require("react-redux"),h=require("../noop-7ea3b37b.js"),J=l=>Object.freeze(Object.defineProperty({__proto__:null,default:l},Symbol.toStringTag,{value:"Module"}));function K(l,_,j,q){const{logger:w,enableLogging:D,serverTimeoutInMilliseconds:M,keepAliveIntervalInMilliseconds:m}=l;let{signalRApiUrl:v}=l;const{clientIdSelector:x,isSignalROnlineSelector:c}=_??{},{setIsSignalROnlineAction:a}=j??{},{useAppStore:G=I.useStore,useAppDispatch:P=I.useDispatch,useAppSelector:T=I.useSelector}=q??{},b=new Set;function z(o){const[f,N]=n.useState(v),[e,B]=n.useState(null),d=G(),p=P(),C=T(x??(()=>{})),[u,F]=n.useState(!1),[r,E]=n.useState(!1),k=n.useRef(u),S=n.useCallback(()=>{(c==null?void 0:c(d.getState()))===!1&&a&&p(a({isSignalROnline:!0})),r||E(!0)},[p,r,d]),A=S,H=n.useCallback(()=>{(c==null?void 0:c(d.getState()))===!0&&a&&p(a({isSignalROnline:!1})),r&&E(!1)},[p,r,d]),O=n.useCallback(async t=>e?e.joinGroup(t):!1,[e]),y=n.useCallback(async t=>{e&&e.groups.indexOf(t)>=0&&await(e==null?void 0:e.leaveGroup(t))},[e]),L=n.useCallback((t,s,i)=>e?e==null?void 0:e.addSignalListener(t,s,i):h.noop,[e]),U=n.useCallback((t,s)=>{e&&e.removeSignalListener(t,s)},[e]),g=n.useCallback(()=>e?e.getHubConnectionId():null,[e]);return n.useEffect(()=>{let t=!1;return Promise.resolve().then(()=>J(require("./SignalR.js"))).then(({default:s})=>{t||B(new s({signalRApiUrl:f,hub:o,logger:w,enableLogging:D,serverTimeoutInMilliseconds:M,keepAliveIntervalInMilliseconds:m}))}),()=>{t=!0}},[o,f]),n.useEffect(()=>{function t(s){N(s)}return b.add(t),()=>{b.delete(t)}},[]),n.useEffect(()=>{k.current=u},[u]),n.useEffect(()=>{const t=e==null?void 0:e.addEventListener("connected",S),s=e==null?void 0:e.addEventListener("reconnected",A),i=e==null?void 0:e.addEventListener("disconnected",H);return e&&!k.current&&F(!0),()=>{t==null||t(),s==null||s(),i==null||i()}},[e,S,H,A]),n.useEffect(()=>{e==null||e.setClientId(C||void 0)},[e,C]),n.useEffect(()=>{async function t(){await(e==null?void 0:e.connect())}u&&t()},[e,u]),n.useEffect(()=>()=>{e==null||e.leaveAllGroups().then(()=>{e==null||e.disconnect()}).catch(h.noop).finally(()=>{e==null||e.disconnect().catch(h.noop)})},[e]),n.useMemo(()=>({isHubConnected:r,join:O,leave:y,subscribe:L,unsubscribe:U,getHubConnectionId:g}),[r,O,y,L,U,g])}return{useSignalR:z,setUrl(o){v=o,b.forEach(f=>{f(o)})}}}module.exports=K;
@@ -1,122 +1,127 @@
1
- import { useState as S, useRef as P, useCallback as c, useEffect as r, useMemo as Q } from "react";
2
- import { useStore as T, useDispatch as V, useSelector as W } from "react-redux";
3
- import { n as I } from "../noop-378485d5.mjs";
4
- function $(v, D, G, g) {
5
- const { logger: j, enableLogging: k } = v;
6
- let { signalRApiUrl: H } = v;
7
- const { clientIdSelector: M, isSignalROnlineSelector: s } = D ?? {}, { setIsSignalROnlineAction: a } = G ?? {}, {
8
- useAppStore: q = T,
9
- useAppDispatch: z = V,
10
- useAppSelector: B = W
11
- } = g ?? {}, b = /* @__PURE__ */ new Set();
12
- function F(i) {
13
- const [l, J] = S(H), [e, K] = S(null), f = q(), p = z(), A = B(M ?? (() => {
14
- })), [u, N] = S(!1), [o, L] = S(!1), U = P(u), h = c(() => {
15
- (s == null ? void 0 : s(f.getState())) === !1 && a && p(a({ isSignalROnline: !0 })), o || L(!0);
16
- }, [p, o, f]), m = h, C = c(() => {
17
- (s == null ? void 0 : s(f.getState())) === !0 && a && p(a({ isSignalROnline: !1 })), o && L(!1);
18
- }, [p, o, f]), O = c(
19
- async (t) => {
20
- e && e.groups.indexOf(t) < 0 && await (e == null ? void 0 : e.joinGroup(t));
21
- },
1
+ import { useState as I, useRef as Q, useCallback as r, useEffect as s, useMemo as V } from "react";
2
+ import { useStore as W, useDispatch as X, useSelector as Y } from "react-redux";
3
+ import { n as h } from "../noop-378485d5.mjs";
4
+ function ee(v, k, D, G) {
5
+ const {
6
+ logger: M,
7
+ enableLogging: j,
8
+ serverTimeoutInMilliseconds: x,
9
+ keepAliveIntervalInMilliseconds: T
10
+ } = v;
11
+ let { signalRApiUrl: A } = v;
12
+ const { clientIdSelector: q, isSignalROnlineSelector: c } = k ?? {}, { setIsSignalROnlineAction: a } = D ?? {}, {
13
+ useAppStore: z = W,
14
+ useAppDispatch: B = X,
15
+ useAppSelector: F = Y
16
+ } = G ?? {}, S = /* @__PURE__ */ new Set();
17
+ function J(i) {
18
+ const [d, K] = I(A), [e, N] = I(null), f = z(), p = B(), H = F(q ?? (() => {
19
+ })), [u, P] = I(!1), [o, m] = I(!1), L = Q(u), b = r(() => {
20
+ (c == null ? void 0 : c(f.getState())) === !1 && a && p(a({ isSignalROnline: !0 })), o || m(!0);
21
+ }, [p, o, f]), U = b, C = r(() => {
22
+ (c == null ? void 0 : c(f.getState())) === !0 && a && p(a({ isSignalROnline: !1 })), o && m(!1);
23
+ }, [p, o, f]), E = r(
24
+ async (t) => e ? e.joinGroup(t) : !1,
22
25
  [e]
23
- ), w = c(
26
+ ), O = r(
24
27
  async (t) => {
25
28
  e && e.groups.indexOf(t) >= 0 && await (e == null ? void 0 : e.leaveGroup(t));
26
29
  },
27
30
  [e]
28
- ), E = c(
29
- (t, n, d) => e ? e == null ? void 0 : e.addSignalListener(
31
+ ), g = r(
32
+ (t, n, l) => e ? e == null ? void 0 : e.addSignalListener(
30
33
  t,
31
34
  n,
32
- d
33
- ) : I,
35
+ l
36
+ ) : h,
34
37
  [e]
35
- ), y = c(
38
+ ), w = r(
36
39
  (t, n) => {
37
40
  e && e.removeSignalListener(t, n);
38
41
  },
39
42
  [e]
40
- ), x = c(() => e ? e.getHubConnectionId() : null, [e]);
41
- return r(() => {
43
+ ), y = r(() => e ? e.getHubConnectionId() : null, [e]);
44
+ return s(() => {
42
45
  let t = !1;
43
46
  return import("./SignalR.mjs").then(({ default: n }) => {
44
- t || K(
47
+ t || N(
45
48
  new n({
46
- signalRApiUrl: l,
49
+ signalRApiUrl: d,
47
50
  hub: i,
48
- logger: j,
49
- enableLogging: k
51
+ logger: M,
52
+ enableLogging: j,
53
+ serverTimeoutInMilliseconds: x,
54
+ keepAliveIntervalInMilliseconds: T
50
55
  })
51
56
  );
52
57
  }), () => {
53
58
  t = !0;
54
59
  };
55
- }, [i, l]), r(() => {
60
+ }, [i, d]), s(() => {
56
61
  function t(n) {
57
- J(n);
62
+ K(n);
58
63
  }
59
- return b.add(t), () => {
60
- b.delete(t);
64
+ return S.add(t), () => {
65
+ S.delete(t);
61
66
  };
62
- }, []), r(() => {
63
- U.current = u;
64
- }, [u]), r(() => {
67
+ }, []), s(() => {
68
+ L.current = u;
69
+ }, [u]), s(() => {
65
70
  const t = e == null ? void 0 : e.addEventListener(
66
71
  "connected",
67
- h
72
+ b
68
73
  ), n = e == null ? void 0 : e.addEventListener(
69
74
  "reconnected",
70
- m
71
- ), d = e == null ? void 0 : e.addEventListener(
75
+ U
76
+ ), l = e == null ? void 0 : e.addEventListener(
72
77
  "disconnected",
73
78
  C
74
79
  );
75
- return e && !U.current && N(!0), () => {
76
- t == null || t(), n == null || n(), d == null || d();
80
+ return e && !L.current && P(!0), () => {
81
+ t == null || t(), n == null || n(), l == null || l();
77
82
  };
78
83
  }, [
79
84
  e,
80
- h,
85
+ b,
81
86
  C,
82
- m
83
- ]), r(() => {
84
- e == null || e.setClientId(A || void 0);
85
- }, [e, A]), r(() => {
87
+ U
88
+ ]), s(() => {
89
+ e == null || e.setClientId(H || void 0);
90
+ }, [e, H]), s(() => {
86
91
  async function t() {
87
92
  await (e == null ? void 0 : e.connect());
88
93
  }
89
94
  u && t();
90
- }, [e, u]), r(
95
+ }, [e, u]), s(
91
96
  () => () => {
92
97
  e == null || e.leaveAllGroups().then(() => {
93
98
  e == null || e.disconnect();
94
- }).catch(I).finally(() => {
95
- e == null || e.disconnect().catch(I);
99
+ }).catch(h).finally(() => {
100
+ e == null || e.disconnect().catch(h);
96
101
  });
97
102
  },
98
103
  [e]
99
- ), Q(
104
+ ), V(
100
105
  () => ({
101
106
  isHubConnected: o,
102
- join: O,
103
- leave: w,
104
- subscribe: E,
105
- unsubscribe: y,
106
- getHubConnectionId: x
107
+ join: E,
108
+ leave: O,
109
+ subscribe: g,
110
+ unsubscribe: w,
111
+ getHubConnectionId: y
107
112
  }),
108
- [o, O, w, E, y, x]
113
+ [o, E, O, g, w, y]
109
114
  );
110
115
  }
111
116
  return {
112
- useSignalR: F,
117
+ useSignalR: J,
113
118
  setUrl(i) {
114
- H = i, b.forEach((l) => {
115
- l(i);
119
+ A = i, S.forEach((d) => {
120
+ d(i);
116
121
  });
117
122
  }
118
123
  };
119
124
  }
120
125
  export {
121
- $ as default
126
+ ee as default
122
127
  };
@@ -12,5 +12,7 @@ export declare type SignalRConfig = {
12
12
  enableLogging?: boolean;
13
13
  /** @default 30000 */
14
14
  serverTimeoutInMilliseconds?: number;
15
+ /** @default 15000 */
16
+ keepAliveIntervalInMilliseconds?: number;
15
17
  };
16
18
  export declare type SignalRHookConfig = Omit<SignalRConfig, 'hub'>;