|
| 1 | +# Backend readiness for the JavaScript SDK beta |
| 2 | + |
| 3 | +Status: **Design — approved decisions recorded, not built.** 2026-09-29. |
| 4 | + |
| 5 | +The backend already carries most of the JavaScript support: the `javascript` app type and its 27 |
| 6 | +target subtypes, per-runtime version floors, symbol routing, the `js` build format, sourcemap |
| 7 | +symbolication by debug-id, native minidumps, rrweb replay, runtime labels, frame variables and |
| 8 | +source context. That work is described in `javascript-application-type.md`, |
| 9 | +`appserver-javascript-support.md` and `electron-native-crashes.md`. |
| 10 | + |
| 11 | +This document covers what is still missing before a public beta can send reports to production |
| 12 | +and have them accepted, processed and shown. It came out of a read-only audit of each repo's |
| 13 | +`origin/main` against the SDK's `main` (`0c32485`), with every load-bearing finding re-checked in |
| 14 | +source. |
| 15 | + |
| 16 | +## Scope |
| 17 | + |
| 18 | +In: the appserver, the background worker and the viewer. One worktree and one PR per repo, under |
| 19 | +`/Volumes/External2TB/Projects/Bugsee/_worktrees/{appserver,worker,viewer}-js-beta`, branch |
| 20 | +`feat/js-beta-readiness`, based on each repo's `origin/main`. |
| 21 | + |
| 22 | +Out, and tracked as the SDK release track (see the last section): anything that has to change in |
| 23 | +this repo — publishing, the reported version string, missing `crash.json` on some report kinds, |
| 24 | +the browser `user-agent` header, the empty OS value, the Electron video attributes. |
| 25 | + |
| 26 | +## Decisions |
| 27 | + |
| 28 | +| # | Decision | Chosen | |
| 29 | +|---|---|---| |
| 30 | +| D1 | Version the beta reports | `0.1.0-beta.N`. The appserver floor must admit prereleases. | |
| 31 | +| D2 | Electron runtime system symbols (Electron/V8/Node frames in native crashes) | **Deferred.** Beta limitation; the viewer says so instead of asking for an upload. | |
| 32 | +| D3 | When the viewer's production gate opens | **In this PR.** The viewer reaches production only through a manual deploy, so the launch is that deploy. | |
| 33 | +| — | Line-level secret scrubbing of source context | **Not revived.** Closed as a product decision on 2026-09-06 (worker #53, javascript #4); `source_context_disabled` is the mitigation. | |
| 34 | + |
| 35 | +## appserver |
| 36 | + |
| 37 | +### A1 — stop rejecting reports from mobile browsers (blocker) |
| 38 | + |
| 39 | +Since `e53b58f` the browser tier reports the operating system in `platform.type`, so a mobile |
| 40 | +browser sends `ios` or `android` (`packages/browser/src/user-agent.ts`). |
| 41 | +`issue.service.js:1050-1064` treats any Apple or Android `platform.type` as a native mobile SDK |
| 42 | +and checks the reported version against the iOS (1.27.0) or Android (1.19.11) floor. A JS SDK at |
| 43 | +0.1.0 fails and gets `IssueReportedByUnsupportedSdk` (12007). The SDK does not know 12007, treats it |
| 44 | +as transient, and retries the report at every launch without end. |
| 45 | + |
| 46 | +Change: return early from that check when `env.sdk.type === 'javascript'`. The JS floor is |
| 47 | +enforced at session start by `utils.js isSupportedSdkVersion`, keyed by the runtime, not the OS. |
| 48 | + |
| 49 | +### A2 — admit prerelease versions (blocker) |
| 50 | + |
| 51 | +`config/default.js:156-161` sets every JS runtime's `minimum` to `0.1.0`, and |
| 52 | +`semver.gt('0.1.0', '0.1.0-beta.1')` is true, so a beta session fails with `UnsupportedSdkError` |
| 53 | +(99098). The SDK classes 99098 as permanent and deletes the report. |
| 54 | + |
| 55 | +Change: every JS runtime block gets `minimum: '0.0.0-0'` (the lowest version semver can express, so |
| 56 | +any prerelease passes), and `current`/`old` become `0.1.0-beta.1`. Those two are release-tunable and |
| 57 | +move with each beta. |
| 58 | + |
| 59 | +### A3 — send a retryable error under the rate limit (major) |
| 60 | + |
| 61 | +When the issue rate limit trips, `issue.controller.js:84,96` sends the retryable `ServerTooBusy` |
| 62 | +only if `errors/index.js isServerTooBusySupported` says so. That function answers for iOS and |
| 63 | +Android only, so a JS client gets `TooManySimilarCrashes` (12004), which the SDK treats as |
| 64 | +permanent. Error storms on a busy site lose their reports. |
| 65 | + |
| 66 | +Change: `isServerTooBusySupported` returns `true` when `environment.sdk.type === 'javascript'`. The |
| 67 | +JS SDK has handled `ServerTooBusy` since its first version, so there is no version floor. |
| 68 | + |
| 69 | +### A4 — list the JS resource types for the MCP (minor) |
| 70 | + |
| 71 | +`mcp/tools/issue.get-resource.js:43-62` omits `replay`, `profile` and `video.aux`, so an agent cannot |
| 72 | +fetch a web session's replay. Add them to the enum and its description. |
| 73 | + |
| 74 | +## worker |
| 75 | + |
| 76 | +### W1 — symbolicate from `trace` (blocker + major) |
| 77 | + |
| 78 | +Commit `6b99a7a` on `feat/js-context-diagnostic` was pushed after PR #48 merged and never reached |
| 79 | +`main`. It reads each frame's position from the SDK's `trace` instead of from `data`, which fixes two |
| 80 | +bugs the audit reproduced: |
| 81 | + |
| 82 | +- **Anonymous frames never symbolicate.** A frame with no function name carries no `data.member`, |
| 83 | + so the worker builds `@line:col`; `symbolfiles/sourcemap.py:17`'s `^(.+)@(\d+):(\d+)$` needs a |
| 84 | + non-empty name and silently misses. Anonymous frames are the norm in minified code. |
| 85 | +- **A reprocess reverts symbolicated frames.** The first pass overwrites `data` with the |
| 86 | + original-source position; the second pass reads that as if it were minified, and resets |
| 87 | + `trace-sym` to the minified trace. Any late map upload triggers this. |
| 88 | + |
| 89 | +Change: cherry-pick `6b99a7a` onto the branch. It applies cleanly. |
| 90 | + |
| 91 | +### W2 — keep the SDK's summary and signatures when there is no `crash.json` (blocker) |
| 92 | + |
| 93 | +Hang (AppHang) reports and Electron renderer-gone reports ship without `crash.json`. |
| 94 | +`jobs/bundle.py:884-891` fills any non-bug report without one with signature `no-crash-file` and |
| 95 | +summary `<missing crash details>`. That overwrites the SDK's own summary ("Main thread hang |
| 96 | +detected") and signatures (`AppHang::Fair|Medium|Severe`), and because the appserver merges on |
| 97 | +`signatures: {$in}`, every hang and every renderer crash in an app collapses into one issue. |
| 98 | + |
| 99 | +Change: when the environment is a JavaScript one (`environment.sdk.type == 'javascript'`) and there |
| 100 | +is no crash file, leave the summary and signatures as the SDK reported them. Other SDKs keep the |
| 101 | +current fallback. The SDK track separately adds a `crash.json` to both report kinds; this change |
| 102 | +makes the worker correct either way. |
| 103 | + |
| 104 | +### W3 — respect the SDK's `user` frame flag (major) |
| 105 | + |
| 106 | +`crash/javascript.py:445` sets `frame['user'] = True` for every frame. The SDK marks runtime frames |
| 107 | +(`node:`) and its own `@bugsee/*` frames as not the app's (`packages/core/src/crash.ts:178-186`), so |
| 108 | +issues currently group on a `@bugsee/capture` or `node:` frame instead of the app's call site. |
| 109 | + |
| 110 | +Change: keep the SDK's value and default to `True` only when the flag is absent. |
| 111 | + |
| 112 | +### W4 — no "at at" in titles (minor) |
| 113 | + |
| 114 | +The summary format (`crash/javascript.py:461`, `"%s at %s"`) prefixes `at` to a location that, |
| 115 | +when unsymbolicated, is the SDK trace and already starts with `at `. Strip a leading `at ` from the |
| 116 | +location before formatting. |
| 117 | + |
| 118 | +## viewer |
| 119 | + |
| 120 | +### V1 — open the JavaScript app type in production (blocker) |
| 121 | + |
| 122 | +`elements-application-create-dialog.component.ts:199` sets |
| 123 | +`isJavascriptTypeAvailable = config.getValue("env.type") !== "production"`, and line 200 reuses it |
| 124 | +for Rust. Only the create-dialog radio is gated. |
| 125 | + |
| 126 | +Change: make JavaScript always available. Give Rust its own gate through |
| 127 | +`core/utils/deployment.ts isPreProductionFeatureAvailable()` — the helper that file names as the |
| 128 | +only definition — so opening JavaScript does not make Rust public too. |
| 129 | + |
| 130 | +### V2 — debug data for JavaScript apps (major) |
| 131 | + |
| 132 | +"Setup auto upload" branches only on unity, xamarin/dotnet, android and ios, so for a JS app it opens |
| 133 | +with zero steps. The upload dialog says "Upload mapping" and asks for a zip of debug information. |
| 134 | + |
| 135 | +Change: add a JavaScript branch to the setup dialog that shows the `@bugsee/vite-plugin` and |
| 136 | +`@bugsee/webpack-plugin` configuration (and the `bugsee-cli` command for other bundlers), and use |
| 137 | +source-map wording in the upload dialog for JS apps. The iOS-only drop zone stays as it is; manual |
| 138 | +`.map` upload is not part of the beta flow. |
| 139 | + |
| 140 | +### V3 — stop downloading `profile.json` to discard it (major) |
| 141 | + |
| 142 | +`profile` is not in `recording-session.service.ts`'s ignored-types list, so a CPU profile (often |
| 143 | +several MB) is fetched and then dropped by `recording-helper.service.ts`'s `default:` branch. Add it |
| 144 | +to the ignored list. A flame-graph view comes after the beta. |
| 145 | + |
| 146 | +### V4 — documentation links for JavaScript apps (major) |
| 147 | + |
| 148 | +`getDocumentationDir.ts:5-6` returns the subtype, so the console hint links to |
| 149 | +`docs.bugsee.com/sdk/express/...`. Map every JavaScript subtype to `javascript`. The JavaScript |
| 150 | +section of the docs site does not exist yet; that is docs-repo work, tracked below. |
| 151 | + |
| 152 | +### V5 — small fixes (minor) |
| 153 | + |
| 154 | +- The app-list filter has no JavaScript option. |
| 155 | +- The issue list's reporter-email tooltip has text for iOS and Android only. |
| 156 | +- The application plate prints the raw `application.type`. |
| 157 | +- The performance tab computes `end - start` for spans that the SDK may leave without an end, which |
| 158 | + gives NaN widths; treat a missing end as the transaction end. |
| 159 | +- Onboarding install lines name only the adapter (`npm install @bugsee/react`) while the snippet |
| 160 | + imports `@bugsee/bugsee`, which resolves only through hoisting. Install both, or import from the |
| 161 | + adapter where it re-exports — whichever the adapter's actual exports support. |
| 162 | +- A native frame in a JavaScript app shows "missing debug information… upload". Per D2, Electron's |
| 163 | + own frames cannot be symbolicated during the beta, so the hint for those frames says that instead. |
| 164 | + |
| 165 | +## Testing |
| 166 | + |
| 167 | +Each change is test-first in its repo's own harness, with the mutator loop from |
| 168 | +`docs/implementation-standards.md` applied to every changed function: |
| 169 | + |
| 170 | +- appserver: mocha (`NODE_ENV=test npx mocha`), plus `npm run eslint` and the circular-dependency |
| 171 | + check. |
| 172 | +- worker: `unittest` under `.venv314`, `PYTHONDONTWRITEBYTECODE=1` during mutation runs (stale |
| 173 | + `.pyc` files otherwise mask a mutation). |
| 174 | +- viewer: the specs that exist for the touched services, plus `npm run build`, which is the viewer's |
| 175 | + real gate. |
| 176 | + |
| 177 | +Each PR then gets the convergent multi-agent review before it is marked ready. |
| 178 | + |
| 179 | +## Rollout |
| 180 | + |
| 181 | +Merging to `main` deploys each repo to staging only. After staging is checked with a real JS app |
| 182 | +(one browser session, one Node crash with a map, one mobile-browser session): |
| 183 | + |
| 184 | +- appserver: the manual production workflow run. Production is 34 commits behind `main`, which |
| 185 | + also brings the `js` build format there — without it, bundler-plugin build registration fails in |
| 186 | + production today. |
| 187 | +- worker: a PR from `main` into `release` labelled `deploy-production`. |
| 188 | +- viewer: the manual production workflow run. This is the launch, since it opens the app type. |
| 189 | + |
| 190 | +Order: worker and appserver first, viewer last, so the app type becomes creatable only once the |
| 191 | +backend accepts what it produces. |
| 192 | + |
| 193 | +## SDK release track (not in these PRs) |
| 194 | + |
| 195 | +Found by the same audit; each is a change in this repo: |
| 196 | + |
| 197 | +- Every package is `private: true` and `.changeset/config.json` has `access: restricted`; nothing is |
| 198 | + on npm, so every onboarding snippet fails at `npm install`. |
| 199 | +- The reported version is hardcoded as `'0.1.0'` in six places (browser, node, webview, webworker, |
| 200 | + vercel-edge launch files and `core/src/carrier.ts`), so bumping `package.json` does not change it. |
| 201 | +- Hang reports (`node/src/hang-detection-provider.ts`) and Electron renderer-gone reports |
| 202 | + (`electron/src/renderer-incident-provider.ts`) send no `crash.json`. A renderer minidump is |
| 203 | + attached but never stackwalked. |
| 204 | +- The browser transport forwards a `user-agent` header, which is not in production's CORS |
| 205 | + allowed-headers list; Firefox is expected to fail the preflight. |
| 206 | +- An unrecognised OS is sent as `platform.type: ''`, which the session endpoint rejects as missing |
| 207 | + (permanent); send `'unknown'`. |
| 208 | +- Electron video is WebM, but the manifest carries no codec/format attributes, so the worker and |
| 209 | + viewer label it `video/mp4`. |
| 210 | +- A cross-origin "Script error." with no filename sends zero frames, so each occurrence is its own |
| 211 | + issue. |
| 212 | +- The performance uploader sends no `x-bugsee-internal` header and ignores `{ok:false}`. |
| 213 | + |
| 214 | +Docs: the docs site has no JavaScript section, which V4 and the first-issue email both link to. |
| 215 | + |
| 216 | +## Deferred |
| 217 | + |
| 218 | +- Electron runtime system symbols (D2): an upload prefix routed to `electron_symbols.system` in the |
| 219 | + worker, plus a way to fetch Electron's published `.sym` files per version. |
| 220 | +- A CPU-profile view in the viewer. |
| 221 | +- Public resource links and integrations (Slack/Jira) that include the web replay; the public API's |
| 222 | + crash formatter, which currently renders JS crashes through the Android one; JS app-level |
| 223 | + "SDK outdated" flags, which cannot use a single version for an app with several runtimes. |
| 224 | +- Retention and rollups for performance data. |
0 commit comments