Skip to content

fix(sdk): broadcast failover for browser errors + similar-entries limit - #817

Merged
feruzm merged 4 commits into
developfrom
fix/broadcast-failover-browser-errors
May 21, 2026
Merged

fix(sdk): broadcast failover for browser errors + similar-entries limit#817
feruzm merged 4 commits into
developfrom
fix/broadcast-failover-browser-errors

Conversation

@feruzm

@feruzmferuzm commented May 21, 2026

Copy link
Copy Markdown
Member

Two unrelated SDK fixes bundled per request — separate commits so they can be reverted independently.


1. Broaden broadcast failover for browser-side errors

callRPCBroadcast only retried on Node.js pre-connection codes (ECONNREFUSED, ENOTFOUND, EHOSTUNREACH, EAI_AGAIN), which never appear in the browser. When a public Hive node's CDN returned an HTML error page (e.g. CF tunnel 1033 → 530, or a 502/520-528 interstitial) or the browser blocked the response with CORS, fetch surfaced a TypeError("Failed to fetch") or the downstream res.json() threw SyntaxError — both fell through isPreConnectionError(e) === false and the broadcast threw immediately with no failover to the next node, so any single sick node could break broadcasts site-wide.

Reproduced today against a public node returning Cloudflare error 1033 — browser console showed a CORS failure on the broadcast call and no second-node attempt was made.

Changes

  • isBroadcastSafeToRetry(e) replaces isPreConnectionError(e). Accepts:
    • Node.js pre-connection codes (existing).
    • Browser fetch TypeError("Failed to fetch") (Chromium), "NetworkError when attempting to fetch" (Firefox), "Load failed" (Safari).
    • SyntaxError / generic JSON-parse messages from HTML interstitial bodies.
    • NodeError (HTTP 429/5xx surfaced by jsonRPCCall).
    • Still rejects RPCError — real blockchain rejections must not failover.
  • jsonRPCCall now treats any 5xx as NodeError (was 503 only), so 502 / 504 / 520-530 reach the retry path with a typed error instead of a generic SyntaxError downstream.

Retrying the same signed tx across nodes is safe: broadcastOperations signs exactly once and callRPCBroadcast reuses that payload across attempts; Hive's mempool dedupes by trx_id.

Notes

  • No public API changes; callRPC (read path) is untouched — it already wraps around all nodes via the retry budget.
  • Doesn't address the separate Keychain-extension case (extension uses its own nodes; this PR only fixes the SDK-internal broadcast path used by private-key, MetaMask snap, and any code calling callRPCBroadcast directly).

2. Raise SIMILAR_ENTRIES_LIMIT from 12 to 50

The 6-month recency filter drops most semantic neighbours that HiveSense returns (it ranks by embedding similarity with no recency bias), and the search-api primary path currently yields 0 hits for tag-filtered queries in production, so HiveSense is effectively the sole source. With the cap at 12, the recency filter routinely collapsed the result set below the 3-result render threshold and the suggestions strip hid entirely on most posts.

Spec assertion updated to mirror the new limit.


Test plan

  • pnpm --filter @ecency/sdk test — all 364 tests pass (5 new in call.spec.ts)
  • New call.spec.ts covers: browser TypeError → failover; CF HTML 530 → failover; CF HTML 502 (tunnel 1033) → failover; RPCError does not failover; all-nodes-fail traverses each node exactly once
  • Existing get-similar-entries-query-options.spec.ts updated assertion (12 → 50)
  • Verify failover on alpha by temporarily pointing one node entry at a host that 5xxs — broadcast should succeed by failing over
  • Sanity-check suggestions strip appears on a sample of recent posts after deploy

Summary by CodeRabbit

  • Bug Fixes

    • Improved broadcast failover mechanism to handle browser network errors and HTTP proxy errors more reliably.
    • Enhanced error classification to distinguish between retryable and non-retryable failures.
  • Improvements

    • Increased similar entries search result limit from 12 to 50.

Review Change Stack

