@openreplay/tracker 4.1.7 → 4.1.8

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.
@@ -109,7 +109,8 @@ export default class App {
109
109
  active(): boolean;
110
110
  resetNextPageSession(flag: boolean): void;
111
111
  private _start;
112
- start(...args: Parameters<App['_start']>): Promise<StartPromiseReturn>;
112
+ start(options?: StartOptions): Promise<StartPromiseReturn>;
113
113
  stop(stopWorker?: boolean): void;
114
+ restart(): void;
114
115
  }
115
116
  export {};
package/cjs/app/index.js CHANGED
@@ -83,7 +83,7 @@ class App {
83
83
  this.worker.onmessage = ({ data }) => {
84
84
  if (data === 'restart') {
85
85
  this.stop(false);
86
- this.start({}, true);
86
+ this.start({ forceNew: true }); // TODO: keep userID & metadata (draw scenarios)
87
87
  }
88
88
  else if (data.type === 'failure') {
89
89
  this.stop(false);
@@ -120,6 +120,7 @@ class App {
120
120
  }
121
121
  send(message, urgent = false) {
122
122
  if (this.activityState === ActivityState.NotActive) {
123
+ // this.debug.log('SendiTrying to send when not active', message) <- crashing the app
123
124
  return;
124
125
  }
125
126
  this.messages.push(message);
@@ -268,7 +269,8 @@ class App {
268
269
  this.sessionStorage.removeItem(this.options.session_reset_key);
269
270
  }
270
271
  }
271
- _start(startOpts = {}, resetByWorker = false) {
272
+ _start(startOpts) {
273
+ (0, utils_js_1.adjustTimeOrigin)();
272
274
  if (!this.worker) {
273
275
  return Promise.resolve(UnsuccessfulStart('No worker found: perhaps, CSP is not set.'));
274
276
  }
@@ -279,13 +281,6 @@ class App {
279
281
  if (startOpts.sessionHash) {
280
282
  this.session.applySessionHash(startOpts.sessionHash);
281
283
  }
282
- if (startOpts.forceNew) { // Reset session metadata only if requested directly
283
- this.session.reset();
284
- }
285
- this.session.assign({
286
- userID: startOpts.userID,
287
- metadata: startOpts.metadata,
288
- });
289
284
  const timestamp = (0, utils_js_1.now)();
290
285
  this.worker.postMessage({
291
286
  type: 'start',
@@ -296,16 +291,23 @@ class App {
296
291
  connAttemptCount: this.options.connAttemptCount,
297
292
  connAttemptGap: this.options.connAttemptGap,
298
293
  });
299
- const lsReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
294
+ this.session.update({
295
+ // TODO: transparent "session" module logic AND explicit internal api for plugins.
296
+ // "updating" with old metadata in order to trigger session's UpdateCallbacks.
297
+ // (for the case of internal .start() calls, like on "restart" webworker signal or assistent connection in tracker-assist )
298
+ metadata: startOpts.metadata || this.session.getInfo().metadata,
299
+ userID: startOpts.userID,
300
+ });
301
+ const sReset = this.sessionStorage.getItem(this.options.session_reset_key);
300
302
  this.sessionStorage.removeItem(this.options.session_reset_key);
301
- const needNewSessionID = startOpts.forceNew || lsReset || resetByWorker;
303
+ const shouldReset = startOpts.forceNew || sReset !== null;
302
304
  return window
303
305
  .fetch(this.options.ingestPoint + '/v1/web/start', {
304
306
  method: 'POST',
305
307
  headers: {
306
308
  'Content-Type': 'application/json',
307
309
  },
308
- body: JSON.stringify(Object.assign(Object.assign({}, this.getTrackerInfo()), { timestamp, userID: this.session.getInfo().userID, token: needNewSessionID ? undefined : this.session.getSessionToken(), deviceMemory: performance_js_1.deviceMemory,
310
+ body: JSON.stringify(Object.assign(Object.assign({}, this.getTrackerInfo()), { timestamp, userID: this.session.getInfo().userID, token: shouldReset ? undefined : this.session.getSessionToken(), deviceMemory: performance_js_1.deviceMemory,
309
311
  jsHeapSizeLimit: performance_js_1.jsHeapSizeLimit })),
310
312
  })
311
313
  .then((r) => {
@@ -327,28 +329,23 @@ class App {
327
329
  if (this.activityState === ActivityState.NotActive) {
328
330
  return Promise.reject('Tracker stopped during authorisation');
329
331
  }
330
- const { token, userUUID, projectID, beaconSizeLimit, delay, // derived from token
331
- sessionID, // derived from token
332
- startTimestamp, // real startTS (server time), derived from sessionID
333
- } = r;
332
+ const { token, userUUID, sessionID, projectID, beaconSizeLimit, startTimestamp, // real startTS, derived from sessionID
333
+ delay, } = r;
334
334
  if (typeof token !== 'string' ||
335
335
  typeof userUUID !== 'string' ||
336
- (typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
337
- typeof sessionID !== 'string' ||
336
+ //typeof startTimestamp !== 'number' ||
337
+ //typeof sessionID !== 'string' ||
338
338
  typeof delay !== 'number' ||
339
339
  (typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined')) {
340
340
  return Promise.reject(`Incorrect server response: ${JSON.stringify(r)}`);
341
341
  }
342
342
  this.delay = delay;
343
+ const prevSessionID = this.session.getInfo().sessionID;
344
+ if (prevSessionID && prevSessionID !== sessionID) {
345
+ this.session.reset();
346
+ }
343
347
  this.session.setSessionToken(token);
344
- this.session.assign({
345
- sessionID,
346
- timestamp: startTimestamp || timestamp,
347
- projectID,
348
- });
349
- // Resend Metadata in case of updated sessionID
350
- Object.entries(this.session.getInfo().metadata)
351
- .forEach(([key, value]) => this.send((0, messages_gen_js_1.Metadata)(key, value)));
348
+ this.session.update({ sessionID, timestamp: startTimestamp || timestamp, projectID }); // TODO: no no-explicit 'any'
352
349
  this.localStorage.setItem(this.options.local_uuid_key, userUUID);
353
350
  this.worker.postMessage({
354
351
  type: 'auth',
@@ -379,16 +376,16 @@ class App {
379
376
  return UnsuccessfulStart(START_ERROR);
380
377
  });
381
378
  }
382
- start(...args) {
379
+ start(options = {}) {
383
380
  if (!document.hidden) {
384
- return this._start(...args);
381
+ return this._start(options);
385
382
  }
386
383
  else {
387
384
  return new Promise((resolve) => {
388
385
  const onVisibilityChange = () => {
389
386
  if (!document.hidden) {
390
387
  document.removeEventListener('visibilitychange', onVisibilityChange);
391
- resolve(this._start(...args));
388
+ resolve(this._start(options));
392
389
  }
393
390
  };
394
391
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -413,5 +410,9 @@ class App {
413
410
  }
414
411
  }
415
412
  }
413
+ restart() {
414
+ this.stop(false);
415
+ this.start({ forceNew: false });
416
+ }
416
417
  }
417
418
  exports.default = App;
@@ -23,7 +23,7 @@ export default class Session {
23
23
  constructor(app: App, options: Options);
24
24
  attachUpdateCallback(cb: OnUpdateCallback): void;
25
25
  private handleUpdate;
26
- assign(newInfo: Partial<SessionInfo>): void;
26
+ update(newInfo: Partial<SessionInfo>): void;
27
27
  setMetadata(key: string, value: string): void;
28
28
  setUserID(userID: string): void;
29
29
  private getPageNumber;
@@ -21,7 +21,7 @@ class Session {
21
21
  }
22
22
  this.callbacks.forEach((cb) => cb(newInfo));
23
23
  }
24
- assign(newInfo) {
24
+ update(newInfo) {
25
25
  if (newInfo.userID !== undefined) {
26
26
  // TODO clear nullable/undefinable types
27
27
  this.userID = newInfo.userID;
@@ -70,12 +70,13 @@ function default_1(app, opts) {
70
70
  } // Resources are necessary for all timings
71
71
  let resources = {};
72
72
  function resourceTiming(entry) {
73
+ var _a;
73
74
  if (entry.duration < 0 || !(0, utils_js_1.isURL)(entry.name) || app.isServiceURL(entry.name))
74
75
  return;
75
76
  if (resources !== null) {
76
77
  resources[entry.name] = entry.startTime + entry.duration;
77
78
  }
78
- app.send((0, messages_gen_js_1.ResourceTiming)(entry.startTime + performance.timing.navigationStart, entry.duration, entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0, entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, entry.encodedBodySize || 0, entry.decodedBodySize || 0, entry.name, entry.initiatorType));
79
+ app.send((0, messages_gen_js_1.ResourceTiming)(entry.startTime + ((_a = performance === null || performance === void 0 ? void 0 : performance.timing) === null || _a === void 0 ? void 0 : _a.navigationStart), entry.duration, entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0, entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, entry.encodedBodySize || 0, entry.decodedBodySize || 0, entry.name, entry.initiatorType));
79
80
  }
80
81
  const observer = new PerformanceObserver((list) => list.getEntries().forEach(resourceTiming));
81
82
  let prevSessionID;
package/cjs/utils.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export declare const IN_BROWSER: boolean;
2
2
  export declare const IS_FIREFOX: false | RegExpMatchArray | null;
3
3
  export declare const MAX_STR_LEN = 100000;
4
+ export declare function adjustTimeOrigin(): void;
4
5
  export declare const now: () => number;
5
6
  export declare const stars: (str: string) => string;
6
7
  export declare function normSpaces(str: string): string;
package/cjs/utils.js CHANGED
@@ -1,23 +1,21 @@
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.MAX_STR_LEN = exports.IS_FIREFOX = exports.IN_BROWSER = void 0;
3
+ exports.hasOpenreplayAttribute = exports.getLabelAttribute = exports.deprecationWarn = exports.DOCS_HOST = exports.isURL = exports.normSpaces = exports.stars = exports.now = 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);
7
7
  exports.MAX_STR_LEN = 1e5;
8
- // TODO: time adjustment
9
- // // Bad to use `performance.timeOrigin || performance.timing.navigationStart`
10
- // // https://github.com/mdn/content/issues/4713
11
- // const timeOrigin: number | false =
12
- // IN_BROWSER && (Date.now() - performance.now())
13
- // export const now: () => number =
14
- // IN_BROWSER && timeOrigin && !!performance.now
15
- // ? () => Math.round(performance.now() + timeOrigin)
16
- // : () => Date.now()
17
- const navigationStart = exports.IN_BROWSER && (performance.timing.navigationStart || performance.timeOrigin);
18
- // performance.now() is buggy in some browsers
19
- exports.now = exports.IN_BROWSER && performance.now() && navigationStart
20
- ? () => Math.round(performance.now() + navigationStart)
8
+ // Buggy to use `performance.timeOrigin || performance.timing.navigationStart`
9
+ // https://github.com/mdn/content/issues/4713
10
+ let timeOrigin = exports.IN_BROWSER
11
+ ? Date.now() - performance.now()
12
+ : 0;
13
+ function adjustTimeOrigin() {
14
+ timeOrigin = Date.now() - performance.now();
15
+ }
16
+ exports.adjustTimeOrigin = adjustTimeOrigin;
17
+ exports.now = exports.IN_BROWSER && !!performance.now
18
+ ? () => Math.round(performance.now() + timeOrigin)
21
19
  : () => Date.now();
22
20
  exports.stars = 'repeat' in String.prototype
23
21
  ? (str) => '*'.repeat(str.length)
@@ -109,7 +109,8 @@ export default class App {
109
109
  active(): boolean;
110
110
  resetNextPageSession(flag: boolean): void;
111
111
  private _start;
112
- start(...args: Parameters<App['_start']>): Promise<StartPromiseReturn>;
112
+ start(options?: StartOptions): Promise<StartPromiseReturn>;
113
113
  stop(stopWorker?: boolean): void;
114
+ restart(): void;
114
115
  }
115
116
  export {};
package/lib/app/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Timestamp, Metadata, UserID } from './messages.gen.js';
2
- import { now } from '../utils.js';
2
+ import { now, adjustTimeOrigin } from '../utils.js';
3
3
  import Nodes from './nodes.js';
4
4
  import Observer from './observer/top_observer.js';
5
5
  import Sanitizer from './sanitizer.js';
@@ -80,7 +80,7 @@ export default class App {
80
80
  this.worker.onmessage = ({ data }) => {
81
81
  if (data === 'restart') {
82
82
  this.stop(false);
83
- this.start({}, true);
83
+ this.start({ forceNew: true }); // TODO: keep userID & metadata (draw scenarios)
84
84
  }
85
85
  else if (data.type === 'failure') {
86
86
  this.stop(false);
@@ -117,6 +117,7 @@ export default class App {
117
117
  }
118
118
  send(message, urgent = false) {
119
119
  if (this.activityState === ActivityState.NotActive) {
120
+ // this.debug.log('SendiTrying to send when not active', message) <- crashing the app
120
121
  return;
121
122
  }
122
123
  this.messages.push(message);
@@ -265,7 +266,8 @@ export default class App {
265
266
  this.sessionStorage.removeItem(this.options.session_reset_key);
266
267
  }
267
268
  }
268
- _start(startOpts = {}, resetByWorker = false) {
269
+ _start(startOpts) {
270
+ adjustTimeOrigin();
269
271
  if (!this.worker) {
270
272
  return Promise.resolve(UnsuccessfulStart('No worker found: perhaps, CSP is not set.'));
271
273
  }
@@ -276,13 +278,6 @@ export default class App {
276
278
  if (startOpts.sessionHash) {
277
279
  this.session.applySessionHash(startOpts.sessionHash);
278
280
  }
279
- if (startOpts.forceNew) { // Reset session metadata only if requested directly
280
- this.session.reset();
281
- }
282
- this.session.assign({
283
- userID: startOpts.userID,
284
- metadata: startOpts.metadata,
285
- });
286
281
  const timestamp = now();
287
282
  this.worker.postMessage({
288
283
  type: 'start',
@@ -293,16 +288,23 @@ export default class App {
293
288
  connAttemptCount: this.options.connAttemptCount,
294
289
  connAttemptGap: this.options.connAttemptGap,
295
290
  });
296
- const lsReset = this.sessionStorage.getItem(this.options.session_reset_key) !== null;
291
+ this.session.update({
292
+ // TODO: transparent "session" module logic AND explicit internal api for plugins.
293
+ // "updating" with old metadata in order to trigger session's UpdateCallbacks.
294
+ // (for the case of internal .start() calls, like on "restart" webworker signal or assistent connection in tracker-assist )
295
+ metadata: startOpts.metadata || this.session.getInfo().metadata,
296
+ userID: startOpts.userID,
297
+ });
298
+ const sReset = this.sessionStorage.getItem(this.options.session_reset_key);
297
299
  this.sessionStorage.removeItem(this.options.session_reset_key);
298
- const needNewSessionID = startOpts.forceNew || lsReset || resetByWorker;
300
+ const shouldReset = startOpts.forceNew || sReset !== null;
299
301
  return window
300
302
  .fetch(this.options.ingestPoint + '/v1/web/start', {
301
303
  method: 'POST',
302
304
  headers: {
303
305
  'Content-Type': 'application/json',
304
306
  },
305
- body: JSON.stringify(Object.assign(Object.assign({}, this.getTrackerInfo()), { timestamp, userID: this.session.getInfo().userID, token: needNewSessionID ? undefined : this.session.getSessionToken(), deviceMemory,
307
+ body: JSON.stringify(Object.assign(Object.assign({}, this.getTrackerInfo()), { timestamp, userID: this.session.getInfo().userID, token: shouldReset ? undefined : this.session.getSessionToken(), deviceMemory,
306
308
  jsHeapSizeLimit })),
307
309
  })
308
310
  .then((r) => {
@@ -324,28 +326,23 @@ export default class App {
324
326
  if (this.activityState === ActivityState.NotActive) {
325
327
  return Promise.reject('Tracker stopped during authorisation');
326
328
  }
327
- const { token, userUUID, projectID, beaconSizeLimit, delay, // derived from token
328
- sessionID, // derived from token
329
- startTimestamp, // real startTS (server time), derived from sessionID
330
- } = r;
329
+ const { token, userUUID, sessionID, projectID, beaconSizeLimit, startTimestamp, // real startTS, derived from sessionID
330
+ delay, } = r;
331
331
  if (typeof token !== 'string' ||
332
332
  typeof userUUID !== 'string' ||
333
- (typeof startTimestamp !== 'number' && typeof startTimestamp !== 'undefined') ||
334
- typeof sessionID !== 'string' ||
333
+ //typeof startTimestamp !== 'number' ||
334
+ //typeof sessionID !== 'string' ||
335
335
  typeof delay !== 'number' ||
336
336
  (typeof beaconSizeLimit !== 'number' && typeof beaconSizeLimit !== 'undefined')) {
337
337
  return Promise.reject(`Incorrect server response: ${JSON.stringify(r)}`);
338
338
  }
339
339
  this.delay = delay;
340
+ const prevSessionID = this.session.getInfo().sessionID;
341
+ if (prevSessionID && prevSessionID !== sessionID) {
342
+ this.session.reset();
343
+ }
340
344
  this.session.setSessionToken(token);
341
- this.session.assign({
342
- sessionID,
343
- timestamp: startTimestamp || timestamp,
344
- projectID,
345
- });
346
- // Resend Metadata in case of updated sessionID
347
- Object.entries(this.session.getInfo().metadata)
348
- .forEach(([key, value]) => this.send(Metadata(key, value)));
345
+ this.session.update({ sessionID, timestamp: startTimestamp || timestamp, projectID }); // TODO: no no-explicit 'any'
349
346
  this.localStorage.setItem(this.options.local_uuid_key, userUUID);
350
347
  this.worker.postMessage({
351
348
  type: 'auth',
@@ -376,16 +373,16 @@ export default class App {
376
373
  return UnsuccessfulStart(START_ERROR);
377
374
  });
378
375
  }
379
- start(...args) {
376
+ start(options = {}) {
380
377
  if (!document.hidden) {
381
- return this._start(...args);
378
+ return this._start(options);
382
379
  }
383
380
  else {
384
381
  return new Promise((resolve) => {
385
382
  const onVisibilityChange = () => {
386
383
  if (!document.hidden) {
387
384
  document.removeEventListener('visibilitychange', onVisibilityChange);
388
- resolve(this._start(...args));
385
+ resolve(this._start(options));
389
386
  }
390
387
  };
391
388
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -410,4 +407,8 @@ export default class App {
410
407
  }
411
408
  }
412
409
  }
410
+ restart() {
411
+ this.stop(false);
412
+ this.start({ forceNew: false });
413
+ }
413
414
  }
@@ -23,7 +23,7 @@ export default class Session {
23
23
  constructor(app: App, options: Options);
24
24
  attachUpdateCallback(cb: OnUpdateCallback): void;
25
25
  private handleUpdate;
26
- assign(newInfo: Partial<SessionInfo>): void;
26
+ update(newInfo: Partial<SessionInfo>): void;
27
27
  setMetadata(key: string, value: string): void;
28
28
  setUserID(userID: string): void;
29
29
  private getPageNumber;
@@ -19,7 +19,7 @@ export default class Session {
19
19
  }
20
20
  this.callbacks.forEach((cb) => cb(newInfo));
21
21
  }
22
- assign(newInfo) {
22
+ update(newInfo) {
23
23
  if (newInfo.userID !== undefined) {
24
24
  // TODO clear nullable/undefinable types
25
25
  this.userID = newInfo.userID;
@@ -68,12 +68,13 @@ export default function (app, opts) {
68
68
  } // Resources are necessary for all timings
69
69
  let resources = {};
70
70
  function resourceTiming(entry) {
71
+ var _a;
71
72
  if (entry.duration < 0 || !isURL(entry.name) || app.isServiceURL(entry.name))
72
73
  return;
73
74
  if (resources !== null) {
74
75
  resources[entry.name] = entry.startTime + entry.duration;
75
76
  }
76
- app.send(ResourceTiming(entry.startTime + performance.timing.navigationStart, entry.duration, entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0, entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, entry.encodedBodySize || 0, entry.decodedBodySize || 0, entry.name, entry.initiatorType));
77
+ app.send(ResourceTiming(entry.startTime + ((_a = performance === null || performance === void 0 ? void 0 : performance.timing) === null || _a === void 0 ? void 0 : _a.navigationStart), entry.duration, entry.responseStart && entry.startTime ? entry.responseStart - entry.startTime : 0, entry.transferSize > entry.encodedBodySize ? entry.transferSize - entry.encodedBodySize : 0, entry.encodedBodySize || 0, entry.decodedBodySize || 0, entry.name, entry.initiatorType));
77
78
  }
78
79
  const observer = new PerformanceObserver((list) => list.getEntries().forEach(resourceTiming));
79
80
  let prevSessionID;
package/lib/utils.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export declare const IN_BROWSER: boolean;
2
2
  export declare const IS_FIREFOX: false | RegExpMatchArray | null;
3
3
  export declare const MAX_STR_LEN = 100000;
4
+ export declare function adjustTimeOrigin(): void;
4
5
  export declare const now: () => number;
5
6
  export declare const stars: (str: string) => string;
6
7
  export declare function normSpaces(str: string): string;
package/lib/utils.js CHANGED
@@ -2,19 +2,16 @@ const DEPRECATED_ATTRS = { htmlmasked: 'hidden', masked: 'obscured' };
2
2
  export const IN_BROWSER = !(typeof window === 'undefined');
3
3
  export const IS_FIREFOX = IN_BROWSER && navigator.userAgent.match(/firefox|fxios/i);
4
4
  export const MAX_STR_LEN = 1e5;
5
- // TODO: time adjustment
6
- // // Bad to use `performance.timeOrigin || performance.timing.navigationStart`
7
- // // https://github.com/mdn/content/issues/4713
8
- // const timeOrigin: number | false =
9
- // IN_BROWSER && (Date.now() - performance.now())
10
- // export const now: () => number =
11
- // IN_BROWSER && timeOrigin && !!performance.now
12
- // ? () => Math.round(performance.now() + timeOrigin)
13
- // : () => Date.now()
14
- const navigationStart = IN_BROWSER && (performance.timing.navigationStart || performance.timeOrigin);
15
- // performance.now() is buggy in some browsers
16
- export const now = IN_BROWSER && performance.now() && navigationStart
17
- ? () => Math.round(performance.now() + navigationStart)
5
+ // Buggy to use `performance.timeOrigin || performance.timing.navigationStart`
6
+ // https://github.com/mdn/content/issues/4713
7
+ let timeOrigin = IN_BROWSER
8
+ ? Date.now() - performance.now()
9
+ : 0;
10
+ export function adjustTimeOrigin() {
11
+ timeOrigin = Date.now() - performance.now();
12
+ }
13
+ export const now = IN_BROWSER && !!performance.now
14
+ ? () => Math.round(performance.now() + timeOrigin)
18
15
  : () => Date.now();
19
16
  export const stars = 'repeat' in String.prototype
20
17
  ? (str) => '*'.repeat(str.length)
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": "4.1.7",
4
+ "version": "4.1.8",
5
5
  "keywords": [
6
6
  "logging",
7
7
  "replay"