@openreplay/tracker 12.0.5 → 12.0.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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 12.0.6
2
+
3
+ - allow network sanitizer to return null (will ignore network req)
4
+
5
+ # 12.0.5
6
+
7
+ - patch for img.ts srcset detector
8
+
1
9
  # 12.0.4
2
10
 
3
11
  - patch for email sanitizer (supports + now)
package/cjs/app/index.js CHANGED
@@ -81,7 +81,7 @@ class App {
81
81
  this.stopCallbacks = [];
82
82
  this.commitCallbacks = [];
83
83
  this.activityState = ActivityState.NotActive;
84
- this.version = '12.0.5'; // TODO: version compatability check inside each plugin.
84
+ this.version = '12.0.6'; // TODO: version compatability check inside each plugin.
85
85
  this.compressionThreshold = 24 * 1000;
86
86
  this.restartAttempts = 0;
87
87
  this.bc = null;
package/cjs/index.js CHANGED
@@ -98,7 +98,7 @@ class API {
98
98
  const orig = this.options.ingestPoint || index_js_1.DEFAULT_INGEST_POINT;
99
99
  req.open('POST', orig + '/v1/web/not-started');
100
100
  req.send(JSON.stringify({
101
- trackerVersion: '12.0.5',
101
+ trackerVersion: '12.0.6',
102
102
  projectKey: this.options.projectKey,
103
103
  doNotTrack,
104
104
  reason,
@@ -6,11 +6,11 @@ export declare class BeaconProxyHandler<T extends typeof navigator.sendBeacon> i
6
6
  private readonly sanitize;
7
7
  private readonly sendMessage;
8
8
  private readonly isServiceUrl;
9
- constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean);
9
+ constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean);
10
10
  apply(target: T, thisArg: T, argsList: any[]): any;
11
11
  }
12
12
  export default class BeaconProxy {
13
13
  static origSendBeacon: (url: string | URL, data?: BodyInit | null | undefined) => boolean;
14
14
  static hasSendBeacon(): boolean;
15
- 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): any;
15
+ static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean): any;
16
16
  }
@@ -65,7 +65,10 @@ class BeaconProxyHandler {
65
65
  item.status = 500;
66
66
  item.statusText = 'Unknown';
67
67
  }
68
- this.sendMessage(item.getMessage());
68
+ const msg = item.getMessage();
69
+ if (msg) {
70
+ this.sendMessage(msg);
71
+ }
69
72
  return isSuccess;
70
73
  }
71
74
  }
@@ -23,12 +23,12 @@ export declare class FetchProxyHandler<T extends typeof fetch> implements ProxyH
23
23
  private readonly sendMessage;
24
24
  private readonly isServiceUrl;
25
25
  private readonly tokenUrlMatcher?;
26
- constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: ((url: string) => boolean) | undefined);
26
+ constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: ((url: string) => boolean) | undefined);
27
27
  apply(target: T, _: typeof window, argsList: [RequestInfo | URL, RequestInit]): any;
28
28
  protected beforeFetch(item: NetworkMessage, input: RequestInfo | string, init?: RequestInit): void;
29
29
  protected afterFetch(item: NetworkMessage): (resp: Response) => Response;
30
30
  protected handleResponseBody(resp: Response, item: NetworkMessage): Promise<ArrayBuffer> | Promise<string>;
31
31
  }
32
32
  export default class FetchProxy {
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, tokenUrlMatcher?: (url: string) => boolean): typeof fetch;
33
+ static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean): typeof fetch;
34
34
  }
@@ -264,7 +264,10 @@ class FetchProxyHandler {
264
264
  typeof responseValue === 'string' ? responseValue.length : responseValue.byteLength;
265
265
  item.responseSizeText = (0, utils_js_1.formatByteSize)(item.responseSize);
266
266
  item.response = (0, utils_js_1.getStringResponseByType)(item.responseType, responseValue);
267
- this.sendMessage(item.getMessage());
267
+ const msg = item.getMessage();
268
+ if (msg) {
269
+ this.sendMessage(msg);
270
+ }
268
271
  });