callRPCBroadcast only retried on Node.js pre-connection codes
(ECONNREFUSED, ENOTFOUND, etc.), which never appear in the browser.
When a node's CDN returned an HTML error page (e.g. Cloudflare tunnel
1033) or the browser blocked the response with CORS, fetch surfaced a
plain TypeError("Failed to fetch") or a SyntaxError from res.json(), so
the broadcast threw immediately with no failover to the next node.
- Add isBroadcastSafeToRetry that also accepts TypeError network
failures from Chromium/Firefox/Safari, JSON parse errors from HTML
interstitial bodies, and NodeError (HTTP 429/5xx).
- jsonRPCCall now treats any 5xx as NodeError (was 503 only), so 502 /
504 / 520-530 reach the retry path with a typed error instead of a
generic SyntaxError downstream.
- Retrying the same signed tx across nodes is safe because Hive
mempools dedupe by trx_id; broadcastOperations signs exactly once.
- Tests cover browser TypeError, HTML 530, HTML 502, RPCError
(must NOT failover), and all-nodes-down traversal.
@coderabbitai

coderabbitaiBot commented May 21, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 30e71c42-81fb-4760-b474-8e3d319afbad

📥 Commits

Reviewing files that changed from the base of the PR and between 86dfa55 and 058e637.

