@gscdump/sdk 0.27.1 → 0.27.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.
package/dist/index.d.mts CHANGED
@@ -403,6 +403,18 @@ interface PartnerWebSocketLike {
403
403
  interface PartnerWebSocketConstructor {
404
404
  new (url: string, protocols?: string | string[]): PartnerWebSocketLike;
405
405
  }
406
+ interface PartnerRealtimeReconnectOptions {
407
+ /** Default `true`. Set `false` to disable automatic reconnection entirely. */
408
+ enabled?: boolean;
409
+ /** First-retry delay in ms; doubles each attempt. Default `1000`. */
410
+ baseDelayMs?: number;
411
+ /** Upper bound on the backoff delay in ms. Default `30000`. */
412
+ maxDelayMs?: number;
413
+ /** Stop after this many consecutive failed attempts. Default `Infinity`. */
414
+ maxRetries?: number;
415
+ /** Apply equal-jitter to each delay so many clients don't reconnect in lockstep. Default `true`. */
416
+ jitter?: boolean;
417
+ }
406
418
  interface PartnerRealtimeOptions {
407
419
  /**
408
420
  * HTTP API base used only to derive a same-origin websocket base when `wsBase`
@@ -421,6 +433,17 @@ interface PartnerRealtimeOptions {
421
433
  siteIds?: string[];
422
434
  protocols?: string | string[];
423
435
  WebSocket?: PartnerWebSocketConstructor;
436
+ /**
437
+ * Automatic reconnection on unexpected close (network drop, server restart).
438
+ * A raw WebSocket `error` Event carries no actionable detail, so rather than
439
+ * surfacing it the client transparently reconnects with exponential backoff.
440
+ * `true` (default) uses defaults; pass an object to tune; `false` disables.
441
+ * The backoff counter resets once a reconnection reaches `authenticated`.
442
+ */
443
+ reconnect?: boolean | PartnerRealtimeReconnectOptions;
444
+ /** Injected timer hooks (for tests). Defaults to global `setTimeout`/`clearTimeout`. */
445
+ setTimeout?: (handler: () => void, ms: number) => unknown;
446
+ clearTimeout?: (handle: unknown) => void;
424
447
  }
425
448
  interface PartnerRealtimeClient {
426
449
  readonly status: PartnerRealtimeStatus;
package/dist/index.mjs CHANGED
@@ -1379,6 +1379,16 @@ function getGscUnstableCutoffDate() {
1379
1379
  const cutoff = new Date(y, m - 1, d - 3);
1380
1380
  return `${cutoff.getFullYear()}-${String(cutoff.getMonth() + 1).padStart(2, "0")}-${String(cutoff.getDate()).padStart(2, "0")}`;
1381
1381
  }
1382
+ function resolveReconnect(reconnect) {
1383
+ const opts = reconnect === false ? { enabled: false } : reconnect === true || reconnect == null ? {} : reconnect;
1384
+ return {
1385
+ enabled: opts.enabled ?? true,
1386
+ baseDelayMs: opts.baseDelayMs ?? 1e3,
1387
+ maxDelayMs: opts.maxDelayMs ?? 3e4,
1388
+ maxRetries: opts.maxRetries ?? Number.POSITIVE_INFINITY,
1389
+ jitter: opts.jitter ?? true
1390
+ };
1391
+ }
1382
1392
  const HTTP_PROTOCOL_RE = /^http/i;
1383
1393
  const API_SUFFIX_RE = /\/api\/?$/;
1384
1394
  const TRAILING_SLASH_RE = /\/+$/;
@@ -1415,6 +1425,12 @@ function getWebSocketCtor(options) {
1415
1425
  function createPartnerRealtimeClient(options) {
1416
1426
  let socket = null;
1417
1427
  let status = "idle";
1428
+ const reconnect = resolveReconnect(options.reconnect);
1429
+ const setTimer = options.setTimeout ?? ((handler, ms) => setTimeout(handler, ms));
1430
+ const clearTimer = options.clearTimeout ?? ((handle) => clearTimeout(handle));
1431
+ let manualClose = false;
1432
+ let reconnectAttempts = 0;
1433
+ let reconnectTimer = null;
1418
1434
  const statusHandlers = /* @__PURE__ */ new Set();
1419
1435
  const messageHandlers = /* @__PURE__ */ new Set();
1420
1436
  const eventHandlers = /* @__PURE__ */ new Set();
@@ -1441,6 +1457,68 @@ function createPartnerRealtimeClient(options) {
1441
1457
  siteIds
1442
1458
  });
1443
1459
  }
1460
+ function clearReconnectTimer() {
1461
+ if (reconnectTimer != null) {
1462
+ clearTimer(reconnectTimer);
1463
+ reconnectTimer = null;
1464
+ }
1465
+ }
1466
+ function backoffDelay(attempt) {
1467
+ const exp = Math.min(reconnect.maxDelayMs, reconnect.baseDelayMs * 2 ** attempt);
1468
+ if (!reconnect.jitter) return exp;
1469
+ return Math.round(exp / 2 + Math.random() * (exp / 2));
1470
+ }
1471
+ function openSocket() {
1472
+ if (socket) return socket;
1473
+ clearReconnectTimer();
1474
+ manualClose = false;
1475
+ socket = new (getWebSocketCtor(options))(buildWsUrl(options), options.protocols);
1476
+ setStatus("connecting");
1477
+ socket.onopen = () => {
1478
+ setStatus("open");
1479
+ sendJson(authPayload());
1480
+ };
1481
+ socket.onmessage = (event) => {
1482
+ const message = parseMessage(event.data);
1483
+ if (!message) return;
1484
+ if ("event" in message && message.event === "connected") {
1485
+ reconnectAttempts = 0;
1486
+ setStatus("authenticated");
1487
+ if (options.siteIds?.length) subscribe(options.siteIds);
1488
+ }
1489
+ for (const handler of messageHandlers) handler(message);
1490
+ if (isRealtimeEvent(message)) for (const handler of eventHandlers) handler(message);
1491
+ };
1492
+ socket.onerror = (event) => {
1493
+ setStatus("error");
1494
+ for (const handler of errorHandlers) handler(event);
1495
+ };
1496
+ socket.onclose = (event) => {
1497
+ socket = null;
1498
+ setStatus("closed");
1499
+ for (const handler of messageHandlers) handler({
1500
+ type: "error",
1501
+ message: `WebSocket closed: ${JSON.stringify(event)}`
1502
+ });
1503
+ scheduleReconnect();
1504
+ };
1505
+ return socket;
1506
+ }
1507
+ function scheduleReconnect() {
1508
+ if (!reconnect.enabled || manualClose || reconnectTimer != null) return;
1509
+ if (reconnectAttempts >= reconnect.maxRetries) {
1510
+ const err = /* @__PURE__ */ new Error(`Partner realtime reconnect exhausted after ${reconnectAttempts} attempts`);
1511
+ for (const handler of errorHandlers) handler(err);
1512
+ return;
1513
+ }
1514
+ const delay = backoffDelay(reconnectAttempts);
1515
+ reconnectAttempts += 1;
1516
+ setStatus("connecting");
1517
+ reconnectTimer = setTimer(() => {
1518
+ reconnectTimer = null;
1519
+ openSocket();
1520
+ }, delay);
1521
+ }
1444
1522
  return {
1445
1523
  get status() {
1446
1524
  return status;
@@ -1449,38 +1527,11 @@ function createPartnerRealtimeClient(options) {
1449
1527
  return socket;
1450
1528
  },
1451
1529
  connect() {
1452
- if (socket) return socket;
1453
- socket = new (getWebSocketCtor(options))(buildWsUrl(options), options.protocols);
1454
- setStatus("connecting");
1455
- socket.onopen = () => {
1456
- setStatus("open");
1457
- sendJson(authPayload());
1458
- };
1459
- socket.onmessage = (event) => {
1460
- const message = parseMessage(event.data);
1461
- if (!message) return;
1462
- if ("event" in message && message.event === "connected") {
1463
- setStatus("authenticated");
1464
- if (options.siteIds?.length) subscribe(options.siteIds);
1465
- }
1466
- for (const handler of messageHandlers) handler(message);
1467
- if (isRealtimeEvent(message)) for (const handler of eventHandlers) handler(message);
1468
- };
1469
- socket.onerror = (event) => {
1470
- setStatus("error");
1471
- for (const handler of errorHandlers) handler(event);
1472
- };
1473
- socket.onclose = (event) => {
1474
- socket = null;
1475
- setStatus("closed");
1476
- for (const handler of messageHandlers) handler({
1477
- type: "error",
1478
- message: `WebSocket closed: ${JSON.stringify(event)}`
1479
- });
1480
- };
1481
- return socket;
1530
+ return openSocket();
1482
1531
  },
1483
1532
  close(code, reason) {
1533
+ manualClose = true;
1534
+ clearReconnectTimer();
1484
1535
  socket?.close(code, reason);
1485
1536
  socket = null;
1486
1537
  setStatus("closed");
@@ -1777,7 +1828,7 @@ function classifySearchConsoleStage(input) {
1777
1828
  const discovered = issueCount$1(issues, "discovered_not_indexed");
1778
1829
  const crawled = issueCount$1(issues, "crawled_not_indexed");
1779
1830
  const hardBlocks = issueCount$1(issues, "blocked_robots", "server_error", "access_denied", "forbidden") + (input.crawlAuditBlockerCount ?? 0);
1780
- const canonicalMismatches = input.canonicalMismatchCount ?? issueCount$1(issues, "canonical_mismatch");
1831
+ const canonicalMismatches = Math.min(notIndexed, input.canonicalMismatchCount ?? issueCount$1(issues, "canonical_mismatch"));
1781
1832
  const visibleNoClickPages = (input.pageInventory ?? []).filter((page) => page.impressions >= 50 && page.clicks === 0).length;
1782
1833
  const poorPositionPages = (input.pageInventory ?? []).filter((page) => page.impressions >= 50 && (page.position ?? 0) > 20).length;
1783
1834
  const ctrOutlierCount = input.ctrOutlierCount ?? 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/sdk",
3
3
  "type": "module",
4
- "version": "0.27.1",
4
+ "version": "0.27.2",
5
5
  "description": "Consumer SDK for hosted gscdump.com integrations.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -44,10 +44,10 @@
44
44
  "date-fns": "^4.4.0",
45
45
  "ofetch": "^1.5.1",
46
46
  "zod": "^4.4.3",
47
- "@gscdump/analysis": "0.27.1",
48
- "@gscdump/contracts": "0.27.1",
49
- "@gscdump/engine": "0.27.1",
50
- "gscdump": "0.27.1"
47
+ "@gscdump/analysis": "0.27.2",
48
+ "@gscdump/engine": "0.27.2",
49
+ "gscdump": "0.27.2",
50
+ "@gscdump/contracts": "0.27.2"
51
51
  },
52
52
  "devDependencies": {
53
53
  "typescript": "^6.0.3",