@openreplay/tracker 8.0.0 → 8.0.1-beta14

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.
Files changed (100) hide show
  1. package/cjs/app/index.d.ts +4 -1
  2. package/cjs/app/index.js +18 -2
  3. package/cjs/app/session.d.ts +10 -0
  4. package/cjs/app/session.js +3 -0
  5. package/cjs/index.d.ts +11 -0
  6. package/cjs/index.js +28 -1
  7. package/cjs/modules/Network/fetchProxy.d.ts +34 -0
  8. package/cjs/modules/Network/fetchProxy.js +240 -0
  9. package/cjs/modules/Network/index.d.ts +3 -0
  10. package/cjs/modules/Network/index.js +9 -0
  11. package/cjs/modules/Network/networkMessage.d.ts +49 -0
  12. package/cjs/modules/Network/networkMessage.js +82 -0
  13. package/cjs/modules/Network/types.d.ts +13 -0
  14. package/cjs/modules/Network/types.js +3 -0
  15. package/cjs/modules/Network/utils.d.ts +11 -0
  16. package/cjs/modules/Network/utils.js +213 -0
  17. package/cjs/modules/Network/xhrProxy.d.ts +47 -0
  18. package/cjs/modules/Network/xhrProxy.js +209 -0
  19. package/cjs/modules/console.js +21 -13
  20. package/cjs/modules/featureFlags.d.ts +25 -0
  21. package/cjs/modules/featureFlags.js +100 -0
  22. package/cjs/modules/network.d.ts +3 -4
  23. package/cjs/modules/network.js +13 -3
  24. package/coverage/clover.xml +898 -449
  25. package/coverage/coverage-final.json +13 -8
  26. package/coverage/lcov-report/index.html +50 -35
  27. package/coverage/lcov-report/main/app/guards.ts.html +1 -1
  28. package/coverage/lcov-report/main/app/index.html +21 -21
  29. package/coverage/lcov-report/main/app/index.ts.html +46 -4
  30. package/coverage/lcov-report/main/app/logger.ts.html +1 -1
  31. package/coverage/lcov-report/main/app/messages.gen.ts.html +146 -146
  32. package/coverage/lcov-report/main/app/observer/iframe_observer.ts.html +1 -1
  33. package/coverage/lcov-report/main/app/observer/iframe_offsets.ts.html +1 -1
  34. package/coverage/lcov-report/main/app/observer/index.html +1 -1
  35. package/coverage/lcov-report/main/app/observer/shadow_root_observer.ts.html +1 -1
  36. package/coverage/lcov-report/main/app/observer/top_observer.ts.html +1 -1
  37. package/coverage/lcov-report/main/app/sanitizer.ts.html +1 -1
  38. package/coverage/lcov-report/main/app/session.ts.html +47 -5
  39. package/coverage/lcov-report/main/app/ticker.ts.html +1 -1
  40. package/coverage/lcov-report/main/index.html +20 -20
  41. package/coverage/lcov-report/main/index.ts.html +38 -26
  42. package/coverage/lcov-report/main/modules/Network/fetchProxy.ts.html +949 -0
  43. package/coverage/lcov-report/main/modules/Network/index.html +176 -0
  44. package/coverage/lcov-report/main/modules/Network/index.ts.html +169 -0
  45. package/coverage/lcov-report/main/modules/Network/networkMessage.ts.html +382 -0
  46. package/coverage/lcov-report/main/modules/Network/utils.ts.html +700 -0
  47. package/coverage/lcov-report/main/modules/Network/xhrProxy.ts.html +823 -0
  48. package/coverage/lcov-report/main/modules/attributeSender.ts.html +1 -1
  49. package/coverage/lcov-report/main/modules/axiosSpy.ts.html +1 -1
  50. package/coverage/lcov-report/main/modules/connection.ts.html +1 -1
  51. package/coverage/lcov-report/main/modules/console.ts.html +174 -147
  52. package/coverage/lcov-report/main/modules/constructedStyleSheets.ts.html +1 -1
  53. package/coverage/lcov-report/main/modules/cssrules.ts.html +1 -1
  54. package/coverage/lcov-report/main/modules/exception.ts.html +1 -1
  55. package/coverage/lcov-report/main/modules/featureFlags.ts.html +102 -51
  56. package/coverage/lcov-report/main/modules/focus.ts.html +1 -1
  57. package/coverage/lcov-report/main/modules/fonts.ts.html +1 -1
  58. package/coverage/lcov-report/main/modules/img.ts.html +1 -1
  59. package/coverage/lcov-report/main/modules/index.html +33 -33
  60. package/coverage/lcov-report/main/modules/input.ts.html +1 -1
  61. package/coverage/lcov-report/main/modules/mouse.ts.html +1 -1
  62. package/coverage/lcov-report/main/modules/network.ts.html +70 -70
  63. package/coverage/lcov-report/main/modules/performance.ts.html +1 -1
  64. package/coverage/lcov-report/main/modules/scroll.ts.html +1 -1
  65. package/coverage/lcov-report/main/modules/selection.ts.html +1 -1
  66. package/coverage/lcov-report/main/modules/tabs.ts.html +1 -1
  67. package/coverage/lcov-report/main/modules/timing.ts.html +1 -1
  68. package/coverage/lcov-report/main/modules/viewport.ts.html +1 -1
  69. package/coverage/lcov-report/main/utils.ts.html +45 -45
  70. package/coverage/lcov-report/webworker/BatchWriter.ts.html +1 -1
  71. package/coverage/lcov-report/webworker/MessageEncoder.gen.ts.html +1 -1
  72. package/coverage/lcov-report/webworker/PrimitiveEncoder.ts.html +1 -1
  73. package/coverage/lcov-report/webworker/QueueSender.ts.html +1 -1
  74. package/coverage/lcov-report/webworker/index.html +1 -1
  75. package/coverage/lcov-report/webworker/index.ts.html +1 -1
  76. package/coverage/lcov.info +1558 -726
  77. package/lib/app/index.d.ts +4 -1
  78. package/lib/app/index.js +18 -2
  79. package/lib/app/session.d.ts +10 -0
  80. package/lib/app/session.js +3 -0
  81. package/lib/index.d.ts +11 -0
  82. package/lib/index.js +28 -1
  83. package/lib/modules/Network/fetchProxy.d.ts +34 -0
  84. package/lib/modules/Network/fetchProxy.js +234 -0
  85. package/lib/modules/Network/index.d.ts +3 -0
  86. package/lib/modules/Network/index.js +6 -0
  87. package/lib/modules/Network/networkMessage.d.ts +49 -0
  88. package/lib/modules/Network/networkMessage.js +78 -0
  89. package/lib/modules/Network/types.d.ts +13 -0
  90. package/lib/modules/Network/types.js +2 -0
  91. package/lib/modules/Network/utils.d.ts +11 -0
  92. package/lib/modules/Network/utils.js +201 -0
  93. package/lib/modules/Network/xhrProxy.d.ts +47 -0
  94. package/lib/modules/Network/xhrProxy.js +204 -0
  95. package/lib/modules/console.js +21 -13
  96. package/lib/modules/featureFlags.d.ts +25 -0
  97. package/lib/modules/featureFlags.js +97 -0
  98. package/lib/modules/network.d.ts +3 -4
  99. package/lib/modules/network.js +13 -3
  100. package/package.json +1 -1
