blob: 6b94da16daef0d7a5ab06c1f94a1eb8a551c2bb4 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
import config from "$config";
const mod = (await import("@asoltys/clightning-client")).default;
const { LightningClient } = mod as { LightningClient: any };
class LightningUnavailableError extends Error {
code = "LIGHTNING_UNAVAILABLE" as const;
constructor(msg: string) {
super(msg);
this.name = "LightningUnavailableError";
}
}
function isUnavailable(e: any) {
const code = e?.code ?? e?.errno;
return code === "ENOENT" || code === 2 || code === "ECONNREFUSED";
}
function isSocketDied(e: any) {
const code = e?.code ?? e?.errno;
return code === "EPIPE" || code === "ECONNRESET" || code === "ENOENT" || code === 2;
}
function lightningProxy(rpcPath: string): any {
let client: any = null;
let nextTryAt = 0;
let backoff = 250;
const maxBackoff = 5000;
function ensure() {
if (client) return client;
const now = Date.now();
if (now < nextTryAt) {
throw new LightningUnavailableError(`Lightning not ready at ${rpcPath}`);
}
try {
client = new LightningClient(rpcPath);
backoff = 250;
nextTryAt = 0;
return client;
} catch (e: any) {
nextTryAt = Date.now() + backoff;
backoff = Math.min(maxBackoff, Math.floor(backoff * 1.8));
if (isUnavailable(e)) {
throw new LightningUnavailableError(
`Lightning RPC unavailable at ${rpcPath} (${e?.code ?? e?.errno ?? "error"})`,
);
}
throw e;
}
}
return new Proxy(
{},
{
get(_t, prop: string | symbol) {
if (prop === "toString") return () => "[LightningProxy]";
if (prop === Symbol.toStringTag) return "LightningProxy";
return async (...args: any[]) => {
const c = ensure();
const v = c[prop as any];
if (typeof v !== "function") return v;
try {
return await v.apply(c, args);
} catch (e: any) {
if (isSocketDied(e)) client = null;
throw e;
}
};
},
},
);
}
// Main proxy for short-lived RPC calls (getinfo, invoice, decode, etc.)
const ln = lightningProxy(config.lightning);
export default ln;
// Dedicated proxy for long-polling (waitanyinvoice) — uses its own socket
// so it doesn't block the main connection
export const lnListen = lightningProxy(config.lightning);
export const lnb = config.lightningb ? lightningProxy(config.lightningb) : ln;
export { LightningUnavailableError };
|