noibu-react-native 0.2.35-rc.4 → 0.2.35-rc.6

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.
@@ -66,5 +66,5 @@ def kotlin_version = getExtOrDefault("kotlinVersion")
66
66
  dependencies {
67
67
  implementation "com.facebook.react:react-native:+"
68
68
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
69
- implementation "com.noibu:sessionreplay-recorder:1.0.5-rc.2"
69
+ implementation "com.noibu:sessionreplay-recorder:1.0.5-rc.3"
70
70
  }
package/dist/constants.js CHANGED
@@ -24,7 +24,7 @@ const CONTENT_TYPE = 'content-type';
24
24
  * Gets the script id from the cookie object, returns default if cannot be found
25
25
  */
26
26
  function GET_SCRIPT_ID() {
27
- return "1.0.104-rn-sdk-0.2.35-rc.4" ;
27
+ return "1.0.104-rn-sdk-0.2.35-rc.6" ;
28
28
  }
29
29
  /**
30
30
  * Gets the max metro recon number
@@ -10,6 +10,7 @@ import { HTTPDataBundler } from './http-tools/HTTPDataBundler.js';
10
10
  import GqlErrorValidator from './http-tools/GqlErrorValidator.js';
11
11
  import { Singleton } from './BaseMonitor.js';
12
12
  import { Severity, PageVisitErrorSource } from 'noibu-metroplex-ts-bindings';
13
+ import { removePII } from '../utils/piiRedactor.js';
13
14
 
14
15
  /** request monitor */
15
16
  const BODY_USED_ERROR = 'Response data unavailable due to an improperly wrapped fetch call';
