@goliapkg/sentori-react-native 0.5.0 → 0.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/handlers/network.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,qBAAqB,QAAO,IA8DxC,CAAC"}
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/handlers/network.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,qBAAqB,QAAO,IAKxC,CAAC"}
@@ -1,17 +1,31 @@
1
1
  import { startSpan } from '@goliapkg/sentori-core';
2
2
  import { addBreadcrumb } from '../breadcrumbs';
3
+ import { getConfig } from '../config';
3
4
  let _installed = false;
4
5
  const AUTH_PARAMS = ['token', 'key', 'password', 'secret', 'access_token'];
6
+ // Requests to our own ingest endpoint shouldn't be traced — otherwise
7
+ // every span upload spawns another http.client span, and so on.
8
+ const isIngestUrl = (url) => {
9
+ const base = getConfig()?.ingestUrl;
10
+ return !!base && url.startsWith(base);
11
+ };
5
12
  export const installNetworkHandler = () => {
6
13
  if (_installed)
7
14
  return;
15
+ _installed = true;
16
+ patchFetch();
17
+ patchXhr();
18
+ };
19
+ // ── fetch ──────────────────────────────────────────────────────────
20
+ function patchFetch() {
8
21
  if (typeof globalThis.fetch !== 'function')
9
22
  return;
10
- _installed = true;
11
23
  const original = globalThis.fetch;
12
24
  globalThis.fetch = (async (input, init) => {
13
25
  const start = Date.now();
14
26
  const url = extractUrl(input);
27
+ if (isIngestUrl(url))
28
+ return original(input, init);
15
29
  const scrubbed = scrubUrl(url);
16
30
  const method = (init?.method ??
17
31
  (typeof input !== 'string' && 'method' in input
@@ -63,7 +77,79 @@ export const installNetworkHandler = () => {
63
77
  throw e;
64
78
  }
65
79
  });
66
- };
80
+ }
81
+ function patchXhr() {
82
+ const XHR = globalThis.XMLHttpRequest;
83
+ if (typeof XHR !== 'function')
84
+ return;
85
+ const proto = XHR.prototype;
86
+ if (proto.__sentoriPatched)
87
+ return;
88
+ proto.__sentoriPatched = true;
89
+ const originalOpen = proto.open;
90
+ const originalSend = proto.send;
91
+ const originalSetHeader = proto.setRequestHeader;
92
+ proto.open = function (method, url, ...rest) {
93
+ this.__sentoriMethod = String(method).toUpperCase();
94
+ this.__sentoriUrl = typeof url === 'string' ? url : String(url);
95
+ // @ts-expect-error variadic forwarding to the native signature
96
+ return originalOpen.call(this, method, url, ...rest);
97
+ };
98
+ proto.send = function (body) {
99
+ if (isIngestUrl(this.__sentoriUrl ?? ''))
100
+ return originalSend.call(this, body);
101
+ const method = this.__sentoriMethod ?? 'GET';
102
+ const url = scrubUrl(this.__sentoriUrl ?? '');
103
+ const span = startSpan('http.client', {
104
+ name: `${method} ${url}`,
105
+ tags: { 'http.method': method, 'http.url': url },
106
+ });
107
+ this.__sentoriSpan = span;
108
+ this.__sentoriStart = Date.now();
109
+ // setRequestHeader must be called between open() and send(); we're
110
+ // inside send() before the underlying call, so this is legal.
111
+ try {
112
+ originalSetHeader.call(this, 'traceparent', toTraceparent(span.traceId, span.spanId));
113
+ }
114
+ catch {
115
+ // Some XHR polyfills are strict about header timing; if it
116
+ // rejects, drop the header rather than fail the request.
117
+ }
118
+ const finish = () => {
119
+ const s = this.__sentoriSpan;
120
+ if (!s)
121
+ return;
122
+ this.__sentoriSpan = undefined;
123
+ const status = this.status;
124
+ s.setTag('http.status', String(status));
125
+ // status 0 means network error / aborted / CORS block — treat
126
+ // as error. The `abort` event handler below downgrades aborts.
127
+ s.finish({ status: status === 0 || status >= 400 ? 'error' : 'ok' });
128
+ addBreadcrumb({
129
+ type: 'net',
130
+ data: {
131
+ method,
132
+ url,
133
+ status,
134
+ durationMs: Date.now() - (this.__sentoriStart ?? Date.now()),
135
+ },
136
+ });
137
+ };
138
+ this.addEventListener('loadend', finish);
139
+ this.addEventListener('abort', () => {
140
+ const s = this.__sentoriSpan;
141
+ if (!s)
142
+ return;
143
+ this.__sentoriSpan = undefined;
144
+ s.finish({ status: 'cancelled' });
145
+ addBreadcrumb({
146
+ type: 'net',
147
+ data: { method, url, status: 0, durationMs: Date.now() - (this.__sentoriStart ?? Date.now()), error: 'aborted' },
148
+ });
149
+ });
150
+ return originalSend.call(this, body);
151
+ };
152
+ }
67
153
  function mergeHeaders(input, init) {
68
154
  const out = new Headers();
69
155
  if (typeof input !== 'string' && !(input instanceof URL)) {
@@ -1 +1 @@
1
- {"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/handlers/network.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/handlers/network.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,IAAI,UAAU,GAAG,KAAK,CAAC;AAEvB,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;AAE3E,sEAAsE;AACtE,gEAAgE;AAChE,MAAM,WAAW,GAAG,CAAC,GAAW,EAAW,EAAE;IAC3C,MAAM,IAAI,GAAG,SAAS,EAAE,EAAE,SAAS,CAAC;IACpC,OAAO,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAS,EAAE;IAC9C,IAAI,UAAU;QAAE,OAAO;IACvB,UAAU,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,CAAC;IACb,QAAQ,EAAE,CAAC;AACb,CAAC,CAAC;AAEF,sEAAsE;AAEtE,SAAS,UAAU;IACjB,IAAI,OAAO,UAAU,CAAC,KAAK,KAAK,UAAU;QAAE,OAAO;IACnD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC;IAElC,UAAU,CAAC,KAAK,GAAG,CAAC,KAAK,EAAE,KAAwB,EAAE,IAAkB,EAAE,EAAE;QACzE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,WAAW,CAAC,GAAG,CAAC;YAAE,OAAO,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM;YAC1B,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,QAAQ,IAAK,KAAiB;gBAC1D,CAAC,CAAE,KAAiB,CAAC,MAAM;gBAC3B,CAAC,CAAC,KAAK,CAAC,CAAW,CAAC;QAExB,+DAA+D;QAC/D,8DAA8D;QAC9D,iEAAiE;QACjE,8DAA8D;QAC9D,MAAM,IAAI,GAAG,SAAS,CAAC,aAAa,EAAE;YACpC,IAAI,EAAE,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,QAAQ,EAAE;YAC3C,IAAI,EAAE,EAAE,aAAa,EAAE,MAAM,CAAC,WAAW,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE;SACpE,CAAC,CAAC;QAEH,kDAAkD;QAClD,MAAM,OAAO,GAAgB,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;QACjD,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACrE,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC;QAE1B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAChD,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAC7D,aAAa,CAAC;gBACZ,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE;oBACJ,MAAM;oBACN,GAAG,EAAE,QAAQ;oBACb,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;iBAC/B;aACF,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;YAChC,IAAI,CAAC,YAAY,KAAK;gBAAE,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;YAChE,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YACzD,aAAa,CAAC;gBACZ,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE;oBACJ,MAAM;oBACN,GAAG,EAAE,QAAQ;oBACb,MAAM,EAAE,CAAC;oBACT,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;oBAC9B,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;iBACjB;aACF,CAAC,CAAC;YACH,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC,CAAiB,CAAC;AACrB,CAAC;AAiBD,SAAS,QAAQ;IACf,MAAM,GAAG,GAAI,UAAyD,CAAC,cAAc,CAAC;IACtF,IAAI,OAAO,GAAG,KAAK,UAAU;QAAE,OAAO;IACtC,MAAM,KAAK,GAAG,GAAG,CAAC,SAEjB,CAAC;IACF,IAAI,KAAK,CAAC,gBAAgB;QAAE,OAAO;IACnC,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAE9B,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC;IAChC,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC;IAChC,MAAM,iBAAiB,GAAG,KAAK,CAAC,gBAAgB,CAAC;IAEjD,KAAK,CAAC,IAAI,GAAG,UAEX,MAAc,EACd,GAAiB,EACjB,GAAG,IAAe;QAElB,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QACpD,IAAI,CAAC,YAAY,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChE,+DAA+D;QAC/D,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACvD,CAAC,CAAC;IAEF,KAAK,CAAC,IAAI,GAAG,UAA2B,IAA+C;QACrF,IAAI,WAAW,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;YAAE,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,IAAI,KAAK,CAAC;QAC7C,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,SAAS,CAAC,aAAa,EAAE;YACpC,IAAI,EAAE,GAAG,MAAM,IAAI,GAAG,EAAE;YACxB,IAAI,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE;SACjD,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEjC,mEAAmE;QACnE,8DAA8D;QAC9D,IAAI,CAAC;YACH,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,EAAE,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACxF,CAAC;QAAC,MAAM,CAAC;YACP,2DAA2D;YAC3D,yDAAyD;QAC3D,CAAC;QAED,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,MAAM,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC;YAC7B,IAAI,CAAC,CAAC;gBAAE,OAAO;YACf,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;YAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YAC3B,CAAC,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YACxC,8DAA8D;YAC9D,+DAA+D;YAC/D,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC,IAAI,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YACrE,aAAa,CAAC;gBACZ,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE;oBACJ,MAAM;oBACN,GAAG;oBACH,MAAM;oBACN,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;iBAC7D;aACF,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YAClC,MAAM,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC;YAC7B,IAAI,CAAC,CAAC;gBAAE,OAAO;YACf,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;YAC/B,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;YAClC,aAAa,CAAC;gBACZ,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE;aACjH,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAwB,EAAE,IAAkB;IAChE,MAAM,GAAG,GAAG,IAAI,OAAO,EAAE,CAAC;IAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,YAAY,GAAG,CAAC,EAAE,CAAC;QACxD,KAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,IAAI,IAAI,EAAE,OAAO,EAAE,CAAC;QAClB,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,aAAa,CAAC,OAAe,EAAE,MAAc;IACpD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnE,OAAO,MAAM,KAAK,IAAI,MAAM,KAAK,CAAC;AACpC,CAAC;AAED,SAAS,YAAY,CAAC,GAAY;IAChC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC1D,OAAQ,GAA0B,CAAC,IAAI,KAAK,YAAY,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,KAAwB,EAAU,EAAE;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,KAAK,YAAY,GAAG;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IAC5C,OAAQ,KAAiB,CAAC,GAAG,CAAC;AAChC,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAU,EAAE;IACvC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;YAC5B,IAAI,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1B,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;gBACpC,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import type { Event } from './types';
2
2
  export declare const enqueue: (event: Event) => void;
3
3
  export declare const startTransport: () => void;
4
+ export declare const flushSpans: () => Promise<void>;
4
5
  export declare const flush: () => Promise<void>;
5
6
  export declare const drainOfflineQueue: () => Promise<void>;
6
7
  export declare const __resetForTests: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAcrC,eAAO,MAAM,OAAO,GAAI,OAAO,KAAK,KAAG,IAUtC,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,IAEjC,CAAC;AAEF,eAAO,MAAM,KAAK,QAAa,OAAO,CAAC,IAAI,CAkB1C,CAAC;AA4FF,eAAO,MAAM,iBAAiB,QAAa,OAAO,CAAC,IAAI,CAatD,CAAC;AAEF,eAAO,MAAM,eAAe,QAAO,IAKlC,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,SAAS,KAAK,EAAY,CAAC;AAE1D;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,MAAM,EACjB,OAAO,MAAM,EACb,MAAM,OAAO,KACZ,OAAO,CAAC,IAAI,CAcd,CAAC"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAqBrC,eAAO,MAAM,OAAO,GAAI,OAAO,KAAK,KAAG,IAUtC,CAAC;AAEF,eAAO,MAAM,cAAc,QAAO,IAOjC,CAAC;AAEF,eAAO,MAAM,UAAU,QAAa,OAAO,CAAC,IAAI,CAe/C,CAAC;AAqBF,eAAO,MAAM,KAAK,QAAa,OAAO,CAAC,IAAI,CAkB1C,CAAC;AA4FF,eAAO,MAAM,iBAAiB,QAAa,OAAO,CAAC,IAAI,CAatD,CAAC;AAEF,eAAO,MAAM,eAAe,QAAO,IAOlC,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,SAAS,KAAK,EAAY,CAAC;AAE1D;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,MAAM,EACjB,OAAO,MAAM,EACb,MAAM,OAAO,KACZ,OAAO,CAAC,IAAI,CAcd,CAAC"}
package/lib/transport.js CHANGED
@@ -1,11 +1,18 @@
1
+ import { drainSpans } from '@goliapkg/sentori-core';
1
2
  import { getConfig } from './config';
2
3
  const FLUSH_INTERVAL_MS = 5_000;
3
4
  const BATCH_SIZE = 10;
4
5
  const MAX_RETRY = 3;
5
6
  const STORAGE_KEY = '@sentori/pending';
6
7
  const MAX_PERSISTED = 1000;
8
+ // Spans are higher-volume and lower-value than error events: we batch
9
+ // them on their own timer, cap each request at the server's per-batch
10
+ // limit, and drop on failure rather than persisting offline.
11
+ const SPAN_FLUSH_INTERVAL_MS = 10_000;
12
+ const SPAN_BATCH_MAX = 200;
7
13
  let _queue = [];
8
14
  let _flushTimer = null;
15
+ let _spanTimer = null;
9
16
  let _started = false;
10
17
  const SDK_VERSION = '0.0.0';
11
18
  export const enqueue = (event) => {
@@ -22,6 +29,46 @@ export const enqueue = (event) => {
22
29
  };
23
30
  export const startTransport = () => {
24
31
  _started = true;
32
+ if (!_spanTimer) {
33
+ _spanTimer = setInterval(() => {
34
+ void flushSpans();
35
+ }, SPAN_FLUSH_INTERVAL_MS);
36
+ }
37
+ };
38
+ export const flushSpans = async () => {
39
+ if (!_started)
40
+ return;
41
+ const spans = drainSpans();
42
+ if (spans.length === 0)
43
+ return;
44
+ const config = getConfig();
45
+ if (!config)
46
+ return;
47
+ for (let i = 0; i < spans.length; i += SPAN_BATCH_MAX) {
48
+ const chunk = spans.slice(i, i + SPAN_BATCH_MAX);
49
+ try {
50
+ await sendSpansOnce(chunk, config.ingestUrl, config.token);
51
+ }
52
+ catch {
53
+ // drop the remaining chunks — span uploads aren't worth retrying
54
+ break;
55
+ }
56
+ }
57
+ };
58
+ const sendSpansOnce = async (spans, ingestUrl, token) => {
59
+ const resp = await fetch(`${ingestUrl}/v1/spans:batch`, {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ Authorization: `Bearer ${token}`,
64
+ 'Sentori-Sdk': `react-native/${SDK_VERSION}`,
65
+ },
66
+ body: JSON.stringify({ spans }),
67
+ });
68
+ // 5xx: server overloaded. 4xx (bad token / quota / oversized): also
69
+ // pointless to keep sending. Either way, stop the rest of the batch.
70
+ if (resp.status >= 400)
71
+ throw new Error(`spans-${resp.status}`);
25
72
  };
26
73
  export const flush = async () => {
27
74
  if (!_started)
@@ -137,6 +184,9 @@ export const __resetForTests = () => {
137
184
  if (_flushTimer)
138
185
  clearTimeout(_flushTimer);
139
186
  _flushTimer = null;
187
+ if (_spanTimer)
188
+ clearInterval(_spanTimer);
189
+ _spanTimer = null;
140
190
  _started = false;
141
191
  };
142
192
  export const __peekQueue = () => _queue;
@@ -1 +1 @@
1
- {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@goliapkg/sentori-react-native",
3
- "version": "0.5.0",
4
- "description": "Sentori SDK for React Native JS-layer error capture, native crash handlers (iOS / Android), batched transport, fetch + react-navigation tracing.",
3
+ "version": "0.5.2",
4
+ "description": "Sentori SDK for React Native \u2014 JS-layer error capture, native crash handlers (iOS / Android), batched transport, fetch + react-navigation tracing.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://sentori.golia.jp",
7
7
  "repository": {
@@ -33,9 +33,38 @@ const recorder = (async (input: Request | string | URL, init?: RequestInit) => {
33
33
  return new Response('', { status: r.status });
34
34
  }) as unknown as typeof fetch;
35
35
 
36
+ // Fake XMLHttpRequest so installNetworkHandler()'s patchXhr() has a
37
+ // prototype to patch. RN's native XHR isn't present in bun:test.
38
+ type FakeListener = () => void;
39
+ class FakeXHR {
40
+ status = 0;
41
+ private listeners: Record<string, FakeListener[]> = {};
42
+ private requestHeaders: Record<string, string> = {};
43
+ private opened = false;
44
+
45
+ open(_method: string, _url: string | URL): void {
46
+ this.opened = true;
47
+ }
48
+ setRequestHeader(name: string, value: string): void {
49
+ if (!this.opened) throw new Error('setRequestHeader before open');
50
+ this.requestHeaders[name.toLowerCase()] = value;
51
+ }
52
+ send(_body?: unknown): void {}
53
+ addEventListener(event: string, fn: FakeListener): void {
54
+ (this.listeners[event] ??= []).push(fn);
55
+ }
56
+ getHeader(name: string): string | undefined {
57
+ return this.requestHeaders[name.toLowerCase()];
58
+ }
59
+ fire(event: string): void {
60
+ for (const fn of this.listeners[event] ?? []) fn();
61
+ }
62
+ }
63
+
36
64
  beforeAll(() => {
37
65
  (globalThis as { fetch: typeof fetch }).fetch = recorder;
38
- installNetworkHandler();
66
+ (globalThis as { XMLHttpRequest: unknown }).XMLHttpRequest = FakeXHR as unknown;
67
+ installNetworkHandler(); // patches globalThis.fetch + XMLHttpRequest.prototype
39
68
  });
40
69
 
41
70
  beforeEach(() => {
@@ -106,3 +135,58 @@ describe('RN network handler tracing', () => {
106
135
  expect(h.get('traceparent')).toBeTruthy();
107
136
  });
108
137
  });
138
+
139
+ describe('RN XHR tracing (axios goes through XHR on RN)', () => {
140
+ test('patched XHR emits an http.client span on loadend', () => {
141
+ const x = new FakeXHR();
142
+ x.open('POST', 'https://api.example.com/v1/orders');
143
+ x.send('{}');
144
+ x.status = 201;
145
+ x.fire('loadend');
146
+
147
+ const sp = drainSpans()[0]!;
148
+ expect(sp.op).toBe('http.client');
149
+ expect(sp.name).toBe('POST https://api.example.com/v1/orders');
150
+ expect(sp.tags).toMatchObject({
151
+ 'http.method': 'POST',
152
+ 'http.status': '201',
153
+ 'http.url': 'https://api.example.com/v1/orders',
154
+ });
155
+ expect(sp.status).toBe('ok');
156
+ });
157
+
158
+ test('injects W3C traceparent request header', () => {
159
+ const x = new FakeXHR();
160
+ x.open('GET', 'https://api.example.com/x');
161
+ x.send();
162
+ expect(x.getHeader('traceparent')).toMatch(/^00-[0-9a-f]{32}-[0-9a-f]{16}-01$/);
163
+ x.status = 200;
164
+ x.fire('loadend');
165
+ });
166
+
167
+ test('5xx → span.status = "error"', () => {
168
+ const x = new FakeXHR();
169
+ x.open('GET', 'https://api.example.com/x');
170
+ x.send();
171
+ x.status = 502;
172
+ x.fire('loadend');
173
+ expect(drainSpans()[0]?.status).toBe('error');
174
+ });
175
+
176
+ test('status 0 → span.status = "error"', () => {
177
+ const x = new FakeXHR();
178
+ x.open('GET', 'https://api.example.com/x');
179
+ x.send();
180
+ x.status = 0;
181
+ x.fire('loadend');
182
+ expect(drainSpans()[0]?.status).toBe('error');
183
+ });
184
+
185
+ test('abort → span.status = "cancelled"', () => {
186
+ const x = new FakeXHR();
187
+ x.open('GET', 'https://api.example.com/x');
188
+ x.send();
189
+ x.fire('abort');
190
+ expect(drainSpans()[0]?.status).toBe('cancelled');
191
+ });
192
+ });
@@ -7,10 +7,13 @@ import {
7
7
  mock,
8
8
  } from 'bun:test';
9
9
 
10
+ import { clearSpans, startSpan } from '@goliapkg/sentori-core';
11
+
10
12
  import { setConfig, __resetForTests as resetConfig } from '../config';
11
13
  import {
12
14
  enqueue,
13
15
  flush,
16
+ flushSpans,
14
17
  startTransport,
15
18
  __resetForTests as resetTransport,
16
19
  __peekQueue,
@@ -168,3 +171,77 @@ describe('transport', () => {
168
171
  expect(__peekQueue()).toHaveLength(0);
169
172
  });
170
173
  });
174
+
175
+ describe('span flush', () => {
176
+ beforeEach(() => {
177
+ resetConfig();
178
+ resetTransport();
179
+ clearSpans();
180
+ setConfig({
181
+ token: 'st_pk_test',
182
+ release: 'app@1.0.0+1',
183
+ environment: 'test',
184
+ ingestUrl: 'http://localhost:8080',
185
+ enabled: true,
186
+ });
187
+ startTransport();
188
+ });
189
+ afterEach(() => {
190
+ globalThis.fetch = originalFetch;
191
+ clearSpans();
192
+ });
193
+
194
+ it('POSTs buffered spans to /v1/spans:batch', async () => {
195
+ const calls: { url: string; init?: RequestInit }[] = [];
196
+ globalThis.fetch = mock(async (url: string | URL | Request, init?: RequestInit) => {
197
+ calls.push({ url: String(url), init });
198
+ return new Response(null, { status: 202 });
199
+ }) as typeof fetch;
200
+
201
+ startSpan('http.client', { name: 'GET /a' }).finish({ status: 'ok' });
202
+ startSpan('http.client', { name: 'GET /b' }).finish({ status: 'error' });
203
+ await flushSpans();
204
+
205
+ expect(calls).toHaveLength(1);
206
+ expect(calls[0]?.url).toBe('http://localhost:8080/v1/spans:batch');
207
+ const body = JSON.parse((calls[0]?.init?.body as string) ?? '{}');
208
+ expect(body.spans).toHaveLength(2);
209
+ expect(body.spans[0]).toMatchObject({ op: 'http.client', name: 'GET /a', status: 'ok' });
210
+ });
211
+
212
+ it('no-op when the span buffer is empty', async () => {
213
+ let attempts = 0;
214
+ globalThis.fetch = mock(async () => {
215
+ attempts++;
216
+ return new Response(null, { status: 202 });
217
+ }) as typeof fetch;
218
+ await flushSpans();
219
+ expect(attempts).toBe(0);
220
+ });
221
+
222
+ it('splits >200 spans into multiple batches', async () => {
223
+ const sizes: number[] = [];
224
+ globalThis.fetch = mock(async (_url: string | URL | Request, init?: RequestInit) => {
225
+ const body = JSON.parse((init?.body as string) ?? '{}');
226
+ sizes.push(body.spans.length);
227
+ return new Response(null, { status: 202 });
228
+ }) as typeof fetch;
229
+
230
+ for (let i = 0; i < 450; i++) startSpan('http.client', { name: `GET /${i}` }).finish();
231
+ await flushSpans();
232
+
233
+ expect(sizes).toEqual([200, 200, 50]);
234
+ });
235
+
236
+ it('drops on 5xx without retrying', async () => {
237
+ let attempts = 0;
238
+ globalThis.fetch = mock(async () => {
239
+ attempts++;
240
+ return new Response('boom', { status: 503 });
241
+ }) as typeof fetch;
242
+
243
+ startSpan('http.client', { name: 'GET /a' }).finish();
244
+ await flushSpans();
245
+ expect(attempts).toBe(1);
246
+ });
247
+ });
@@ -1,21 +1,36 @@
1
1
  import { startSpan } from '@goliapkg/sentori-core';
2
2
 
3
3
  import { addBreadcrumb } from '../breadcrumbs';
4
+ import { getConfig } from '../config';
4
5
 
5
6
  let _installed = false;
6
7
 
7
8
  const AUTH_PARAMS = ['token', 'key', 'password', 'secret', 'access_token'];
8
9
 
10
+ // Requests to our own ingest endpoint shouldn't be traced — otherwise
11
+ // every span upload spawns another http.client span, and so on.
12
+ const isIngestUrl = (url: string): boolean => {
13
+ const base = getConfig()?.ingestUrl;
14
+ return !!base && url.startsWith(base);
15
+ };
16
+
9
17
  export const installNetworkHandler = (): void => {
10
18
  if (_installed) return;
11
- if (typeof globalThis.fetch !== 'function') return;
12
19
  _installed = true;
20
+ patchFetch();
21
+ patchXhr();
22
+ };
13
23
 
24
+ // ── fetch ──────────────────────────────────────────────────────────
25
+
26
+ function patchFetch(): void {
27
+ if (typeof globalThis.fetch !== 'function') return;
14
28
  const original = globalThis.fetch;
15
29
 
16
30
  globalThis.fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
17
31
  const start = Date.now();
18
32
  const url = extractUrl(input);
33
+ if (isIngestUrl(url)) return original(input, init);
19
34
  const scrubbed = scrubUrl(url);
20
35
  const method = (init?.method ??
21
36
  (typeof input !== 'string' && 'method' in (input as Request)
@@ -68,8 +83,104 @@ export const installNetworkHandler = (): void => {
68
83
  throw e;
69
84
  }
70
85
  }) as typeof fetch;
86
+ }
87
+
88
+ // ── XMLHttpRequest ─────────────────────────────────────────────────
89
+ //
90
+ // React Native's XHR is a native polyfill, not built on fetch — so
91
+ // patching `globalThis.fetch` alone misses every axios / older-style
92
+ // request. axios on RN uses its `xhr` adapter by default. We patch
93
+ // the prototype's `open` + `send` so the instance carries the span
94
+ // from `send()` to `loadend`.
95
+
96
+ type TracedXhr = XMLHttpRequest & {
97
+ __sentoriMethod?: string;
98
+ __sentoriUrl?: string;
99
+ __sentoriSpan?: ReturnType<typeof startSpan>;
100
+ __sentoriStart?: number;
71
101
  };
72
102
 
103
+ function patchXhr(): void {
104
+ const XHR = (globalThis as { XMLHttpRequest?: typeof XMLHttpRequest }).XMLHttpRequest;
105
+ if (typeof XHR !== 'function') return;
106
+ const proto = XHR.prototype as XMLHttpRequest & {
107
+ __sentoriPatched?: boolean;
108
+ };
109
+ if (proto.__sentoriPatched) return;
110
+ proto.__sentoriPatched = true;
111
+
112
+ const originalOpen = proto.open;
113
+ const originalSend = proto.send;
114
+ const originalSetHeader = proto.setRequestHeader;
115
+
116
+ proto.open = function (
117
+ this: TracedXhr,
118
+ method: string,
119
+ url: string | URL,
120
+ ...rest: unknown[]
121
+ ): void {
122
+ this.__sentoriMethod = String(method).toUpperCase();
123
+ this.__sentoriUrl = typeof url === 'string' ? url : String(url);
124
+ // @ts-expect-error variadic forwarding to the native signature
125
+ return originalOpen.call(this, method, url, ...rest);
126
+ };
127
+
128
+ proto.send = function (this: TracedXhr, body?: Document | XMLHttpRequestBodyInit | null): void {
129
+ if (isIngestUrl(this.__sentoriUrl ?? '')) return originalSend.call(this, body);
130
+ const method = this.__sentoriMethod ?? 'GET';
131
+ const url = scrubUrl(this.__sentoriUrl ?? '');
132
+ const span = startSpan('http.client', {
133
+ name: `${method} ${url}`,
134
+ tags: { 'http.method': method, 'http.url': url },
135
+ });
136
+ this.__sentoriSpan = span;
137
+ this.__sentoriStart = Date.now();
138
+
139
+ // setRequestHeader must be called between open() and send(); we're
140
+ // inside send() before the underlying call, so this is legal.
141
+ try {
142
+ originalSetHeader.call(this, 'traceparent', toTraceparent(span.traceId, span.spanId));
143
+ } catch {
144
+ // Some XHR polyfills are strict about header timing; if it
145
+ // rejects, drop the header rather than fail the request.
146
+ }
147
+
148
+ const finish = () => {
149
+ const s = this.__sentoriSpan;
150
+ if (!s) return;
151
+ this.__sentoriSpan = undefined;
152
+ const status = this.status;
153
+ s.setTag('http.status', String(status));
154
+ // status 0 means network error / aborted / CORS block — treat
155
+ // as error. The `abort` event handler below downgrades aborts.
156
+ s.finish({ status: status === 0 || status >= 400 ? 'error' : 'ok' });
157
+ addBreadcrumb({
158
+ type: 'net',
159
+ data: {
160
+ method,
161
+ url,
162
+ status,
163
+ durationMs: Date.now() - (this.__sentoriStart ?? Date.now()),
164
+ },
165
+ });
166
+ };
167
+
168
+ this.addEventListener('loadend', finish);
169
+ this.addEventListener('abort', () => {
170
+ const s = this.__sentoriSpan;
171
+ if (!s) return;
172
+ this.__sentoriSpan = undefined;
173
+ s.finish({ status: 'cancelled' });
174
+ addBreadcrumb({
175
+ type: 'net',
176
+ data: { method, url, status: 0, durationMs: Date.now() - (this.__sentoriStart ?? Date.now()), error: 'aborted' },
177
+ });
178
+ });
179
+
180
+ return originalSend.call(this, body);
181
+ };
182
+ }
183
+
73
184
  function mergeHeaders(input: RequestInfo | URL, init?: RequestInit): Headers {
74
185
  const out = new Headers();
75
186
  if (typeof input !== 'string' && !(input instanceof URL)) {
package/src/transport.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { drainSpans } from '@goliapkg/sentori-core';
2
+
1
3
  import { getConfig } from './config';
2
4
  import type { Event } from './types';
3
5
 
@@ -7,8 +9,15 @@ const MAX_RETRY = 3;
7
9
  const STORAGE_KEY = '@sentori/pending';
8
10
  const MAX_PERSISTED = 1000;
9
11
 
12
+ // Spans are higher-volume and lower-value than error events: we batch
13
+ // them on their own timer, cap each request at the server's per-batch
14
+ // limit, and drop on failure rather than persisting offline.
15
+ const SPAN_FLUSH_INTERVAL_MS = 10_000;
16
+ const SPAN_BATCH_MAX = 200;
17
+
10
18
  let _queue: Event[] = [];
11
19
  let _flushTimer: ReturnType<typeof setTimeout> | null = null;
20
+ let _spanTimer: ReturnType<typeof setInterval> | null = null;
12
21
  let _started = false;
13
22
 
14
23
  const SDK_VERSION = '0.0.0';
@@ -27,6 +36,47 @@ export const enqueue = (event: Event): void => {
27
36
 
28
37
  export const startTransport = (): void => {
29
38
  _started = true;
39
+ if (!_spanTimer) {
40
+ _spanTimer = setInterval(() => {
41
+ void flushSpans();
42
+ }, SPAN_FLUSH_INTERVAL_MS);
43
+ }
44
+ };
45
+
46
+ export const flushSpans = async (): Promise<void> => {
47
+ if (!_started) return;
48
+ const spans = drainSpans();
49
+ if (spans.length === 0) return;
50
+ const config = getConfig();
51
+ if (!config) return;
52
+ for (let i = 0; i < spans.length; i += SPAN_BATCH_MAX) {
53
+ const chunk = spans.slice(i, i + SPAN_BATCH_MAX);
54
+ try {
55
+ await sendSpansOnce(chunk, config.ingestUrl, config.token);
56
+ } catch {
57
+ // drop the remaining chunks — span uploads aren't worth retrying
58
+ break;
59
+ }
60
+ }
61
+ };
62
+
63
+ const sendSpansOnce = async (
64
+ spans: unknown[],
65
+ ingestUrl: string,
66
+ token: string,
67
+ ): Promise<void> => {
68
+ const resp = await fetch(`${ingestUrl}/v1/spans:batch`, {
69
+ method: 'POST',
70
+ headers: {
71
+ 'Content-Type': 'application/json',
72
+ Authorization: `Bearer ${token}`,
73
+ 'Sentori-Sdk': `react-native/${SDK_VERSION}`,
74
+ },
75
+ body: JSON.stringify({ spans }),
76
+ });
77
+ // 5xx: server overloaded. 4xx (bad token / quota / oversized): also
78
+ // pointless to keep sending. Either way, stop the rest of the batch.
79
+ if (resp.status >= 400) throw new Error(`spans-${resp.status}`);
30
80
  };
31
81
 
32
82
  export const flush = async (): Promise<void> => {
@@ -158,6 +208,8 @@ export const __resetForTests = (): void => {
158
208
  _queue = [];
159
209
  if (_flushTimer) clearTimeout(_flushTimer);
160
210
  _flushTimer = null;
211
+ if (_spanTimer) clearInterval(_spanTimer);
212
+ _spanTimer = null;
161
213
  _started = false;
162
214
  };
163
215