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.
- package/android/build.gradle +1 -1
- package/dist/constants.js +1 -1
- package/dist/monitors/RequestMonitor.js +31 -16
- package/dist/utils/piiRedactor.js +24 -4
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
*
|
|
74
|
-
*
|
|
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