269
272
  }
270
273
  return new Proxy(resp, new ResponseProxyHandler(resp, item));
@@ -1,3 +1,3 @@
1
1
  import { RequestResponseData } from './types.js';
2
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, tokenUrlMatcher?: (url: string) => boolean): void;
3
+ export default function setProxy(context: typeof globalThis, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean): void;
@@ -39,8 +39,8 @@ export default class NetworkMessage {
39
39
  [key: string]: string;
40
40
  };
41
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;
42
+ constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null);
43
+ getMessage(): import("../../common/messages.gen.js").NetworkRequest | undefined;
44
44
  writeHeaders(): {
45
45
  reqHs: Record<string, string>;
46
46
  resHs: Record<string, string>;
@@ -53,6 +53,8 @@ class NetworkMessage {
53
53
  request,
54
54
  response,
55
55
  });
56
+ if (!messageInfo)
57
+ return;
56
58
  return (0, messages_gen_js_1.NetworkRequest)(this.requestType, messageInfo.method, messageInfo.url, JSON.stringify(messageInfo.request), JSON.stringify(messageInfo.response), messageInfo.status, this.startTime + (0, utils_js_1.getTimeOrigin)(), this.duration, this.responseSize);
57
59
  }
58
60
  writeHeaders() {
@@ -17,7 +17,7 @@ export declare class XHRProxyHandler<T extends XMLHttpRequest> implements ProxyH
17
17
  private readonly tokenUrlMatcher?;
18
18
  XMLReq: XMLHttpRequest;
19
19
  item: NetworkMessage;
20
- constructor(XMLReq: XMLHttpRequest, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: ((url: string) => boolean) | undefined);
20
+ constructor(XMLReq: XMLHttpRequest, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: ((url: string) => boolean) | undefined);
21
21
  get(target: T, key: string): any;
22
22
  set(target: T, key: string, value: (args: any[]) => any): boolean;
23
23
  onReadyStateChange(): void;
@@ -35,5 +35,5 @@ export declare class XHRProxyHandler<T extends XMLHttpRequest> implements ProxyH
35
35
  protected updateItemByReadyState(): void;
36
36
  }
37
37
  export default class XHRProxy {
38
- static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (data: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean): any;
38
+ static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (data: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean): any;
39
39
  }
@@ -106,18 +106,27 @@ class XHRProxyHandler {
106
106
  this.item.response = (0, utils_js_1.getStringResponseByType)(this.item.responseType, this.item.response);
107
107
  }, 0);
108
108
  if (this.XMLReq.readyState === networkMessage_js_1.RequestState.DONE) {
109
- this.sendMessage(this.item.getMessage());
109
+ const msg = this.item.getMessage();
110
+ if (msg) {
111
+ this.sendMessage(msg);
112
+ }
110
113
  }
111
114
  }
112
115
  onAbort() {
113
116
  this.item.cancelState = 1;
114
117
  this.item.statusText = 'Abort';
115
- this.sendMessage(this.item.getMessage());
118
+ const msg = this.item.getMessage();
119
+ if (msg) {
120
+ this.sendMessage(msg);
121
+ }
116
122
  }
117
123
  onTimeout() {
118
124
  this.item.cancelState = 3;
119
125
  this.item.statusText = 'Timeout';
120
- this.sendMessage(this.item.getMessage());
126
+ const msg = this.item.getMessage();
127
+ if (msg) {
128
+ this.sendMessage(msg);
129
+ }
121
130
  }
122
131
  getOpen(target) {
123
132
  const targetFunction = Reflect.get(target, 'open');
@@ -15,7 +15,7 @@ export interface RequestResponseData {
15
15
  request: RequestData;
16
16
  response: ResponseData;
17
17
  }
18
- type Sanitizer = (data: RequestResponseData) => RequestResponseData;
18
+ type Sanitizer = (data: RequestResponseData) => RequestResponseData | null;
19
19
  export interface Options {
20
20
  sessionTokenHeader: string | boolean;
21
21
  failuresOnly: boolean;
package/lib/app/index.js CHANGED
@@ -52,7 +52,7 @@ export default class App {
52
52
  this.stopCallbacks = [];
53
53
  this.commitCallbacks = [];
54
54
  this.activityState = ActivityState.NotActive;
55
- this.version = '12.0.5'; // TODO: version compatability check inside each plugin.
55
+ this.version = '12.0.6'; // TODO: version compatability check inside each plugin.
56
56
  this.compressionThreshold = 24 * 1000;
57
57
  this.restartAttempts = 0;
58
58
  this.bc = null;
package/lib/index.js CHANGED
@@ -67,7 +67,7 @@ export default class API {
67
67
  const orig = this.options.ingestPoint || DEFAULT_INGEST_POINT;
68
68
  req.open('POST', orig + '/v1/web/not-started');
69
69
  req.send(JSON.stringify({
70
- trackerVersion: '12.0.5',
70
+ trackerVersion: '12.0.6',
71
71
  projectKey: this.options.projectKey,
72
72
  doNotTrack,
73
73
  reason,
@@ -6,11 +6,11 @@ export declare class BeaconProxyHandler<T extends typeof navigator.sendBeacon> i
6
6
  private readonly sanitize;
7
7
  private readonly sendMessage;
8
8
  private readonly isServiceUrl;
9
- constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean);
9
+ constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean);
10
10
  apply(target: T, thisArg: T, argsList: any[]): any;
11
11
  }
12
12
  export default class BeaconProxy {
13
13
  static origSendBeacon: (url: string | URL, data?: BodyInit | null | undefined) => boolean;
14
14
  static hasSendBeacon(): boolean;
15
- 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): any;
15
+ static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean): any;
16
16
  }
@@ -59,7 +59,10 @@ export class BeaconProxyHandler {
59
59
  item.status = 500;
60
60
  item.statusText = 'Unknown';
61
61
  }
62
- this.sendMessage(item.getMessage());
62
+ const msg = item.getMessage();
63
+ if (msg) {
64
+ this.sendMessage(msg);
65
+ }
63
66
  return isSuccess;
64
67
  }
65
68
  }
@@ -23,12 +23,12 @@ export declare class FetchProxyHandler<T extends typeof fetch> implements ProxyH
23
23
  private readonly sendMessage;
24
24
  private readonly isServiceUrl;
25
25
  private readonly tokenUrlMatcher?;
26
- constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: ((url: string) => boolean) | undefined);
26
+ constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: ((url: string) => boolean) | undefined);
27
27
  apply(target: T, _: typeof window, argsList: [RequestInfo | URL, RequestInit]): any;
28
28
  protected beforeFetch(item: NetworkMessage, input: RequestInfo | string, init?: RequestInit): void;
29
29
  protected afterFetch(item: NetworkMessage): (resp: Response) => Response;
30
30
  protected handleResponseBody(resp: Response, item: NetworkMessage): Promise<string> | Promise<ArrayBuffer>;
31
31
  }
32
32
  export default class FetchProxy {
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, tokenUrlMatcher?: (url: string) => boolean): typeof fetch;
33
+ static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (item: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean): typeof fetch;
34
34
  }
@@ -237,7 +237,10 @@ export class FetchProxyHandler {
237
237
  typeof responseValue === 'string' ? responseValue.length : responseValue.byteLength;
238
238
  item.responseSizeText = formatByteSize(item.responseSize);
239
239
  item.response = getStringResponseByType(item.responseType, responseValue);
240
- this.sendMessage(item.getMessage());
240
+ const msg = item.getMessage();
241
+ if (msg) {
242
+ this.sendMessage(msg);
243
+ }
241
244
  });
242
245
  }
243
246
  return new Proxy(resp, new ResponseProxyHandler(resp, item));
@@ -1,3 +1,3 @@
1
1
  import { RequestResponseData } from './types.js';
2
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, tokenUrlMatcher?: (url: string) => boolean): void;
3
+ export default function setProxy(context: typeof globalThis, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean): void;
@@ -39,8 +39,8 @@ export default class NetworkMessage {
39
39
  [key: string]: string;
40
40
  };
41
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;
42
+ constructor(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null);
43
+ getMessage(): import("../../common/messages.gen.js").NetworkRequest | undefined;
44
44
  writeHeaders(): {
45
45
  reqHs: Record<string, string>;
46
46
  resHs: Record<string, string>;
@@ -50,6 +50,8 @@ export default class NetworkMessage {
50
50
  request,
51
51
  response,
52
52
  });
53
+ if (!messageInfo)
54
+ return;
53
55
  return NetworkRequest(this.requestType, messageInfo.method, messageInfo.url, JSON.stringify(messageInfo.request), JSON.stringify(messageInfo.response), messageInfo.status, this.startTime + getTimeOrigin(), this.duration, this.responseSize);
54
56
  }
55
57
  writeHeaders() {
@@ -17,7 +17,7 @@ export declare class XHRProxyHandler<T extends XMLHttpRequest> implements ProxyH
17
17
  private readonly tokenUrlMatcher?;
18
18
  XMLReq: XMLHttpRequest;
19
19
  item: NetworkMessage;
20
- constructor(XMLReq: XMLHttpRequest, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: ((url: string) => boolean) | undefined);
20
+ constructor(XMLReq: XMLHttpRequest, ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (message: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: ((url: string) => boolean) | undefined);
21
21
  get(target: T, key: string): any;
22
22
  set(target: T, key: string, value: (args: any[]) => any): boolean;
23
23
  onReadyStateChange(): void;
@@ -35,5 +35,5 @@ export declare class XHRProxyHandler<T extends XMLHttpRequest> implements ProxyH
35
35
  protected updateItemByReadyState(): void;
36
36
  }
37
37
  export default class XHRProxy {
38
- static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData, sendMessage: (data: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean): any;
38
+ static create(ignoredHeaders: boolean | string[], setSessionTokenHeader: (cb: (name: string, value: string) => void) => void, sanitize: (data: RequestResponseData) => RequestResponseData | null, sendMessage: (data: NetworkRequest) => void, isServiceUrl: (url: string) => boolean, tokenUrlMatcher?: (url: string) => boolean): any;
39
39
  }
@@ -80,18 +80,27 @@ export class XHRProxyHandler {
80
80
  this.item.response = getStringResponseByType(this.item.responseType, this.item.response);
81
81
  }, 0);
82
82
  if (this.XMLReq.readyState === RequestState.DONE) {
83
- this.sendMessage(this.item.getMessage());
83
+ const msg = this.item.getMessage();
84
+ if (msg) {
85
+ this.sendMessage(msg);
86
+ }
84
87
  }
85
88
  }
86
89
  onAbort() {
87
90
  this.item.cancelState = 1;
88
91
  this.item.statusText = 'Abort';
89
- this.sendMessage(this.item.getMessage());
92
+ const msg = this.item.getMessage();
93
+ if (msg) {
94
+ this.sendMessage(msg);
95
+ }
90
96
  }
91
97
  onTimeout() {
92
98
  this.item.cancelState = 3;
93
99
  this.item.statusText = 'Timeout';
94
- this.sendMessage(this.item.getMessage());
100
+ const msg = this.item.getMessage();
101
+ if (msg) {
102
+ this.sendMessage(msg);
103
+ }
95
104
  }
96
105
  getOpen(target) {
97
106
  const targetFunction = Reflect.get(target, 'open');
@@ -15,7 +15,7 @@ export interface RequestResponseData {
15
15
  request: RequestData;
16
16
  response: ResponseData;
17
17
  }
18
- type Sanitizer = (data: RequestResponseData) => RequestResponseData;
18
+ type Sanitizer = (data: RequestResponseData) => RequestResponseData | null;
19
19
  export interface Options {
20
20
  sessionTokenHeader: string | boolean;
21
21
  failuresOnly: boolean;
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": "12.0.5",
4
+ "version": "12.0.6",
5
5
  "keywords": [
6
6
  "logging",
7
7
  "replay"