mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-10 14:20:45 +08:00
Add Telegram and Discord IM channels
This commit is contained in:
parent
7123fce47a
commit
cb74e1c4c7
66 changed files with 8579 additions and 1910 deletions
133
plugin-src/host/channels/shared/connection-supervisor.mjs
Normal file
133
plugin-src/host/channels/shared/connection-supervisor.mjs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
const DEFAULT_RETRY_DELAYS_MS = Object.freeze([250, 1_000, 3_000, 5_000, 10_000, 30_000]);
|
||||
|
||||
function retryDelays(value) {
|
||||
if (!Array.isArray(value) || value.length === 0) return [...DEFAULT_RETRY_DELAYS_MS];
|
||||
const valid = value.filter((delay) => Number.isFinite(delay) && delay >= 0);
|
||||
return valid.length > 0 ? valid : [...DEFAULT_RETRY_DELAYS_MS];
|
||||
}
|
||||
|
||||
export class TokenConnectionSupervisor {
|
||||
#channel;
|
||||
#controller;
|
||||
#harness;
|
||||
#logger;
|
||||
#retryDelays;
|
||||
#healthyIntervalMs;
|
||||
#setTimeout;
|
||||
#clearTimeout;
|
||||
#timer = null;
|
||||
#running = null;
|
||||
#retryIndex = 0;
|
||||
#closed = false;
|
||||
#started = false;
|
||||
#ready;
|
||||
#resolveReady;
|
||||
|
||||
constructor({
|
||||
channel,
|
||||
controller,
|
||||
harness,
|
||||
logger = console,
|
||||
retryDelaysMs,
|
||||
healthyIntervalMs = 15_000,
|
||||
setTimeoutImpl = setTimeout,
|
||||
clearTimeoutImpl = clearTimeout,
|
||||
}) {
|
||||
if (!channel) throw new TypeError('TokenConnectionSupervisor requires a channel');
|
||||
if (!controller || typeof controller.initialize !== 'function' || typeof controller.status !== 'function') {
|
||||
throw new TypeError('TokenConnectionSupervisor requires a controller');
|
||||
}
|
||||
if (!harness || typeof harness.ensureRunning !== 'function') {
|
||||
throw new TypeError('TokenConnectionSupervisor requires a Harness client');
|
||||
}
|
||||
this.#channel = channel;
|
||||
this.#controller = controller;
|
||||
this.#harness = harness;
|
||||
this.#logger = logger;
|
||||
this.#retryDelays = retryDelays(retryDelaysMs);
|
||||
this.#healthyIntervalMs = Number.isFinite(healthyIntervalMs) && healthyIntervalMs >= 0
|
||||
? healthyIntervalMs : 15_000;
|
||||
this.#setTimeout = setTimeoutImpl;
|
||||
this.#clearTimeout = clearTimeoutImpl;
|
||||
this.#ready = new Promise((resolve) => {
|
||||
this.#resolveReady = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
get ready() {
|
||||
return this.#ready;
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.#started || this.#closed) return this;
|
||||
this.#started = true;
|
||||
this.#schedule(0);
|
||||
return this;
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this.#closed) return;
|
||||
this.#closed = true;
|
||||
if (this.#timer !== null) this.#clearTimeout(this.#timer);
|
||||
this.#timer = null;
|
||||
await this.#running?.catch(() => undefined);
|
||||
this.#resolveReady?.(null);
|
||||
this.#resolveReady = null;
|
||||
}
|
||||
|
||||
#schedule(delayMs) {
|
||||
if (this.#closed) return;
|
||||
this.#timer = this.#setTimeout(() => {
|
||||
this.#timer = null;
|
||||
void this.#run();
|
||||
}, delayMs);
|
||||
this.#timer?.unref?.();
|
||||
}
|
||||
|
||||
async #run() {
|
||||
if (this.#closed || this.#running) return;
|
||||
const operation = this.#reconcile();
|
||||
this.#running = operation;
|
||||
try {
|
||||
await operation;
|
||||
} finally {
|
||||
if (this.#running === operation) this.#running = null;
|
||||
}
|
||||
}
|
||||
|
||||
async #reconcile() {
|
||||
try {
|
||||
await this.#harness.ensureRunning();
|
||||
if (this.#closed) return;
|
||||
const status = await this.#controller.initialize();
|
||||
if (this.#closed) return;
|
||||
this.#resolveReady?.(status);
|
||||
this.#resolveReady = null;
|
||||
const { configured, connected } = status.totals;
|
||||
if (connected < configured) {
|
||||
const delay = this.#retryDelays[Math.min(this.#retryIndex, this.#retryDelays.length - 1)];
|
||||
this.#retryIndex += 1;
|
||||
this.#logger.warn?.(
|
||||
`[dsh-im:${this.#channel}] ${connected}/${configured} bots connected; retrying in ${delay}ms`,
|
||||
);
|
||||
this.#schedule(delay);
|
||||
return;
|
||||
}
|
||||
this.#retryIndex = 0;
|
||||
this.#schedule(this.#healthyIntervalMs);
|
||||
} catch (error) {
|
||||
if (this.#closed) return;
|
||||
const delay = this.#retryDelays[Math.min(this.#retryIndex, this.#retryDelays.length - 1)];
|
||||
this.#retryIndex += 1;
|
||||
this.#logger.warn?.(
|
||||
`[dsh-im:${this.#channel}] connection reconciliation failed; retrying in ${delay}ms`,
|
||||
error,
|
||||
);
|
||||
this.#schedule(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createTokenConnectionSupervisor(options) {
|
||||
return new TokenConnectionSupervisor(options);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue