@edraj/sauron-browser 1.5.0 → 1.7.0

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/README.md CHANGED
@@ -199,12 +199,17 @@ offline queue. Returns the live `SauronClient`.
199
199
 
200
200
  Idempotent: a second `init()` tears the previous client down first (restoring
201
201
  every patched global, clearing the current screen) before installing a fresh
202
- one. Throws `Error` when `dsn` is missing or not a string, and `DsnError` when
203
- the DSN itself is malformed.
202
+ one. Throws `Error` when `dsn` is missing or not a string, when `release` is
203
+ missing or blank, and `DsnError` when the DSN itself is malformed.
204
+
205
+ `release` is required as of v1.7.0 — `init()` throws without one.
204
206
 
205
207
  ```ts
206
- const client = Sauron.init({ dsn: 'https://pk_test@localhost:8081/1' });
207
- client.options.release; // null
208
+ const client = Sauron.init({
209
+ dsn: 'https://pk_test@localhost:8081/1',
210
+ release: 'web@1.4.2',
211
+ });
212
+ client.options.release; // 'web@1.4.2'
208
213
  client.dsn.projectId; // '1'
209
214
  ```
210
215
 
@@ -985,7 +990,7 @@ const appFrames = frames.filter((f) => isInAppFrame(f.filename));
985
990
 
986
991
  ```ts
987
992
  const SDK_NAME: string // 'sauron.javascript'
988
- const SDK_VERSION: string // '1.5.0'
993
+ const SDK_VERSION: string // '1.7.0'
989
994
  ```
990
995
 
991
996
  The SDK identity embedded in `header.sdk` of every envelope.
@@ -1027,7 +1032,7 @@ On by default:
1027
1032
  | `window.onunhandledrejection` | Error item from `event.reason`, mechanism `{ type: 'onunhandledrejection', handled: false }`, level `error`. |
1028
1033
  | `console.log/info/warn/error/debug` | Breadcrumb, category `console`, level mapped (`warn`→`warning`, `error`→`error`, `debug`→`debug`, else `info`), message = arguments joined and truncated to 512 chars, `data: { arguments: n }`. Output is untouched. |
1029
1034
  | `document` click listener (capture, passive) | Breadcrumb, category `ui.click`, message = a `tag#id.class` selector (up to 3 classes). Element text and attribute values are never serialized. |
1030
- | `history.pushState` / `replaceState` / `popstate` | Breadcrumb, type `navigation`, category `history`, `data: { from, to }` as paths. Same-path transitions are skipped. |
1035
+ | `history.pushState` / `replaceState` / `popstate` | Breadcrumb, type `navigation`, category `history`, `data: { from, to, operation }` paths plus the direction: `push`, `replace` or `pop`. Same-path transitions are skipped. |
1031
1036
  | `fetch` | Breadcrumb, category `fetch`, message `METHOD url`, `data: { method, url, status_code }`, level `warning` for status >= 400. |
1032
1037
  | `XMLHttpRequest.prototype.open` / `send` | Breadcrumb, category `xhr`, same shape as `fetch`. |
1033
1038
  | `document` `visibilitychange` + window `pagehide` | Beacon flush of the pending batch on unload. |
@@ -1040,6 +1045,16 @@ Opt-in:
1040
1045
  | `performance: true` | A `navigation` transaction for the initial page load (Navigation Timing, captured on `load`), an `http` transaction per instrumented `fetch` (`name` = `METHOD /path`, `status` `ok`/`error`), and a `navigation` transaction per SPA route change measured over one animation frame. No-op when `document` is undefined. |
1041
1046
  | `screenTracking: true` | Sets the screen to the new path on each SPA History navigation, which emits a `$screen` event on change. |
1042
1047
 
1048
+ `operation` uses the same vocabulary as the Flutter SDK's
1049
+ `SauronNavigatorObserver` (`push` / `pop` / `replace` / `remove`), so a trail
1050
+ reads the same whichever SDK sent it. `remove` has no web equivalent and is
1051
+ never emitted here. One limit worth knowing: **a forward navigation is recorded
1052
+ as `pop`.** `history.forward()` fires the same `popstate` event as
1053
+ `history.back()` and carries nothing to separate them, so `pop` means "moved
1054
+ through history", not specifically "went back". Distinguishing them would mean
1055
+ writing to `history.state`, which the host app's router also owns — not a
1056
+ trade the SDK makes.
1057
+
1043
1058
  Two guards keep the SDK from observing itself: a reentrancy flag held while SDK
1044
1059
  code runs, and a denylist on the DSN host. Requests the transport makes are
1045
1060
  therefore never turned into breadcrumbs or transactions. Wrappers are tagged, so
@@ -1096,7 +1111,7 @@ Other scope data:
1096
1111
  is unavailable.
1097
1112
 
1098
1113
  ```ts
1099
- Sauron.init({ dsn, tags: { tier: 'free' }, extra: { build: 'ci-42' } });
1114
+ Sauron.init({ dsn, release: 'web@1.4.2', tags: { tier: 'free' }, extra: { build: 'ci-42' } });
1100
1115
  Sauron.setTag('tier', 'pro'); // scope beats init default
1101
1116
  Sauron.track('upgraded', {}, { tags: { tier: 'trial' } });
1102
1117
  // -> event tags: { tier: 'trial' }, extra: { build: 'ci-42' }
@@ -1116,8 +1131,8 @@ Sauron.track('upgraded', {}, { tags: { tier: 'trial' } });
1116
1131
 
1117
1132
  ```html
1118
1133
  <script type="module">
1119
- import { Sauron } from 'https://esm.sh/@edraj/sauron-browser@1.5.0';
1120
- Sauron.init({ dsn: 'https://pk_test@ingest.example.com/42' });
1134
+ import { Sauron } from 'https://esm.sh/@edraj/sauron-browser@1.7.0';
1135
+ Sauron.init({ dsn: 'https://pk_test@ingest.example.com/42', release: 'web@1.4.2' });
1121
1136
  </script>
1122
1137
  ```
1123
1138
 
@@ -1228,7 +1243,8 @@ npm run dev # tsup --watch
1228
1243
 
1229
1244
  ## License
1230
1245
 
1231
- AGPL-3.0-only — GNU Affero General Public License v3.0.
1246
+ LGPL-3.0-only — GNU Lesser General Public License v3.0. LGPLv3 applies on top of
1247
+ the GNU GPL v3, whose text ships alongside it in `COPYING`.
1232
1248
 
1233
1249
  Repo: <https://github.com/edraj/sauron> — wiki:
1234
1250
  <https://github.com/edraj/sauron/wiki>
package/dist/index.cjs CHANGED
@@ -8,7 +8,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
8
8
 
9
9
  // src/utils.ts
10
10
  var SDK_NAME = "sauron.javascript";
11
- var SDK_VERSION = "1.5.0";
11
+ var SDK_VERSION = "1.7.0";
12
12
  function getGlobal() {
13
13
  return globalThis;
14
14
  }
@@ -843,12 +843,12 @@ function installHistory() {
843
843
  const loc = g2.location;
844
844
  if (!hist) return;
845
845
  let lastPath = toPath(loc?.href ?? null, loc?.href);
846
- const emit = (toHref) => {
846
+ const emit = (toHref, operation) => {
847
847
  const to = toPath(toHref, loc?.href);
848
848
  const from = lastPath;
849
849
  lastPath = to;
850
850
  if (from === to) return;
851
- withInternal(() => addNavigationBreadcrumb(from, to));
851
+ withInternal(() => addNavigationBreadcrumb(from, to, operation));
852
852
  if (to && navHandler) {
853
853
  try {
854
854
  navHandler(to);
@@ -859,11 +859,12 @@ function installHistory() {
859
859
  const wrap = (name) => {
860
860
  const original = hist[name];
861
861
  if (typeof original !== "function" || isWrapped(original)) return;
862
+ const operation = name === "pushState" ? "push" : "replace";
862
863
  hist[name] = markWrapped(function sauronHistory(...args) {
863
864
  const result = original.apply(this, args);
864
865
  if (!isInternal()) {
865
866
  const urlArg = args[2];
866
- emit(urlArg != null ? String(urlArg) : loc?.href ?? null);
867
+ emit(urlArg != null ? String(urlArg) : loc?.href ?? null, operation);
867
868
  }
868
869
  return result;
869
870
  });
@@ -875,7 +876,7 @@ function installHistory() {
875
876
  wrap("replaceState");
876
877
  if (typeof g2.addEventListener === "function") {
877
878
  const onPopState = () => {
878
- if (!isInternal()) emit(loc?.href ?? null);
879
+ if (!isInternal()) emit(loc?.href ?? null, "pop");
879
880
  };
880
881
  g2.addEventListener("popstate", onPopState);
881
882
  registerPatch("popstate", () => {
@@ -2174,10 +2175,13 @@ function resolveOptions(options) {
2174
2175
  if (!options || typeof options.dsn !== "string" || options.dsn.length === 0) {
2175
2176
  throw new Error("[sauron] init() requires a `dsn`");
2176
2177
  }
2178
+ if (typeof options.release !== "string" || options.release.trim().length === 0) {
2179
+ throw new Error("[sauron] init() requires a `release` (the app version this build reports as)");
2180
+ }
2177
2181
  const t = options.transport ?? {};
2178
2182
  return {
2179
2183
  dsn: options.dsn,
2180
- release: options.release ?? null,
2184
+ release: options.release.trim(),
2181
2185
  sampleRate: clamp(options.sampleRate ?? 1, 0, 1),
2182
2186
  maxBreadcrumbs: options.maxBreadcrumbs ?? 50,
2183
2187
  tags: options.tags ?? {},
@@ -2226,13 +2230,13 @@ function addBreadcrumb(input, hint) {
2226
2230
  if (!client) return;
2227
2231
  client.addBreadcrumb(normalizeBreadcrumb(input), hint);
2228
2232
  }
2229
- function addNavigationBreadcrumb(from, to) {
2233
+ function addNavigationBreadcrumb(from, to, operation = "push") {
2230
2234
  addBreadcrumb({
2231
2235
  type: "navigation",
2232
2236
  category: "history",
2233
2237
  level: "info",
2234
2238
  message: null,
2235
- data: { from, to }
2239
+ data: { from, to, operation }
2236
2240
  });
2237
2241
  }
2238
2242