@@ -73,12 +73,13 @@ export default class App {
73
73
  private readonly startCallbacks;
74
74
  private readonly stopCallbacks;
75
75
  private readonly commitCallbacks;
76
- private readonly options;
76
+ readonly options: AppOptions;
77
77
  readonly networkOptions?: NetworkOptions;
78
78
  private readonly revID;
79
79
  private activityState;
80
80
  private readonly version;
81
81
  private readonly worker?;
82
+ private featureFlags;
82
83
  private compressionThreshold;
83
84
  private restartAttempts;
84
85
  private readonly bc;
@@ -120,6 +121,8 @@ export default class App {
120
121
  resolveResourceURL(resourceURL: string): string;
121
122
  isServiceURL(url: string): boolean;
122
123
  active(): boolean;
124
+ isFeatureActive(feature: string): boolean;
125
+ getFeatureFlags(): string[];
123
126
  resetNextPageSession(flag: boolean): void;
124
127
  private _start;
125
128
  /**
package/lib/app/index.js CHANGED
@@ -32,7 +32,8 @@ export default class App {
32
32
  this.stopCallbacks = [];
33
33
  this.commitCallbacks = [];
34
34
  this.activityState = ActivityState.NotActive;
35
- this.version = '8.0.0'; // TODO: version compatability check inside each plugin.
35
+ this.version = '8.0.1-beta14'; // TODO: version compatability check inside each plugin.
36
+ this.featureFlags = [];
36
37
  this.compressionThreshold = 24 * 1000;
37
38
  this.restartAttempts = 0;
38
39
  this.bc = new BroadcastChannel('rick');
@@ -332,6 +333,12 @@ export default class App {
332
333
  active() {
333
334
  return this.activityState === ActivityState.Active;
334
335
  }
336
+ isFeatureActive(feature) {
337
+ return this.featureFlags.includes(feature);
338
+ }
339
+ getFeatureFlags() {
340
+ return this.featureFlags;
341
+ }
335
342
  resetNextPageSession(flag) {
336
343
  if (flag) {
337
344
  this.sessionStorage.setItem(this.options.session_reset_key, 't');
@@ -410,7 +417,8 @@ export default class App {
410
417
  delay, // derived from token
411
418
  sessionID, // derived from token
412
419
  startTimestamp, // real startTS (server time), derived from sessionID
413
- } = r;
420
+ userBrowser, userCity, userCountry, userDevice, userOS, userState, } = r;
421
+ // TODO: insert feature flags here
414
422
  if (typeof token !== 'string' ||
415
423
  typeof userUUID !== 'string' ||
416
424
  (typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
@@ -421,6 +429,14 @@ export default class App {
421
429
  }
422
430
  this.delay = delay;
423
431
  this.session.setSessionToken(token);
432
+ this.session.setUserInfo({
433
+ userBrowser,
434
+ userCity,
435
+ userCountry,
436
+ userDevice,
437
+ userOS,
438
+ userState,
439
+ });
424
440
  this.session.assign({
425
441
  sessionID,
426
442
  timestamp: startTimestamp || timestamp,
@@ -1,4 +1,12 @@
1
1
  import type App from './index.js';
2
+ interface UserInfo {
3
+ userBrowser: string;
4
+ userCity: string;
5
+ userCountry: string;
6
+ userDevice: string;
7
+ userOS: string;
8
+ userState: string;
9
+ }
2
10
  interface SessionInfo {
3
11
  sessionID: string | undefined;
4
12
  metadata: Record<string, string>;
@@ -22,12 +30,14 @@ export default class Session {
22
30
  private timestamp;
23
31
  private projectID;
24
32
  private tabId;
33
+ userInfo: UserInfo;
25
34
  constructor(app: App, options: Options);
26
35
  attachUpdateCallback(cb: OnUpdateCallback): void;
27
36
  private handleUpdate;
28
37
  assign(newInfo: Partial<SessionInfo>): void;
29
38
  setMetadata(key: string, value: string): void;
30
39
  setUserID(userID: string): void;
40
+ setUserInfo(userInfo: UserInfo): void;
31
41
  private getPageNumber;
32
42
  incPageNo(): number;
33
43
  getSessionToken(): string | undefined;
@@ -48,6 +48,9 @@ export default class Session {
48
48
  this.userID = userID;
49
49
  this.handleUpdate({ userID });
50
50
  }
51
+ setUserInfo(userInfo) {
52
+ this.userInfo = userInfo;
53
+ }
51
54
  getPageNumber() {
52
55
  const pageNoStr = this.app.sessionStorage.getItem(this.options.session_pageno_key);
53
56
  if (pageNoStr == null) {
package/lib/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { default as App } from './app/index.js';
3
3
  import * as _Messages from './app/messages.gen.js';
4
4
  export declare const Messages: typeof _Messages;
5
5
  export { SanitizeLevel } from './app/sanitizer.js';
6
+ import FeatureFlags, { IFeatureFlag } from './modules/featureFlags.js';
6
7
  import type { Options as AppOptions } from './app/index.js';
7
8
  import type { Options as ConsoleOptions } from './modules/console.js';
8
9
  import type { Options as ExceptionOptions } from './modules/exception.js';
@@ -21,12 +22,22 @@ export type Options = Partial<AppOptions & ConsoleOptions & ExceptionOptions & I
21
22
  autoResetOnWindowOpen?: boolean;
22
23
  network?: NetworkOptions;
23
24
  mouse?: MouseHandlerOptions;
25
+ flags?: {
26
+ onFlagsLoad?: (flags: IFeatureFlag[]) => void;
27
+ };
24
28
  __DISABLE_SECURE_MODE?: boolean;
25
29
  };
26
30
  export default class API {
27
31
  private readonly options;
32
+ featureFlags: FeatureFlags;
28
33
  private readonly app;
29
34
  constructor(options: Options);
35
+ isFlagEnabled(flagName: string): boolean;
36
+ onFlagsLoad(callback: (flags: IFeatureFlag[]) => void): void;
37
+ clearPersistFlags(): void;
38
+ reloadFlags(): Promise<void>;
39
+ getFeatureFlag(flagName: string): IFeatureFlag | undefined;
40
+ getAllFeatureFlags(): IFeatureFlag[];
30
41
  use<T>(fn: (app: App | null, options?: Options) => T): T;
31
42
  isActive(): boolean;
32
43
  start(startOpts?: Partial<StartOptions>): Promise<StartPromiseReturn>;
package/lib/index.js CHANGED
@@ -22,6 +22,7 @@ import ConstructedStyleSheets from './modules/constructedStyleSheets.js';
22
22
  import Selection from './modules/selection.js';
23
23
  import Tabs from './modules/tabs.js';
24
24
  import { IN_BROWSER, deprecationWarn, DOCS_HOST } from './utils.js';
25
+ import FeatureFlags from './modules/featureFlags.js';
25
26
  const DOCS_SETUP = '/installation/javascript-sdk';
26
27
  function processOptions(obj) {
27
28
  if (obj == null) {
@@ -115,7 +116,15 @@ export default class API {
115
116
  Network(app, options.network);
116
117
  Selection(app);
117
118
  Tabs(app);
119
+ this.featureFlags = new FeatureFlags(app);
118
120
  window.__OPENREPLAY__ = this;
121
+ app.attachStartCallback(() => {
122
+ var _a;
123
+ if ((_a = options.flags) === null || _a === void 0 ? void 0 : _a.onFlagsLoad) {
124
+ this.onFlagsLoad(options.flags.onFlagsLoad);
125
+ }
126
+ void this.featureFlags.reloadFlags();
127
+ });
119
128
  if (options.autoResetOnWindowOpen) {
120
129
  const wOpen = window.open;
121
130
  app.attachStartCallback(() => {
@@ -139,13 +148,31 @@ export default class API {
139
148
  // no-cors issue only with text/plain or not-set Content-Type
140
149
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
141
150
  req.send(JSON.stringify({
142
- trackerVersion: '8.0.0',
151
+ trackerVersion: '8.0.1-beta14',
143
152
  projectKey: options.projectKey,
144
153
  doNotTrack,
145
154
  // TODO: add precise reason (an exact API missing)
146
155
  }));
147
156
  }
148
157
  }
158
+ isFlagEnabled(flagName) {
159
+ return this.featureFlags.isFlagEnabled(flagName);
160
+ }
161
+ onFlagsLoad(callback) {
162
+ this.featureFlags.onFlagsLoad(callback);
163
+ }
164
+ clearPersistFlags() {
165
+ this.featureFlags.clearPersistFlags();
166
+ }
167
+ reloadFlags() {
168
+ return this.featureFlags.reloadFlags();
169
+ }
170
+ getFeatureFlag(flagName) {
171
+ return this.featureFlags.getFeatureFlag(flagName);
172
+ }
173
+ getAllFeatureFlags() {
174
+ return this.featureFlags.flags;
175
+ }
149
176
  use(fn) {
150
177
  return fn(this.app, this.options);
151
178
  }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * I took inspiration in few stack exchange posts
3
+ * and Tencent vConsole library (MIT)
4
+ * by wrapping the XMLHttpRequest object in a Proxy
5
+ * we can intercept the network requests
6
+ * in not-so-hacky way
7
+ * */
8
+ import NetworkMessage from './networkMessage.js';
9
+ import { RequestResponseData } from './types.js';
10
+ import { NetworkRequest } from '../../common/messages.gen.js';
11
+ export declare class ResponseProxyHandler<T extends Response> implements ProxyHandler<T> {
12
+ resp: Response;
13
+ item: NetworkMessage;
14
+ constructor(resp: T, item: NetworkMessage);
15
+ set(target: T, key: string, value: (args: any[]) => any): boolean;
16
+ get(target: T, key: string): any;
17
+ protected mockReader(): void;
18
+ }
19
+ export declare class FetchProxyHandler<T extends typeof fetch> implements ProxyHandler<T> {
20
+ private readonly ignoredHeaders;
21
+ private readonly setSessionTokenHeader;
22
+ private readonly sanitize;
23
+ private readonly sendMessage;
24
+ private readonly isServiceUrl;
25
+ constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean);
26
+ apply(target: T, thisArg: typeof window, argsList: [RequestInfo | URL, RequestInit]): any;
27
+ protected beforeFetch(item: NetworkMessage, input: RequestInfo, init?: RequestInit): void;
28
+ protected afterFetch(item: NetworkMessage): (resp: Response) => Response;
29
+ protected handleResponseBody(resp: Response, item: NetworkMessage): Promise<string> | Promise<ArrayBuffer>;
30
+ }
31
+ export default class FetchProxy {
32
+ static origFetch: typeof fetch;
33
+ static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean): typeof fetch;
34
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * I took inspiration in few stack exchange posts
3
+ * and Tencent vConsole library (MIT)
4
+ * by wrapping the XMLHttpRequest object in a Proxy
5
+ * we can intercept the network requests
6
+ * in not-so-hacky way
7
+ * */
8
+ import NetworkMessage, { RequestState } from './networkMessage.js';
9
+ import { formatByteSize, genStringBody, getStringResponseByType, getURL } from './utils.js';
10
+ export class ResponseProxyHandler {
11
+ constructor(resp, item) {
12
+ this.resp = resp;
13
+ this.item = item;
14
+ this.mockReader();
15
+ }
16
+ set(target, key, value) {
17
+ return Reflect.set(target, key, value);
18
+ }
19
+ get(target, key) {
20
+ const value = Reflect.get(target, key);
21
+ switch (key) {
22
+ case 'arrayBuffer':
23
+ case 'blob':
24
+ case 'formData':
25
+ case 'json':
26
+ case 'text':
27
+ return () => {
28
+ this.item.responseType = key.toLowerCase();
29
+ // @ts-ignore
30
+ return value.apply(target).then((resp) => {
31
+ this.item.response = getStringResponseByType(this.item.responseType, resp);
32
+ return resp;
33
+ });
34
+ };
35
+ }
36
+ if (typeof value === 'function') {
37
+ return value.bind(target);
38
+ }
39
+ else {
40
+ return value;
41
+ }
42
+ }
43
+ mockReader() {
44
+ let readerReceivedValue;
45
+ if (!this.resp.body) {
46
+ // some browsers do not return `body` in some cases, like `OPTIONS` method
47
+ return;
48
+ }
49
+ if (typeof this.resp.body.getReader !== 'function') {
50
+ return;
51
+ }
52
+ const _getReader = this.resp.body.getReader;
53
+ // @ts-ignore
54
+ this.resp.body.getReader = () => {
55
+ const reader = _getReader.apply(this.resp.body);
56
+ // when readyState is already 4,
57
+ // it's not a chunked stream, or it had already been read.
58
+ // so should not update status.
59
+ if (this.item.readyState === RequestState.DONE) {
60
+ return reader;
61
+ }
62
+ const _read = reader.read;
63
+ const _cancel = reader.cancel;
64
+ this.item.responseType = 'arraybuffer';
65
+ // @ts-ignore
66
+ reader.read = () => {
67
+ return _read.apply(reader).then((result) => {
68
+ if (!readerReceivedValue) {
69
+ // @ts-ignore
70
+ readerReceivedValue = new Uint8Array(result.value);
71
+ }
72
+ else {
73
+ const newValue = new Uint8Array(readerReceivedValue.length + result.value.length);
74
+ newValue.set(readerReceivedValue);
75
+ newValue.set(result.value, readerReceivedValue.length);
76
+ readerReceivedValue = newValue;
77
+ }
78
+ this.item.endTime = performance.now();
79
+ this.item.duration = this.item.endTime - (this.item.startTime || this.item.endTime);
80
+ this.item.readyState = result.done ? 4 : 3;
81
+ this.item.statusText = result.done ? String(this.item.status) : 'Loading';
82
+ this.item.responseSize = readerReceivedValue.length;
83
+ this.item.responseSizeText = formatByteSize(this.item.responseSize);
84
+ if (result.done) {
85
+ this.item.response = getStringResponseByType(this.item.responseType, readerReceivedValue);
86
+ }
87
+ return result;
88
+ });
89
+ };
90
+ reader.cancel = (...args) => {
91
+ this.item.cancelState = 2;
92
+ this.item.statusText = 'Cancel';
93
+ this.item.endTime = performance.now();
94
+ this.item.duration = this.item.endTime - (this.item.startTime || this.item.endTime);
95
+ this.item.response = getStringResponseByType(this.item.responseType, readerReceivedValue);
96
+ return _cancel.apply(reader, args);
97
+ };
98
+ return reader;
99
+ };
100
+ }
101
+ }
102
+ export class FetchProxyHandler {
103
+ constructor(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
104
+ this.ignoredHeaders = ignoredHeaders;
105
+ this.setSessionTokenHeader = setSessionTokenHeader;
106
+ this.sanitize = sanitize;
107
+ this.sendMessage = sendMessage;
108
+ this.isServiceUrl = isServiceUrl;
109
+ }
110
+ apply(target, thisArg, argsList) {
111
+ const input = argsList[0];
112
+ const init = argsList[1];
113
+ const isORUrl = input instanceof URL || typeof input === 'string'
114
+ ? this.isServiceUrl(String(input))
115
+ : this.isServiceUrl(String(input.url));
116
+ if (isORUrl) {
117
+ return target.apply(window, argsList);
118
+ }
119
+ const item = new NetworkMessage(this.ignoredHeaders, this.setSessionTokenHeader, this.sanitize);
120
+ this.beforeFetch(item, input, init);
121
+ return target.apply(window, argsList)
122
+ .then(this.afterFetch(item))
123
+ .catch((e) => {
124
+ // mock finally
125
+ item.endTime = performance.now();
126
+ item.duration = item.endTime - (item.startTime || item.endTime);
127
+ throw e;
128
+ });
129
+ }
130
+ beforeFetch(item, input, init) {
131
+ let url, method = 'GET', requestHeader = {};
132
+ // handle `input` content
133
+ if (typeof input === 'string') {
134
+ // when `input` is a string
135
+ method = (init === null || init === void 0 ? void 0 : init.method) || 'GET';
136
+ url = getURL(input);
137
+ requestHeader = (init === null || init === void 0 ? void 0 : init.headers) || {};
138
+ }
139
+ else {
140
+ // when `input` is a `Request` object
141
+ method = input.method || 'GET';
142
+ url = getURL(input.url);
143
+ requestHeader = input.headers;
144
+ }
145
+ item.method = method;
146
+ item.requestType = 'fetch';
147
+ item.requestHeader = requestHeader;
148
+ item.url = url.toString();
149
+ item.name = (url.pathname.split('/').pop() || '') + url.search;
150
+ item.status = 0;
151
+ item.statusText = 'Pending';
152
+ item.readyState = 1;
153
+ if (!item.startTime) {
154
+ // UNSENT
155
+ item.startTime = performance.now();
156
+ }
157
+ if (Object.prototype.toString.call(requestHeader) === '[object Headers]') {
158
+ item.requestHeader = {};
159
+ for (const [key, value] of requestHeader) {
160
+ item.requestHeader[key] = value;
161
+ }
162
+ }
163
+ else {
164
+ item.requestHeader = requestHeader;
165
+ }
166
+ // save GET data
167
+ if (url.search && url.searchParams) {
168
+ item.getData = {};
169
+ for (const [key, value] of url.searchParams) {
170
+ item.getData[key] = value;
171
+ }
172
+ }
173
+ // save POST data
174
+ if (init === null || init === void 0 ? void 0 : init.body) {
175
+ item.requestData = genStringBody(init.body);
176
+ }
177
+ }
178
+ afterFetch(item) {
179
+ return (resp) => {
180
+ item.endTime = performance.now();
181
+ item.duration = item.endTime - (item.startTime || item.endTime);
182
+ item.status = resp.status;
183
+ item.statusText = String(resp.status);
184
+ let isChunked = false;
185
+ item.header = {};
186
+ for (const [key, value] of resp.headers) {
187
+ item.header[key] = value;
188
+ isChunked = value.toLowerCase().indexOf('chunked') > -1 ? true : isChunked;
189
+ }
190
+ if (isChunked) {
191
+ // when `transfer-encoding` is chunked, the response is a stream which is under loading,
192
+ // so the `readyState` should be 3 (Loading),
193
+ // and the response should NOT be `clone()` which will affect stream reading.
194
+ item.readyState = 3;
195
+ }
196
+ else {
197
+ // Otherwise, not chunked, the response is not a stream,
198
+ // so it's completed and can be cloned for `text()` calling.
199
+ item.readyState = 4;
200
+ void this.handleResponseBody(resp.clone(), item).then((responseValue) => {
201
+ item.responseSize =
202
+ typeof responseValue === 'string' ? responseValue.length : responseValue.byteLength;
203
+ item.responseSizeText = formatByteSize(item.responseSize);
204
+ item.response = getStringResponseByType(item.responseType, responseValue);
205
+ this.sendMessage(item.getMessage());
206
+ });
207
+ }
208
+ return new Proxy(resp, new ResponseProxyHandler(resp, item));
209
+ };
210
+ }
211
+ handleResponseBody(resp, item) {
212
+ // parse response body by Content-Type
213
+ const contentType = resp.headers.get('content-type');
214
+ if (contentType && contentType.includes('application/json')) {
215
+ item.responseType = 'json';
216
+ return resp.text();
217
+ }
218
+ else if (contentType &&
219
+ (contentType.includes('text/html') || contentType.includes('text/plain'))) {
220
+ item.responseType = 'text';
221
+ return resp.text();
222
+ }
223
+ else {
224
+ item.responseType = 'arraybuffer';
225
+ return resp.arrayBuffer();
226
+ }
227
+ }
228
+ }
229
+ export default class FetchProxy {
230
+ static create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
231
+ return new Proxy(fetch, new FetchProxyHandler(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl));
232
+ }
233
+ }
234
+ FetchProxy.origFetch = fetch;
@@ -0,0 +1,3 @@
1
+ import { RequestResponseData } from './types.js';
2
+ import { NetworkRequest } from '../../common/messages.gen.js';
3
+ export default function setProxy(context: typeof globalThis, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean): void;
@@ -0,0 +1,6 @@
1
+ import FetchProxy from './fetchProxy.js';
2
+ import XHRProxy from './xhrProxy.js';
3
+ export default function setProxy(context, ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl) {
4
+ context.XMLHttpRequest = XHRProxy.create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl);
5
+ context.fetch = FetchProxy.create(ignoredHeaders, setSessionTokenHeader, sanitize, sendMessage, isServiceUrl);
6
+ }
@@ -0,0 +1,49 @@
1
+ import { RequestResponseData } from './types.js';
2
+ export type httpMethod = '' | 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH';
3
+ export declare enum RequestState {
4
+ UNSENT = 0,
5
+ OPENED = 1,
6
+ HEADERS_RECEIVED = 2,
7
+ LOADING = 3,
8
+ DONE = 4
9
+ }
10
+ /**
11
+ * I know we're not using most of the information from this class
12
+ * but it can be useful in the future if we will decide to display more stuff in our ui
13
+ * */
14
+ export default class NetworkMessage {
15
+ private readonly ignoredHeaders;
16
+ private readonly setSessionTokenHeader;
17
+ private readonly sanitize;
18
+ id: string;
19
+ name?: string;
20
+ method: httpMethod;
21
+ url: string;
22
+ status: number;
23
+ statusText?: string;
24
+ cancelState?: 0 | 1 | 2 | 3;
25
+ readyState?: RequestState;
26
+ header: {
27
+ [key: string]: string;
28
+ };
29
+ responseType: XMLHttpRequest['responseType'];
30
+ requestType: 'xhr' | 'fetch' | 'ping' | 'custom';
31
+ requestHeader: HeadersInit;
32
+ response: any;
33
+ responseSize: number;
34
+ responseSizeText: string;
35
+ startTime: number;
36
+ endTime: number;
37
+ duration: number;
38
+ getData: {
39
+ [key: string]: string;
40
+ };
41
+ requestData: string | null;
42
+ constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData);
43
+ getMessage(): import("../../common/messages.gen.js").NetworkRequest;
44
+ writeHeaders(): {
45
+ reqHs: Record<string, string>;
46
+ resHs: Record<string, string>;
47
+ };
48
+ isHeaderIgnored(key: string): boolean;
49
+ }
@@ -0,0 +1,78 @@
1
+ import { NetworkRequest } from '../../app/messages.gen.js';
2
+ import { getTimeOrigin } from '../../utils.js';
3
+ export var RequestState;
4
+ (function (RequestState) {
5
+ RequestState[RequestState["UNSENT"] = 0] = "UNSENT";
6
+ RequestState[RequestState["OPENED"] = 1] = "OPENED";
7
+ RequestState[RequestState["HEADERS_RECEIVED"] = 2] = "HEADERS_RECEIVED";
8
+ RequestState[RequestState["LOADING"] = 3] = "LOADING";
9
+ RequestState[RequestState["DONE"] = 4] = "DONE";
10
+ })(RequestState || (RequestState = {}));
11
+ /**
12
+ * I know we're not using most of the information from this class
13
+ * but it can be useful in the future if we will decide to display more stuff in our ui
14
+ * */
15
+ export default class NetworkMessage {
16
+ constructor(ignoredHeaders = [], setSessionTokenHeader, sanitize) {
17
+ this.ignoredHeaders = ignoredHeaders;
18
+ this.setSessionTokenHeader = setSessionTokenHeader;
19
+ this.sanitize = sanitize;
20
+ this.id = '';
21
+ this.name = '';
22
+ this.method = '';
23
+ this.url = '';
24
+ this.status = 0;
25
+ this.statusText = '';
26
+ this.cancelState = 0;
27
+ this.readyState = 0;
28
+ this.header = {};
29
+ this.responseType = '';
30
+ this.requestHeader = {};
31
+ this.responseSize = 0; // bytes
32
+ this.responseSizeText = '';
33
+ this.startTime = 0;
34
+ this.endTime = 0;
35
+ this.duration = 0;
36
+ this.getData = {};
37
+ this.requestData = null;
38
+ }
39
+ getMessage() {
40
+ const { reqHs, resHs } = this.writeHeaders();
41
+ const request = {
42
+ headers: reqHs,
43
+ body: this.method === 'GET' ? JSON.stringify(this.getData) : this.requestData,
44
+ };
45
+ const response = { headers: resHs, body: this.response };
46
+ const messageInfo = this.sanitize({
47
+ url: this.url,
48
+ method: this.method,
49
+ status: this.status,
50
+ request,
51
+ response,
52
+ });
53
+ return NetworkRequest(this.requestType, messageInfo.method, messageInfo.url, JSON.stringify(messageInfo.request), JSON.stringify(messageInfo.response), messageInfo.status, this.startTime + getTimeOrigin(), this.duration);
54
+ }
55
+ writeHeaders() {
56
+ const reqHs = {};
57
+ Object.entries(this.requestHeader).forEach(([key, value]) => {
58
+ if (this.isHeaderIgnored(key))
59
+ return;
60
+ reqHs[key] = value;
61
+ });
62
+ this.setSessionTokenHeader((name, value) => {
63
+ reqHs[name] = value;
64
+ });
65
+ const resHs = {};
66
+ Object.entries(this.header).forEach(([key, value]) => {
67
+ if (this.isHeaderIgnored(key))
68
+ return;
69
+ resHs[key] = value;
70
+ });
71
+ return { reqHs, resHs };
72
+ }
73
+ isHeaderIgnored(key) {
74
+ if (Array.isArray(this.ignoredHeaders))
75
+ return this.ignoredHeaders.includes(key);
76
+ return this.ignoredHeaders;
77
+ }
78
+ }
@@ -0,0 +1,13 @@
1
+ export interface RequestResponseData {
2
+ readonly status: number;
3
+ readonly method: string;
4
+ url: string;
5
+ request: {
6
+ body: string | null;
7
+ headers: Record<string, string>;
8
+ };
9
+ response: {
10
+ body: string | null;
11
+ headers: Record<string, string>;
12
+ };
13
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ // we only support sanitizing for json/string data because how you're gonna sanitize binary data?
@@ -0,0 +1,11 @@
1
+ export declare const genResponseByType: (responseType: XMLHttpRequest['responseType'], response: any) => string | Record<string, any>;
2
+ export declare const getStringResponseByType: (responseType: XMLHttpRequest['responseType'], response: any) => string;
3
+ export declare const genStringBody: (body?: BodyInit) => string | null;
4
+ export declare const genGetDataByUrl: (url: string, getData?: Record<string, any>) => Record<string, any>;
5
+ export declare const genFormattedBody: (body?: BodyInit) => string | {
6
+ [key: string]: string;
7
+ } | null;
8
+ export declare function isPureObject(input: any): input is Record<any, any>;
9
+ export declare function isIterable(value: any): boolean;
10
+ export declare function formatByteSize(bytes: number): string;
11
+ export declare const getURL: (urlString: string) => URL;