@openreplay/tracker 5.0.5-beta.2 → 5.0.5-beta.4

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/cjs/app/index.js CHANGED
@@ -33,7 +33,7 @@ class App {
33
33
  this.stopCallbacks = [];
34
34
  this.commitCallbacks = [];
35
35
  this.activityState = ActivityState.NotActive;
36
- this.version = '5.0.5-beta.2'; // TODO: version compatability check inside each plugin.
36
+ this.version = '5.0.5-beta.3'; // TODO: version compatability check inside each plugin.
37
37
  this._usingOldFetchPlugin = false;
38
38
  this.delay = 0;
39
39
  this.projectKey = projectKey;
package/cjs/index.js CHANGED
@@ -140,7 +140,7 @@ class API {
140
140
  // no-cors issue only with text/plain or not-set Content-Type
141
141
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
142
142
  req.send(JSON.stringify({
143
- trackerVersion: '5.0.5-beta.2',
143
+ trackerVersion: '5.0.5-beta.3',
144
144
  projectKey: options.projectKey,
145
145
  doNotTrack,
146
146
  // TODO: add precise reason (an exact API missing)
@@ -33,6 +33,7 @@ interface AxiosResponse<T = any> {
33
33
  response?: AxiosRequestConfig;
34
34
  }
35
35
  export interface AxiosInstance extends Record<string, any> {
36
+ getUri: (config?: AxiosRequestConfig) => string;
36
37
  interceptors: {
37
38
  request: AxiosInterceptorManager<InternalAxiosRequestConfig>;
38
39
  response: AxiosInterceptorManager<AxiosResponse>;
@@ -2,22 +2,61 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const messages_gen_js_1 = require("../app/messages.gen.js");
4
4
  const utils_js_1 = require("../utils.js");
5
+ const exception_js_1 = require("./exception.js");
5
6
  function default_1(app, instance, opts, sanitize, stringify) {
6
7
  app.debug.log('Openreplay: attaching axios spy to instance', instance);
7
8
  function captureResponseData(axiosResponseObj) {
8
- const { headers: reqHs, data: reqData, method, url } = axiosResponseObj.config;
9
+ app.debug.log('Openreplay: capturing axios response data', axiosResponseObj);
10
+ const { headers: reqHs, data: reqData, method, url, baseURL } = axiosResponseObj.config;
9
11
  const { data: rData, headers: rHs, status: globStatus, response } = axiosResponseObj;
10
12
  const { data: resData, headers: resHs, status: resStatus } = response || {};
13
+ const ihOpt = opts.ignoreHeaders;
14
+ const isHIgnoring = Array.isArray(ihOpt) ? (name) => ihOpt.includes(name) : () => ihOpt;
15
+ function writeHeader(hsObj, header) {
16
+ if (!isHIgnoring(header[0])) {
17
+ hsObj[header[0]] = header[1];
18
+ }
19
+ }
20
+ let requestHs = {};
21
+ let responseHs = {};
22
+ if (reqHs.toJSON) {
23
+ requestHs = reqHs.toJSON();
24
+ }
25
+ else if (reqHs instanceof Headers) {
26
+ reqHs.forEach((v, n) => writeHeader(requestHs, [n, v]));
27
+ }
28
+ else if (Array.isArray(reqHs)) {
29
+ reqHs.forEach((h) => writeHeader(requestHs, h));
30
+ }
31
+ else if (typeof reqHs === 'object') {
32
+ Object.entries(reqHs).forEach((h) => writeHeader(requestHs, h));
33
+ }
34
+ const usedResHeader = resHs ? resHs : rHs;
35
+ if (usedResHeader.toJSON) {
36
+ responseHs = usedResHeader.toJSON();
37
+ }
38
+ else if (usedResHeader instanceof Headers) {
39
+ usedResHeader.forEach((v, n) => writeHeader(responseHs, [n, v]));
40
+ }
41
+ else if (Array.isArray(usedResHeader)) {
42
+ usedResHeader.forEach((h) => writeHeader(responseHs, h));
43
+ }
44
+ else if (typeof usedResHeader === 'object') {
45
+ Object.entries(usedResHeader).forEach(([n, v]) => {
46
+ if (!isHIgnoring(n))
47
+ responseHs[n] = v;
48
+ });
49
+ }
11
50
  const reqResInfo = sanitize({
12
51
  url,
13
52
  method: method || '',
14
53
  status: globStatus || resStatus || 0,
15
54
  request: {
16
- headers: reqHs.toJSON(),
55
+ headers: requestHs,
17
56
  body: reqData,
18
57
  },
19
58
  response: {
20
- headers: (resHs === null || resHs === void 0 ? void 0 : resHs.toJSON()) || rHs.toJSON(),
59
+ headers: responseHs,
21
60
  body: resData || rData,
22
61
  },
23
62
  });
@@ -27,6 +66,7 @@ function default_1(app, instance, opts, sanitize, stringify) {
27
66
  }
28
67
  const requestStart = axiosResponseObj.config.__openreplay_timing;
29
68
  const duration = performance.now() - requestStart;
69
+ console.log(reqResInfo, 'finally');
30
70
  app.send((0, messages_gen_js_1.NetworkRequest)('xhr', String(method), String(reqResInfo.url), stringify(reqResInfo.request), stringify(reqResInfo.response), reqResInfo.status, requestStart + (0, utils_js_1.getTimeOrigin)(), duration));
31
71
  }
32
72
  function getStartTime(config) {
@@ -50,7 +90,13 @@ function default_1(app, instance, opts, sanitize, stringify) {
50
90
  return response;
51
91
  }
52
92
  function captureNetworkError(error) {
53
- captureResponseData(error);
93
+ app.debug.log('Openreplay: capturing API request error', error);
94
+ if (isAxiosError(error)) {
95
+ captureResponseData(error.response);
96
+ }
97
+ else if (error instanceof Error) {
98
+ app.send((0, exception_js_1.getExceptionMessage)(error, []));
99
+ }
54
100
  return Promise.reject(error);
55
101
  }
56
102
  function logRequestError(ev) {
@@ -69,3 +115,9 @@ function default_1(app, instance, opts, sanitize, stringify) {
69
115
  });
70
116
  }
71
117
  exports.default = default_1;
118
+ function isAxiosError(payload) {
119
+ return isObject(payload) && payload.isAxiosError === true;
120
+ }
121
+ function isObject(thing) {
122
+ return thing !== null && typeof thing === 'object';
123
+ }
@@ -165,6 +165,7 @@ function default_1(app, opts = {}) {
165
165
  });
166
166
  xhr.addEventListener('load', app.safe((e) => {
167
167
  const { headers: reqHs, body: reqBody } = getXHRRequestDataObject(xhr);
168
+ app.debug.log('Openreplay: XHR load ', reqHs, reqBody, xhr, xhr.getAllResponseHeaders());
168
169
  const duration = startTime > 0 ? e.timeStamp - startTime : 0;
169
170
  const hString = ignoreHeaders ? '' : xhr.getAllResponseHeaders(); // might be null (though only if no response received though)
170
171
  const resHs = hString
@@ -201,10 +202,12 @@ function default_1(app, opts = {}) {
201
202
  XMLHttpRequest.prototype.send = function (body) {
202
203
  const rdo = getXHRRequestDataObject(this);
203
204
  rdo.body = body;
205
+ app.debug.log('Openreplay: ', 'XHR send', rdo, 'XHR Object', this);
204
206
  return nativeSend.apply(this, arguments);
205
207
  };
206
208
  const nativeSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
207
209
  XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
210
+ app.debug.log('Openreplay: ', name, value, isHIgnored(name), getXHRRequestDataObject(this));
208
211
  if (!isHIgnored(name)) {
209
212
  const rdo = getXHRRequestDataObject(this);
210
213
  rdo.headers[name] = value;
package/cjs/utils.d.ts CHANGED
@@ -11,3 +11,4 @@ export declare const DOCS_HOST = "https://docs.openreplay.com";
11
11
  export declare function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath?: string): void;
12
12
  export declare function getLabelAttribute(e: Element): string | null;
13
13
  export declare function hasOpenreplayAttribute(e: Element, attr: string): boolean;
14
+ export declare function buildFullUrl(baseURL: string | undefined, requestedURL: string): string;
package/cjs/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasOpenreplayAttribute = exports.getLabelAttribute = exports.deprecationWarn = exports.DOCS_HOST = exports.isURL = exports.normSpaces = exports.stars = exports.now = exports.getTimeOrigin = exports.adjustTimeOrigin = exports.MAX_STR_LEN = exports.IS_FIREFOX = exports.IN_BROWSER = void 0;
3
+ exports.buildFullUrl = exports.hasOpenreplayAttribute = exports.getLabelAttribute = exports.deprecationWarn = exports.DOCS_HOST = exports.isURL = exports.normSpaces = exports.stars = exports.now = exports.getTimeOrigin = exports.adjustTimeOrigin = exports.MAX_STR_LEN = exports.IS_FIREFOX = exports.IN_BROWSER = void 0;
4
4
  const DEPRECATED_ATTRS = { htmlmasked: 'hidden', masked: 'obscured' };
5
5
  exports.IN_BROWSER = !(typeof window === 'undefined');
6
6
  exports.IS_FIREFOX = exports.IN_BROWSER && navigator.userAgent.match(/firefox|fxios/i);
@@ -69,3 +69,23 @@ function hasOpenreplayAttribute(e, attr) {
69
69
  return false;
70
70
  }
71
71
  exports.hasOpenreplayAttribute = hasOpenreplayAttribute;
72
+ // Copied from axios library because these functions haven't been exported.
73
+ // Why can't axios put constructed fullURL into the config object or in an additional meta information?
74
+ // TODO: axios feature request
75
+ function isAbsoluteURL(url) {
76
+ // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
77
+ // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
78
+ // by any combination of letters, digits, plus, period, or hyphen.
79
+ // eslint-disable-next-line no-useless-escape
80
+ return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
81
+ }
82
+ function combineURLs(baseURL, relativeURL) {
83
+ return relativeURL ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL;
84
+ }
85
+ function buildFullUrl(baseURL, requestedURL) {
86
+ if (baseURL && !isAbsoluteURL(requestedURL)) {
87
+ return combineURLs(baseURL, requestedURL);
88
+ }
89
+ return requestedURL;
90
+ }
91
+ exports.buildFullUrl = buildFullUrl;
package/lib/app/index.js CHANGED
@@ -30,7 +30,7 @@ export default class App {
30
30
  this.stopCallbacks = [];
31
31
  this.commitCallbacks = [];
32
32
  this.activityState = ActivityState.NotActive;
33
- this.version = '5.0.5-beta.2'; // TODO: version compatability check inside each plugin.
33
+ this.version = '5.0.5-beta.3'; // TODO: version compatability check inside each plugin.
34
34
  this._usingOldFetchPlugin = false;
35
35
  this.delay = 0;
36
36
  this.projectKey = projectKey;
package/lib/index.js CHANGED
@@ -135,7 +135,7 @@ export default class API {
135
135
  // no-cors issue only with text/plain or not-set Content-Type
136
136
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
137
137
  req.send(JSON.stringify({
138
- trackerVersion: '5.0.5-beta.2',
138
+ trackerVersion: '5.0.5-beta.3',
139
139
  projectKey: options.projectKey,
140
140
  doNotTrack,
141
141
  // TODO: add precise reason (an exact API missing)
@@ -33,6 +33,7 @@ interface AxiosResponse<T = any> {
33
33
  response?: AxiosRequestConfig;
34
34
  }
35
35
  export interface AxiosInstance extends Record<string, any> {
36
+ getUri: (config?: AxiosRequestConfig) => string;
36
37
  interceptors: {
37
38
  request: AxiosInterceptorManager<InternalAxiosRequestConfig>;
38
39
  response: AxiosInterceptorManager<AxiosResponse>;
@@ -1,21 +1,60 @@
1
1
  import { NetworkRequest } from '../app/messages.gen.js';
2
2
  import { getTimeOrigin } from '../utils.js';
3
+ import { getExceptionMessage } from './exception.js';
3
4
  export default function (app, instance, opts, sanitize, stringify) {
4
5
  app.debug.log('Openreplay: attaching axios spy to instance', instance);
5
6
  function captureResponseData(axiosResponseObj) {
6
- const { headers: reqHs, data: reqData, method, url } = axiosResponseObj.config;
7
+ app.debug.log('Openreplay: capturing axios response data', axiosResponseObj);
8
+ const { headers: reqHs, data: reqData, method, url, baseURL } = axiosResponseObj.config;
7
9
  const { data: rData, headers: rHs, status: globStatus, response } = axiosResponseObj;
8
10
  const { data: resData, headers: resHs, status: resStatus } = response || {};
11
+ const ihOpt = opts.ignoreHeaders;
12
+ const isHIgnoring = Array.isArray(ihOpt) ? (name) => ihOpt.includes(name) : () => ihOpt;
13
+ function writeHeader(hsObj, header) {
14
+ if (!isHIgnoring(header[0])) {
15
+ hsObj[header[0]] = header[1];
16
+ }
17
+ }
18
+ let requestHs = {};
19
+ let responseHs = {};
20
+ if (reqHs.toJSON) {
21
+ requestHs = reqHs.toJSON();
22
+ }
23
+ else if (reqHs instanceof Headers) {
24
+ reqHs.forEach((v, n) => writeHeader(requestHs, [n, v]));
25
+ }
26
+ else if (Array.isArray(reqHs)) {
27
+ reqHs.forEach((h) => writeHeader(requestHs, h));
28
+ }
29
+ else if (typeof reqHs === 'object') {
30
+ Object.entries(reqHs).forEach((h) => writeHeader(requestHs, h));
31
+ }
32
+ const usedResHeader = resHs ? resHs : rHs;
33
+ if (usedResHeader.toJSON) {
34
+ responseHs = usedResHeader.toJSON();
35
+ }
36
+ else if (usedResHeader instanceof Headers) {
37
+ usedResHeader.forEach((v, n) => writeHeader(responseHs, [n, v]));
38
+ }
39
+ else if (Array.isArray(usedResHeader)) {
40
+ usedResHeader.forEach((h) => writeHeader(responseHs, h));
41
+ }
42
+ else if (typeof usedResHeader === 'object') {
43
+ Object.entries(usedResHeader).forEach(([n, v]) => {
44
+ if (!isHIgnoring(n))
45
+ responseHs[n] = v;
46
+ });
47
+ }
9
48
  const reqResInfo = sanitize({
10
49
  url,
11
50
  method: method || '',
12
51
  status: globStatus || resStatus || 0,
13
52
  request: {
14
- headers: reqHs.toJSON(),
53
+ headers: requestHs,
15
54
  body: reqData,
16
55
  },
17
56
  response: {
18
- headers: (resHs === null || resHs === void 0 ? void 0 : resHs.toJSON()) || rHs.toJSON(),
57
+ headers: responseHs,
19
58
  body: resData || rData,
20
59
  },
21
60
  });
@@ -25,6 +64,7 @@ export default function (app, instance, opts, sanitize, stringify) {
25
64
  }
26
65
  const requestStart = axiosResponseObj.config.__openreplay_timing;
27
66
  const duration = performance.now() - requestStart;
67
+ console.log(reqResInfo, 'finally');
28
68
  app.send(NetworkRequest('xhr', String(method), String(reqResInfo.url), stringify(reqResInfo.request), stringify(reqResInfo.response), reqResInfo.status, requestStart + getTimeOrigin(), duration));
29
69
  }
30
70
  function getStartTime(config) {
@@ -48,7 +88,13 @@ export default function (app, instance, opts, sanitize, stringify) {
48
88
  return response;
49
89
  }
50
90
  function captureNetworkError(error) {
51
- captureResponseData(error);
91
+ app.debug.log('Openreplay: capturing API request error', error);
92
+ if (isAxiosError(error)) {
93
+ captureResponseData(error.response);
94
+ }
95
+ else if (error instanceof Error) {
96
+ app.send(getExceptionMessage(error, []));
97
+ }
52
98
  return Promise.reject(error);
53
99
  }
54
100
  function logRequestError(ev) {
@@ -66,3 +112,9 @@ export default function (app, instance, opts, sanitize, stringify) {
66
112
  (_d = (_c = instance.interceptors.response).eject) === null || _d === void 0 ? void 0 : _d.call(_c, resInt);
67
113
  });
68
114
  }
115
+ function isAxiosError(payload) {
116
+ return isObject(payload) && payload.isAxiosError === true;
117
+ }
118
+ function isObject(thing) {
119
+ return thing !== null && typeof thing === 'object';
120
+ }
@@ -163,6 +163,7 @@ export default function (app, opts = {}) {
163
163
  });
164
164
  xhr.addEventListener('load', app.safe((e) => {
165
165
  const { headers: reqHs, body: reqBody } = getXHRRequestDataObject(xhr);
166
+ app.debug.log('Openreplay: XHR load ', reqHs, reqBody, xhr, xhr.getAllResponseHeaders());
166
167
  const duration = startTime > 0 ? e.timeStamp - startTime : 0;
167
168
  const hString = ignoreHeaders ? '' : xhr.getAllResponseHeaders(); // might be null (though only if no response received though)
168
169
  const resHs = hString
@@ -199,10 +200,12 @@ export default function (app, opts = {}) {
199
200
  XMLHttpRequest.prototype.send = function (body) {
200
201
  const rdo = getXHRRequestDataObject(this);
201
202
  rdo.body = body;
203
+ app.debug.log('Openreplay: ', 'XHR send', rdo, 'XHR Object', this);
202
204
  return nativeSend.apply(this, arguments);
203
205
  };
204
206
  const nativeSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
205
207
  XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
208
+ app.debug.log('Openreplay: ', name, value, isHIgnored(name), getXHRRequestDataObject(this));
206
209
  if (!isHIgnored(name)) {
207
210
  const rdo = getXHRRequestDataObject(this);
208
211
  rdo.headers[name] = value;
package/lib/utils.d.ts CHANGED
@@ -11,3 +11,4 @@ export declare const DOCS_HOST = "https://docs.openreplay.com";
11
11
  export declare function deprecationWarn(nameOfFeature: string, useInstead: string, docsPath?: string): void;
12
12
  export declare function getLabelAttribute(e: Element): string | null;
13
13
  export declare function hasOpenreplayAttribute(e: Element, attr: string): boolean;
14
+ export declare function buildFullUrl(baseURL: string | undefined, requestedURL: string): string;
package/lib/utils.js CHANGED
@@ -59,3 +59,22 @@ export function hasOpenreplayAttribute(e, attr) {
59
59
  }
60
60
  return false;
61
61
  }
62
+ // Copied from axios library because these functions haven't been exported.
63
+ // Why can't axios put constructed fullURL into the config object or in an additional meta information?
64
+ // TODO: axios feature request
65
+ function isAbsoluteURL(url) {
66
+ // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
67
+ // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
68
+ // by any combination of letters, digits, plus, period, or hyphen.
69
+ // eslint-disable-next-line no-useless-escape
70
+ return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
71
+ }
72
+ function combineURLs(baseURL, relativeURL) {
73
+ return relativeURL ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') : baseURL;
74
+ }
75
+ export function buildFullUrl(baseURL, requestedURL) {
76
+ if (baseURL && !isAbsoluteURL(requestedURL)) {
77
+ return combineURLs(baseURL, requestedURL);
78
+ }
79
+ return requestedURL;
80
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@openreplay/tracker",
3
3
  "description": "The OpenReplay tracker main package",
4
- "version": "5.0.5-beta.2",
4
+ "version": "5.0.5-beta.4",
5
5
  "keywords": [
6
6
  "logging",
7
7
  "replay"