⛔ Files ignored due to path filters (12)
  • packages/sdk/dist/browser/hive.js is excluded by !**/dist/**
  • packages/sdk/dist/browser/hive.js.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/browser/index.js is excluded by !**/dist/**
  • packages/sdk/dist/browser/index.js.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/hive.cjs is excluded by !**/dist/**
  • packages/sdk/dist/node/hive.cjs.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/hive.mjs is excluded by !**/dist/**
  • packages/sdk/dist/node/hive.mjs.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.cjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.cjs.map is excluded by !**/dist/**, !**/*.map
  • packages/sdk/dist/node/index.mjs is excluded by !**/dist/**
  • packages/sdk/dist/node/index.mjs.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (8)
  • packages/sdk/CHANGELOG.md
  • packages/sdk/package.json
  • packages/sdk/src/hive-tx/helpers/call.spec.ts
  • packages/sdk/src/hive-tx/helpers/call.ts
  • packages/sdk/src/modules/search/queries/get-similar-entries-query-options.spec.ts
  • packages/sdk/src/modules/search/queries/get-similar-entries-query-options.ts
  • packages/wallets/CHANGELOG.md
  • packages/wallets/package.json

📝 Walkthrough

Walkthrough

SDK 2.2.20 refactors broadcast failover to distinguish browser network errors, HTML interstitials, and pre-connection failures from JSON-RPC errors and abort-like timeouts, ensuring proper node switching without ambiguous retries. HTTP 5xx responses are consistently treated as node-level failures. The similar-entries query limit increases from 12 to 50.

Changes

Broadcast failover and similar entries updates

Layer / File(s)Summary
Broadcast error classification utilities
packages/sdk/src/hive-tx/helpers/call.ts
New BROWSER_NETWORK_ERRORS set, flattenErrorText helper to traverse up to 5 cause levels, and isBroadcastSafeToRetry function classify errors as retryable (pre-connection, browser fetch failures, JSON parse from HTML) or non-retryable (JSON-RPC, abort-like), replacing isPreConnectionError.
Broadcast failover behavior: HTTP classification and retry gating
packages/sdk/src/hive-tx/helpers/call.ts
jsonRPCCall HTTP handling treats all 5xx responses as NodeError, not just 503; callRPCBroadcast now uses isBroadcastSafeToRetry to gate failover, so only classified-safe errors trigger node switching while JSON-RPC and AbortError failures throw immediately.
Broadcast failover test coverage
packages/sdk/src/hive-tx/helpers/call.spec.ts
Comprehensive Vitest suite validates that browser TypeError network errors and Cloudflare HTML 530/502 errors trigger failover, JSON-RPC errors surface without retry, full node traversal occurs on repeated network failures, and AbortError does not trigger failover.
Similar entries limit increase
packages/sdk/src/modules/search/queries/get-similar-entries-query-options.ts, packages/sdk/src/modules/search/queries/get-similar-entries-query-options.spec.ts
SIMILAR_ENTRIES_LIMIT constant bumped from 12 to 50; test assertion updated to match the larger HiveSense result window.
Version and changelog updates
packages/sdk/CHANGELOG.md, packages/sdk/package.json, packages/wallets/CHANGELOG.md, packages/wallets/package.json
SDK version 2.2.19 → 2.2.20 with changelog entry; wallets version 4.0.17 → 4.0.18 reflecting SDK dependency update.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ecency/vision-next#762: Introduces config.broadcastTimeout as the default timeout for broadcasts; this PR updates the broadcast failover/retry error classification that governs node switching behavior.
  • ecency/vision-next#744: Propagates AbortSignal for query cancellation; this PR explicitly ensures AbortError does not trigger node failover, directly aligning abort handling semantics.
  • ecency/vision-next#805: Modifies similar-entries logic in get-similar-entries-query-options.ts; this PR increases the SIMILAR_ENTRIES_LIMIT from 12 to 50, overlapping on the same query constants.

Suggested labels

patch:sdk

Poem

🐰 A broadcast that bounces but knows when to stop,
Network hiccups fade while JSON errors pop,
Browser messages sorted, HTML caught with care,
Failover logic blooms through the troublesome air!
And similar entries? They grew from twelve to plenty,
A patch full of grace, version two-point-two-twenty. 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe pull request title accurately summarizes both main changes: broadcast failover improvements for browser errors and the similar-entries limit increase from 12 to 50.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/broadcast-failover-browser-errors

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/sdk/src/hive-tx/helpers/call.spec.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

YAMLException: Cannot read config file: /packages/sdk/eslint.config.mjs
Error: end of the stream or a document separator is expected (5:12)

2 |
3 | export default tseslint.config(
4 | {
5 | ignores: ["dist", "node_modules", "tsup ...
----------------^
6 | },
7 | ...tseslint.configs.recommended,
at generateError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:199:10)
at throwError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:203:9)
at readDocument (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1651:5)
at loadDocuments (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1694:5)
at Object.load (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1720:19)
at loadLegacyConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2565:21)
at loadConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2680:20)
at ConfigArrayFactory._loadConfigData (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:42)
at ConfigArrayFactory.loadFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
at createCLIConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)

packages/sdk/src/hive-tx/helpers/call.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

YAMLException: Cannot read config file: /packages/sdk/eslint.config.mjs
Error: end of the stream or a document separator is expected (5:12)

2 |
3 | export default tseslint.config(
4 | {
5 | ignores: ["dist", "node_modules", "tsup ...
----------------^
6 | },
7 | ...tseslint.configs.recommended,
at generateError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:199:10)
at throwError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:203:9)
at readDocument (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1651:5)
at loadDocuments (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1694:5)
at Object.load (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1720:19)
at loadLegacyConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2565:21)
at loadConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2680:20)
at ConfigArrayFactory._loadConfigData (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:42)
at ConfigArrayFactory.loadFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
at createCLIConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)

packages/sdk/src/modules/search/queries/get-similar-entries-query-options.spec.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

YAMLException: Cannot read config file: /packages/sdk/eslint.config.mjs
Error: end of the stream or a document separator is expected (5:12)

2 |
3 | export default tseslint.config(
4 | {
5 | ignores: ["dist", "node_modules", "tsup ...
----------------^
6 | },
7 | ...tseslint.configs.recommended,
at generateError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:199:10)
at throwError (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:203:9)
at readDocument (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1651:5)
at loadDocuments (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1694:5)
at Object.load (/node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/lib/loader.js:1720:19)
at loadLegacyConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2565:21)
at loadConfigFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2680:20)
at ConfigArrayFactory._loadConfigData (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2984:42)
at ConfigArrayFactory.loadFile (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:2850:40)
at createCLIConfigArray (/node_modules/.pnpm/@eslint+eslintrc@2.1.4/node_modules/@eslint/eslintrc/dist/eslintrc.cjs:3660:35)

  • 1 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-appsBot commented May 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR bundles two SDK fixes: a broadened broadcast failover that handles browser network errors and HTTP 5xx interstitials (replacing the Node.js-only pre-connection check), and a bump of SIMILAR_ENTRIES_LIMIT from 12 to 50 to keep the suggestions strip visible when the 6-month recency filter prunes most semantic neighbours.

  • isBroadcastSafeToRetry replaces isPreConnectionError in callRPCBroadcast, adding browser TypeError, SyntaxError/HTML-interstitial, and NodeError (5xx) to the retry-safe set while still blocking on RPCError.
  • jsonRPCCall now throws a typed NodeError for all 5xx status codes (previously only 503), so Cloudflare/proxy interstitials surface as a named error instead of a downstream SyntaxError guess.
  • SIMILAR_ENTRIES_LIMIT raised from 12 → 50, with full_posts kept equal to result_limit; spec updated to match.

Confidence Score: 5/5

Safe to merge. The broadcast failover expansion is well-reasoned, AbortError/TimeoutError are correctly excluded from the retry path, and the 5xx NodeError promotion prevents guesswork on HTML interstitial bodies.

Both changes are narrow and well-tested. The retry-safety logic has been carefully constrained — RPCError is fast-pathed out before health recording, AbortError falls through to the non-retry branch, and the new 5xx NodeError promotion removes the ambiguous SyntaxError path. The limit bump is a one-line config change with a matching spec update.

No files require special attention.

Important Files Changed

FilenameOverview
packages/sdk/src/hive-tx/helpers/call.tsCore broadcast retry logic rewritten; isBroadcastSafeToRetry is well-structured, RPCError fast-path in the loop prevents double-recording, AbortError/TimeoutError correctly fall through to the non-retry branch.
packages/sdk/src/hive-tx/helpers/call.spec.tsNew spec file; covers network-error failover, HTML-530 failover, HTML-502 failover, RPCError no-failover, all-nodes-fail exhaustion, and AbortError no-failover. All paths exercised.
packages/sdk/src/modules/search/queries/get-similar-entries-query-options.tsStraightforward constant bump from 12 to 50; full_posts kept in sync with result_limit as required.
packages/sdk/src/modules/search/queries/get-similar-entries-query-options.spec.tsSpec assertion updated from 12 to 50 to mirror the new constant; no other changes.

Reviews (4): Last reviewed commit: "chore: apply changeset versioning for PR..." | Re-trigger Greptile

Comment threadpackages/sdk/src/hive-tx/helpers/call.spec.ts
The 6-month recency filter drops most semantic neighbours that HiveSense
returns (it ranks by embedding similarity with no recency bias), and the
search-api primary path currently yields 0 hits for tag-filtered queries
in production, so HiveSense is effectively the sole source. With the cap
at 12, the recency filter routinely collapsed the result set below the
3-result render threshold and the strip hid entirely on most posts.
Spec assertion updated to mirror the new limit.
@feruzmferuzm changed the title fix(sdk): broaden broadcast failover for browser-side errorsfix(sdk): broadcast failover for browser errors + similar-entries limitMay 21, 2026
Addresses two review notes on the failover widening:
1. jsonRPCCall JSDoc still listed "NodeError for HTTP 429/503" after
the 5xx generalization. Now reads "HTTP 429/5xx" so the doc matches
the code.
2. Add a spec asserting that an AbortError-style timeout makes
callRPCBroadcast throw on the first node without trying the next.
Without this guard a future refactor of isBroadcastSafeToRetry
could silently allow timeout failover — that's the one ambiguous
case where the node may have received the tx and a second-node
"duplicate transaction" RPCError would mask the original success.
@feruzmferuzm added the patch Bug fixes and patches (1.0.0 → 1.0.1) label May 21, 2026
@feruzm
feruzm merged commit bf8ad53 into developMay 21, 2026
4 checks passed
@feruzm
feruzm deleted the fix/broadcast-failover-browser-errors branch May 21, 2026 07:42
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patchBug fixes and patches (1.0.0 → 1.0.1)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@feruzm