reqly-cli 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/tunnel.mjs +60 -10
- package/package.json +1 -1
package/lib/tunnel.mjs
CHANGED
|
@@ -182,8 +182,14 @@ async function registerTunnel(serverUrl, token, name, port, binSlug, passthrough
|
|
|
182
182
|
/**
|
|
183
183
|
* Poll for new captures and forward them.
|
|
184
184
|
* More reliable than SSE through reverse proxies (Traefik, nginx).
|
|
185
|
+
*
|
|
186
|
+
* The server's tunnel registry is in-memory with a short TTL, so after the
|
|
187
|
+
* laptop sleeps (poll loop frozen) or the server restarts, the tunnelId is
|
|
188
|
+
* forgotten and poll returns 404/401. When that happens we re-register via
|
|
189
|
+
* `reregister()` to get a fresh tunnelId and resume — so the tunnel
|
|
190
|
+
* self-heals instead of looping forever on a dead id.
|
|
185
191
|
*/
|
|
186
|
-
async function pollForCaptures(serverUrl, token, tunnelId, localOrigin, pollIntervalMs = 1000) {
|
|
192
|
+
async function pollForCaptures({ serverUrl, token, tunnelId, localOrigin, pollIntervalMs = 1000, reregister }) {
|
|
187
193
|
let aborted = false;
|
|
188
194
|
let lastSeen = new Date().toISOString();
|
|
189
195
|
let consecutiveErrors = 0;
|
|
@@ -209,6 +215,22 @@ async function pollForCaptures(serverUrl, token, tunnelId, localOrigin, pollInte
|
|
|
209
215
|
headers: { Authorization: `Bearer ${token}` },
|
|
210
216
|
});
|
|
211
217
|
|
|
218
|
+
// 404 = server forgot this tunnel (sleep/restart); 401 = token re-auth
|
|
219
|
+
// needed. Either way, re-register to get a fresh tunnelId and resume.
|
|
220
|
+
if (res.status === 404 || res.status === 401) {
|
|
221
|
+
console.log(
|
|
222
|
+
`${DIM}[${timestamp()}]${RESET} ${YELLOW}Tunnel dropped (${res.status}) — reconnecting...${RESET}`
|
|
223
|
+
);
|
|
224
|
+
const fresh = await reregister();
|
|
225
|
+
tunnelId = fresh.tunnelId;
|
|
226
|
+
consecutiveErrors = 0;
|
|
227
|
+
console.log(
|
|
228
|
+
`${DIM}[${timestamp()}]${RESET} ${GREEN}Reconnected.${RESET} ${DIM}Listening for requests...${RESET}`
|
|
229
|
+
);
|
|
230
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
212
234
|
if (!res.ok) {
|
|
213
235
|
throw new Error(`Poll failed (${res.status})`);
|
|
214
236
|
}
|
|
@@ -291,18 +313,45 @@ export async function startTunnel({ port, bin, name, url, token: tokenOverride,
|
|
|
291
313
|
}
|
|
292
314
|
const localOrigin = `http://localhost:${port}`;
|
|
293
315
|
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
316
|
+
// Re-register against the SAME bin slug each time so the public URL stays
|
|
317
|
+
// stable across reconnects. On the first call `bin` may be undefined (the
|
|
318
|
+
// server auto-creates a bin); afterwards we reuse the slug it returned.
|
|
319
|
+
let knownBinSlug = bin;
|
|
320
|
+
async function register() {
|
|
321
|
+
const reg = await registerTunnel(
|
|
299
322
|
serverUrl,
|
|
300
323
|
token,
|
|
301
324
|
name || `cli-${port}`,
|
|
302
325
|
port,
|
|
303
|
-
|
|
326
|
+
knownBinSlug,
|
|
304
327
|
passthrough
|
|
305
328
|
);
|
|
329
|
+
knownBinSlug = reg.binSlug;
|
|
330
|
+
return reg;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Resilient re-register: keep retrying with backoff until the server is
|
|
334
|
+
// reachable again (e.g. it's still booting after a restart).
|
|
335
|
+
async function reregister() {
|
|
336
|
+
let delay = 1000;
|
|
337
|
+
for (;;) {
|
|
338
|
+
try {
|
|
339
|
+
return await register();
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.log(
|
|
342
|
+
`${DIM}[${timestamp()}]${RESET} ${YELLOW}Reconnect failed: ${err.message} — retrying in ${Math.round(delay / 1000)}s${RESET}`
|
|
343
|
+
);
|
|
344
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
345
|
+
delay = Math.min(delay * 2, 30_000);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// 1. Register the tunnel
|
|
351
|
+
let registration;
|
|
352
|
+
try {
|
|
353
|
+
process.stdout.write(`${DIM}Registering tunnel...${RESET}`);
|
|
354
|
+
registration = await register();
|
|
306
355
|
console.log(` ${GREEN}done${RESET}`);
|
|
307
356
|
} catch (err) {
|
|
308
357
|
console.log(` ${RED}failed${RESET}`);
|
|
@@ -323,11 +372,12 @@ export async function startTunnel({ port, bin, name, url, token: tokenOverride,
|
|
|
323
372
|
}
|
|
324
373
|
|
|
325
374
|
// 3. Start forwarding. Passthrough holds the caller open, so poll faster.
|
|
326
|
-
await pollForCaptures(
|
|
375
|
+
await pollForCaptures({
|
|
327
376
|
serverUrl,
|
|
328
377
|
token,
|
|
329
378
|
tunnelId,
|
|
330
379
|
localOrigin,
|
|
331
|
-
passthrough ? 250 : 1000
|
|
332
|
-
|
|
380
|
+
pollIntervalMs: passthrough ? 250 : 1000,
|
|
381
|
+
reregister,
|
|
382
|
+
});
|
|
333
383
|
}
|