@@ -162,7 +163,15 @@ class RequestMonitor extends Singleton {
162
163
  ? yield GqlErrorValidator.fromFetchResponseText(url, options, request, status, responseText)
163
164
  : null;
164
165
  const isHttpError = isHttpCodeFailure(status);
165
- const maybeRequestText = shouldCaptureBody ? yield Promise.resolve((_a = request === null || request === void 0 ? void 0 : request.text) === null || _a === void 0 ? void 0 : _a.call(request)) : undefined;
166
+ let maybeRequestText;
167
+ if (shouldCaptureBody) {
168
+ try {
169
+ maybeRequestText = yield ((_a = request === null || request === void 0 ? void 0 : request.text) === null || _a === void 0 ? void 0 : _a.call(request));
170
+ }
171
+ catch (_b) {
172
+ // request body unavailable (e.g. whatwg-fetch polyfill "Already read") — proceed without it
173
+ }
174
+ }
166
175
  const responseTextForHttp = shouldCaptureBody
167
176
  ? responseText !== null && responseText !== void 0 ? responseText : BODY_USED_ERROR
168
177
  : undefined;
@@ -173,7 +182,7 @@ class RequestMonitor extends Singleton {
173
182
  const seq = saveHTTPEvent(httpEvent, httpData, !!gqlError);
174
183
  if (isHttpError) {
175
184
  // this does not consume response body so no need to clone it
176
- saveErrorToPagevisit(Object.assign(Object.assign({}, ogResponse), { type: PageVisitErrorSource.Response }), seq);
185
+ saveErrorToPagevisit(JSON.parse(removePII(JSON.stringify(Object.assign(Object.assign({}, ogResponse), { type: PageVisitErrorSource.Response })))), seq);
177
186
  }
178
187
  if (gqlError) {
179
188
  gqlError.forEach(error => saveErrorToPagevisit({
@@ -244,22 +253,28 @@ class RequestMonitor extends Singleton {
244
253
  * once and rebuild a fresh Response to avoid mutating the customer's bodyUsed.
245
254
  */
246
255
  const customerPromise = promiseFromOriginalFetch.then((ogResponse) => __awaiter(this, void 0, void 0, function* () {
247
- const shouldCheckGql = GqlErrorValidator.shouldCheckRequest(url, options, request);
248
- const shouldCaptureBody = HTTPDataBundler.getInstance().shouldContinueForURL(url);
249
- const shouldReadBody = shouldCaptureBody || shouldCheckGql;
250
- let customerResponse = ogResponse;
251
- let responseText;
252
- if (shouldReadBody) {
253
- const buffered = yield RequestMonitor.readResponseBody(ogResponse);
254
- if (buffered) {
255
- responseText = buffered.text;
256
- customerResponse = RequestMonitor.buildCustomerResponse(ogResponse, buffered.body);
256
+ try {
257
+ const shouldCheckGql = GqlErrorValidator.shouldCheckRequest(url, options, request);
258
+ const shouldCaptureBody = HTTPDataBundler.getInstance().shouldContinueForURL(url);
259
+ const shouldReadBody = shouldCaptureBody || shouldCheckGql;
260
+ let customerResponse = ogResponse;
261
+ let responseText;
262
+ if (shouldReadBody) {
263
+ const buffered = yield RequestMonitor.readResponseBody(ogResponse);
264
+ if (buffered) {
265
+ responseText = buffered.text;
266
+ customerResponse = RequestMonitor.buildCustomerResponse(ogResponse, buffered.body);
267
+ }
257
268
  }
269
+ RequestMonitor.handleFetchResponse(startTime, url, method, options, request, ogResponse, responseText, shouldCaptureBody, shouldCheckGql).catch(() => {
270
+ // errors are handled inside handleFetchResponse
271
+ });
272
+ return customerResponse;
273
+ }
274
+ catch (_a) {
275
+ // SDK processing failed — return original response so the customer is never affected
276
+ return ogResponse;
258
277
  }
259
- RequestMonitor.handleFetchResponse(startTime, url, method, options, request, ogResponse, responseText, shouldCaptureBody, shouldCheckGql).catch(() => {
260
- // errors are handled inside handleFetchResponse
261
- });
262
- return customerResponse;
263
278
  }));
264
279
  customerPromise.catch(err => {
265
280
  RequestMonitor.handleFetchFailure(err, url);
@@ -60,6 +60,7 @@ const exactFieldsToRedact = [
60
60
  'cardnumber',
61
61
  'email',
62
62
  ];
63
+ const MAX_NESTED_JSON_REDACTION_DEPTH = 5;
63
64
  /**
64
65
  * Redacts one string, returns redacted value or false if nothing to redact
65
66
  */
@@ -70,10 +71,12 @@ function shouldRedact(field) {
70
71
  /**
71
72
  * Try to parse content as a JSON object and
72
73
  * iterate it recursively removing PII.
73
- * Returns original content if nothing was changes or error is thrown.
74
- * @param {String} content
74
+ * Also handles string values that contain serialized JSON by parsing and redacting them recursively.
75
+ * Returns original content if nothing was changed or error is thrown.
76
+ * @param {String} content - The string to parse and redact PII from
77
+ * @param {Number} maxDepth - Maximum recursion depth for nested JSON strings (default: MAX_NESTED_JSON_REDACTION_DEPTH)
75
78
  */
76
- function tryParseObjectAndRemovePII(content) {
79
+ function tryParseObjectAndRemovePII(content, maxDepth = MAX_NESTED_JSON_REDACTION_DEPTH) {
77
80
  const first = content[0];
78
81
  const isParsable = first === '{' || first === '[';
79
82
  if (!isParsable) {
@@ -82,7 +85,7 @@ function tryParseObjectAndRemovePII(content) {
82
85
  let redacted = false;
83
86
  try {
84
87
  const instance = JSON.parse(content);
85
- iterateObjectRecursively(instance, (current, property) => {
88
+ iterateObjectRecursively(instance, (current, property, value) => {
86
89
  if (typeof property !== 'string') {
87
90
  return undefined;
88
91
  }
@@ -92,6 +95,23 @@ function tryParseObjectAndRemovePII(content) {
92
95
  redacted = true;
93
96
  return PII_REDACTION_REPLACEMENT_STRING;
94
97
  }
98
+ // If the value is a JSON string, parse it and redact recursively
99
+ // maxDepth guards against deeply nested structures affecting performance
100
+ if (typeof value === 'string' && maxDepth > 1) {
101
+ try {
102
+ const parsed = JSON.parse(value);
103
+ if (typeof parsed === 'object' && parsed !== null) {
104
+ const redactedString = tryParseObjectAndRemovePII(value, maxDepth - 1);
105
+ if (redactedString !== value) {
106
+ redacted = true;
107
+ return redactedString;
108
+ }
109
+ }
110
+ }
111
+ catch (_a) {
112
+ // not valid JSON, leave as is
113
+ }
114
+ }
95
115
  return undefined;
96
116
  });
97
117
  return redacted ? stringifyJSON(instance) : content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noibu-react-native",
3
- "version": "0.2.35-rc.4",
3
+ "version": "0.2.35-rc.6",
4
4
  "targetNjsVersion": "1.0.104",
5
5
  "description": "React-Native SDK for NoibuJS to collect errors in React-Native applications",
6
6
  "main": "dist/entry/index.js",