@openreplay/tracker 9.0.11-beta.0 → 9.0.11-beta.14

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,6 +1,6 @@
1
1
  # 9.0.11
2
2
 
3
- - fix window.open issue with sessionStorage being inherited (replicating tabId bug)
3
+ - new `resetTabOnWindowOpen` option to fix window.open issue with sessionStorage being inherited (replicating tabId bug), users still should use 'noopener=true' in window.open to prevent it in general...
4
4
 
5
5
  # 9.0.10
6
6
 
@@ -83,6 +83,7 @@ export default class App {
83
83
  private compressionThreshold;
84
84
  private restartAttempts;
85
85
  private readonly bc;
86
+ private readonly contextId;
86
87
  attributeSender: AttributeSender;
87
88
  constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>);
88
89
  private _debug;
package/cjs/app/index.js CHANGED
@@ -33,21 +33,22 @@ function getTimezone() {
33
33
  }
34
34
  class App {
35
35
  constructor(projectKey, sessionToken, options) {
36
- // if (options.onStart !== undefined) {
37
- // deprecationWarn("'onStart' option", "tracker.start().then(/* handle session info */)")
38
- // } ?? maybe onStart is good
39
36
  var _a, _b;
40
37
  this.messages = [];
41
38
  this.startCallbacks = [];
42
39
  this.stopCallbacks = [];
43
40
  this.commitCallbacks = [];
44
41
  this.activityState = ActivityState.NotActive;
45
- this.version = '9.0.11-beta.0'; // TODO: version compatability check inside each plugin.
42
+ this.version = '9.0.11-beta.14'; // TODO: version compatability check inside each plugin.
46
43
  this.compressionThreshold = 24 * 1000;
47
44
  this.restartAttempts = 0;
48
45
  this.bc = null;
49
46
  this._usingOldFetchPlugin = false;
50
47
  this.delay = 0;
48
+ // if (options.onStart !== undefined) {
49
+ // deprecationWarn("'onStart' option", "tracker.start().then(/* handle session info */)")
50
+ // } ?? maybe onStart is good
51
+ this.contextId = Math.random().toString(36).slice(2);
51
52
  this.projectKey = projectKey;
52
53
  this.networkOptions = options.network;
53
54
  this.options = Object.assign({
@@ -69,7 +70,7 @@ class App {
69
70
  forceSingleTab: false,
70
71
  }, options);
71
72
  if (!this.options.forceSingleTab && globalThis && 'BroadcastChannel' in globalThis) {
72
- this.bc = new BroadcastChannel('rick');
73
+ this.bc = (0, utils_js_1.inIframe)() ? null : new BroadcastChannel('rick');
73
74
  }
74
75
  this.revID = this.options.revID;
75
76
  this.localStorage = (_a = this.options.localStorage) !== null && _a !== void 0 ? _a : window.localStorage;
@@ -152,24 +153,43 @@ class App {
152
153
  this._debug('worker_start', e);
153
154
  }
154
155
  const thisTab = this.session.getTabId();
155
- if (!this.session.getSessionToken() && this.bc) {
156
- this.bc.postMessage({ line: 'never-gonna-give-you-up', source: thisTab });
157
- }
156
+ const proto = {
157
+ // ask if there are any tabs alive
158
+ ask: 'never-gonna-give-you-up',
159
+ // yes, there are someone out there
160
+ resp: 'never-gonna-let-you-down',
161
+ // you stole someone's identity
162
+ reg: 'never-gonna-run-around-and-desert-you',
163
+ };
158
164
  if (this.bc) {
165
+ this.bc.postMessage({
166
+ line: proto.ask,
167
+ source: thisTab,
168
+ context: this.contextId,
169
+ });
170
+ }
171
+ if (this.bc !== null) {
159
172
  this.bc.onmessage = (ev) => {
160
- if (ev.data.source === thisTab)
173
+ if (ev.data.context === this.contextId) {
161
174
  return;
162
- if (ev.data.line === 'never-gonna-let-you-down') {
175
+ }
176
+ if (ev.data.line === proto.resp) {
177
+ const sessionToken = ev.data.token;
178
+ this.session.setSessionToken(sessionToken);
179
+ }
180
+ if (ev.data.line === proto.reg) {
163
181
  const sessionToken = ev.data.token;
182
+ this.session.regenerateTabId();
164
183
  this.session.setSessionToken(sessionToken);
165
184
  }
166
- if (ev.data.line === 'never-gonna-give-you-up') {
185
+ if (ev.data.line === proto.ask) {
167
186
  const token = this.session.getSessionToken();
168
187
  if (token && this.bc) {
169
188
  this.bc.postMessage({
170
- line: 'never-gonna-let-you-down',
189
+ line: ev.data.source === thisTab ? proto.reg : proto.resp,
171
190
  token,
172
191
  source: thisTab,
192
+ context: this.contextId,
173
193
  });
174
194
  }
175
195
  }
@@ -498,7 +518,7 @@ class App {
498
518
  return new Promise((resolve) => {
499
519
  setTimeout(() => {
500
520
  resolve(this._start(...args));
501
- }, 10);
521
+ }, 25);
502
522
  });
503
523
  }
504
524
  else {
@@ -508,7 +528,7 @@ class App {
508
528
  document.removeEventListener('visibilitychange', onVisibilityChange);
509
529
  setTimeout(() => {
510
530
  resolve(this._start(...args));
511
- }, 10);
531
+ }, 25);
512
532
  }
513
533
  };
514
534
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -45,6 +45,7 @@ export default class Session {
45
45
  applySessionHash(hash: string): void;
46
46
  getSessionHash(): string | undefined;
47
47
  getTabId(): string;
48
+ regenerateTabId(): void;
48
49
  private createTabId;
49
50
  getInfo(): SessionInfo;
50
51
  reset(): void;
@@ -104,15 +104,18 @@ class Session {
104
104
  this.createTabId();
105
105
  return this.tabId;
106
106
  }
107
+ regenerateTabId() {
108
+ const randomId = (0, utils_js_1.generateRandomId)(12);
109
+ this.app.sessionStorage.setItem(this.options.session_tabid_key, randomId);
110
+ this.tabId = randomId;
111
+ }
107
112
  createTabId() {
108
113
  const localId = this.app.sessionStorage.getItem(this.options.session_tabid_key);
109
114
  if (localId) {
110
115
  this.tabId = localId;
111
116
  }
112
117
  else {
113
- const randomId = (0, utils_js_1.generateRandomId)(12);
114
- this.app.sessionStorage.setItem(this.options.session_tabid_key, randomId);
115
- this.tabId = randomId;
118
+ this.regenerateTabId();
116
119
  }
117
120
  }
118
121
  getInfo() {
package/cjs/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export type Options = Partial<AppOptions & ConsoleOptions & ExceptionOptions & I
20
20
  sessionToken?: string;
21
21
  respectDoNotTrack?: boolean;
22
22
  autoResetOnWindowOpen?: boolean;
23
+ resetTabOnWindowOpen?: boolean;
23
24
  network?: Partial<NetworkOptions>;
24
25
  mouse?: Partial<MouseHandlerOptions>;
25
26
  flags?: {
package/cjs/index.js CHANGED
@@ -131,34 +131,28 @@ class API {
131
131
  void this.featureFlags.reloadFlags();
132
132
  });
133
133
  const wOpen = window.open;
134
- if (options.autoResetOnWindowOpen) {
134
+ if (options.autoResetOnWindowOpen || options.resetTabOnWindowOpen) {
135
135
  app.attachStartCallback(() => {
136
+ var _a;
137
+ const tabId = app.getTabId();
138
+ const sessStorage = (_a = app.sessionStorage) !== null && _a !== void 0 ? _a : window.sessionStorage;
136
139
  // @ts-ignore ?
137
140
  window.open = function (...args) {
138
- app.resetNextPageSession(true);
141
+ if (options.autoResetOnWindowOpen) {
142
+ app.resetNextPageSession(true);
143
+ }
144
+ if (options.resetTabOnWindowOpen) {
145
+ sessStorage.removeItem(options.session_tabid_key || '__openreplay_tabid');
146
+ }
139
147
  wOpen.call(window, ...args);
140
148
  app.resetNextPageSession(false);
149
+ sessStorage.setItem(options.session_tabid_key || '__openreplay_tabid', tabId);
141
150
  };
142
151
  });
143
152
  app.attachStopCallback(() => {
144
153
  window.open = wOpen;
145
154
  });
146
155
  }
147
- else {
148
- app.attachStartCallback(() => {
149
- // this function causes new tab to inherit sessionStorage completely,
150
- // we don't really want that so we remove tabId for a short period
151
- // @ts-ignore
152
- window.open = function (...args) {
153
- var _a;
154
- const tabId = app.getTabId();
155
- const sessStorage = (_a = app.sessionStorage) !== null && _a !== void 0 ? _a : window.sessionStorage;
156
- sessStorage.removeItem(options.session_tabid_key || '__openreplay_tabid');
157
- wOpen.call(window, ...args);
158
- sessStorage.setItem(options.session_tabid_key || '__openreplay_tabid', tabId);
159
- };
160
- });
161
- }
162
156
  }
163
157
  else {
164
158
  console.log("OpenReplay: browser doesn't support API required for tracking or doNotTrack is set to 1.");
@@ -168,7 +162,7 @@ class API {
168
162
  // no-cors issue only with text/plain or not-set Content-Type
169
163
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
170
164
  req.send(JSON.stringify({
171
- trackerVersion: '9.0.11-beta.0',
165
+ trackerVersion: '9.0.11-beta.14',
172
166
  projectKey: options.projectKey,
173
167
  doNotTrack,
174
168
  // TODO: add precise reason (an exact API missing)
package/cjs/utils.d.ts CHANGED
@@ -16,3 +16,4 @@ export declare function hasOpenreplayAttribute(e: Element, attr: string): boolea
16
16
  **/
17
17
  export declare function canAccessIframe(iframe: HTMLIFrameElement): boolean;
18
18
  export declare function generateRandomId(len?: number): string;
19
+ export declare function inIframe(): boolean;
package/cjs/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateRandomId = exports.canAccessIframe = 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.inIframe = exports.generateRandomId = exports.canAccessIframe = 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);
@@ -93,3 +93,12 @@ function generateRandomId(len) {
93
93
  return Array.from(arr, dec2hex).join('');
94
94
  }
95
95
  exports.generateRandomId = generateRandomId;
96
+ function inIframe() {
97
+ try {
98
+ return window.self !== window.top;
99
+ }
100
+ catch (e) {
101
+ return true;
102
+ }
103
+ }
104
+ exports.inIframe = inIframe;
@@ -83,6 +83,7 @@ export default class App {
83
83
  private compressionThreshold;
84
84
  private restartAttempts;
85
85
  private readonly bc;
86
+ private readonly contextId;
86
87
  attributeSender: AttributeSender;
87
88
  constructor(projectKey: string, sessionToken: string | undefined, options: Partial<Options>);
88
89
  private _debug;
package/lib/app/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Timestamp, Metadata, UserID, TabChange, TabData } from './messages.gen.js';
2
- import { now, adjustTimeOrigin, deprecationWarn } from '../utils.js';
2
+ import { now, adjustTimeOrigin, deprecationWarn, inIframe } 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';
@@ -30,21 +30,22 @@ function getTimezone() {
30
30
  }
31
31
  export default class App {
32
32
  constructor(projectKey, sessionToken, options) {
33
- // if (options.onStart !== undefined) {
34
- // deprecationWarn("'onStart' option", "tracker.start().then(/* handle session info */)")
35
- // } ?? maybe onStart is good
36
33
  var _a, _b;
37
34
  this.messages = [];
38
35
  this.startCallbacks = [];
39
36
  this.stopCallbacks = [];
40
37
  this.commitCallbacks = [];
41
38
  this.activityState = ActivityState.NotActive;
42
- this.version = '9.0.11-beta.0'; // TODO: version compatability check inside each plugin.
39
+ this.version = '9.0.11-beta.14'; // TODO: version compatability check inside each plugin.
43
40
  this.compressionThreshold = 24 * 1000;
44
41
  this.restartAttempts = 0;
45
42
  this.bc = null;
46
43
  this._usingOldFetchPlugin = false;
47
44
  this.delay = 0;
45
+ // if (options.onStart !== undefined) {
46
+ // deprecationWarn("'onStart' option", "tracker.start().then(/* handle session info */)")
47
+ // } ?? maybe onStart is good
48
+ this.contextId = Math.random().toString(36).slice(2);
48
49
  this.projectKey = projectKey;
49
50
  this.networkOptions = options.network;
50
51
  this.options = Object.assign({
@@ -66,7 +67,7 @@ export default class App {
66
67
  forceSingleTab: false,
67
68
  }, options);
68
69
  if (!this.options.forceSingleTab && globalThis && 'BroadcastChannel' in globalThis) {
69
- this.bc = new BroadcastChannel('rick');
70
+ this.bc = inIframe() ? null : new BroadcastChannel('rick');
70
71
  }
71
72
  this.revID = this.options.revID;
72
73
  this.localStorage = (_a = this.options.localStorage) !== null && _a !== void 0 ? _a : window.localStorage;
@@ -149,24 +150,43 @@ export default class App {
149
150
  this._debug('worker_start', e);
150
151
  }
151
152
  const thisTab = this.session.getTabId();
152
- if (!this.session.getSessionToken() && this.bc) {
153
- this.bc.postMessage({ line: 'never-gonna-give-you-up', source: thisTab });
154
- }
153
+ const proto = {
154
+ // ask if there are any tabs alive
155
+ ask: 'never-gonna-give-you-up',
156
+ // yes, there are someone out there
157
+ resp: 'never-gonna-let-you-down',
158
+ // you stole someone's identity
159
+ reg: 'never-gonna-run-around-and-desert-you',
160
+ };
155
161
  if (this.bc) {
162
+ this.bc.postMessage({
163
+ line: proto.ask,
164
+ source: thisTab,
165
+ context: this.contextId,
166
+ });
167
+ }
168
+ if (this.bc !== null) {
156
169
  this.bc.onmessage = (ev) => {
157
- if (ev.data.source === thisTab)
170
+ if (ev.data.context === this.contextId) {
158
171
  return;
159
- if (ev.data.line === 'never-gonna-let-you-down') {
172
+ }
173
+ if (ev.data.line === proto.resp) {
174
+ const sessionToken = ev.data.token;
175
+ this.session.setSessionToken(sessionToken);
176
+ }
177
+ if (ev.data.line === proto.reg) {
160
178
  const sessionToken = ev.data.token;
179
+ this.session.regenerateTabId();
161
180
  this.session.setSessionToken(sessionToken);
162
181
  }
163
- if (ev.data.line === 'never-gonna-give-you-up') {
182
+ if (ev.data.line === proto.ask) {
164
183
  const token = this.session.getSessionToken();
165
184
  if (token && this.bc) {
166
185
  this.bc.postMessage({
167
- line: 'never-gonna-let-you-down',
186
+ line: ev.data.source === thisTab ? proto.reg : proto.resp,
168
187
  token,
169
188
  source: thisTab,
189
+ context: this.contextId,
170
190
  });
171
191
  }
172
192
  }
@@ -495,7 +515,7 @@ export default class App {
495
515
  return new Promise((resolve) => {
496
516
  setTimeout(() => {
497
517
  resolve(this._start(...args));
498
- }, 10);
518
+ }, 25);
499
519
  });
500
520
  }
501
521
  else {
@@ -505,7 +525,7 @@ export default class App {
505
525
  document.removeEventListener('visibilitychange', onVisibilityChange);
506
526
  setTimeout(() => {
507
527
  resolve(this._start(...args));
508
- }, 10);
528
+ }, 25);
509
529
  }
510
530
  };
511
531
  document.addEventListener('visibilitychange', onVisibilityChange);
@@ -45,6 +45,7 @@ export default class Session {
45
45
  applySessionHash(hash: string): void;
46
46
  getSessionHash(): string | undefined;
47
47
  getTabId(): string;
48
+ regenerateTabId(): void;
48
49
  private createTabId;
49
50
  getInfo(): SessionInfo;
50
51
  reset(): void;
@@ -102,15 +102,18 @@ export default class Session {
102
102
  this.createTabId();
103
103
  return this.tabId;
104
104
  }
105
+ regenerateTabId() {
106
+ const randomId = generateRandomId(12);
107
+ this.app.sessionStorage.setItem(this.options.session_tabid_key, randomId);
108
+ this.tabId = randomId;
109
+ }
105
110
  createTabId() {
106
111
  const localId = this.app.sessionStorage.getItem(this.options.session_tabid_key);
107
112
  if (localId) {
108
113
  this.tabId = localId;
109
114
  }
110
115
  else {
111
- const randomId = generateRandomId(12);
112
- this.app.sessionStorage.setItem(this.options.session_tabid_key, randomId);
113
- this.tabId = randomId;
116
+ this.regenerateTabId();
114
117
  }
115
118
  }
116
119
  getInfo() {
package/lib/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export type Options = Partial<AppOptions & ConsoleOptions & ExceptionOptions & I
20
20
  sessionToken?: string;
21
21
  respectDoNotTrack?: boolean;
22
22
  autoResetOnWindowOpen?: boolean;
23
+ resetTabOnWindowOpen?: boolean;
23
24
  network?: Partial<NetworkOptions>;
24
25
  mouse?: Partial<MouseHandlerOptions>;
25
26
  flags?: {
package/lib/index.js CHANGED
@@ -126,34 +126,28 @@ export default class API {
126
126
  void this.featureFlags.reloadFlags();
127
127
  });
128
128
  const wOpen = window.open;
129
- if (options.autoResetOnWindowOpen) {
129
+ if (options.autoResetOnWindowOpen || options.resetTabOnWindowOpen) {
130
130
  app.attachStartCallback(() => {
131
+ var _a;
132
+ const tabId = app.getTabId();
133
+ const sessStorage = (_a = app.sessionStorage) !== null && _a !== void 0 ? _a : window.sessionStorage;
131
134
  // @ts-ignore ?
132
135
  window.open = function (...args) {
133
- app.resetNextPageSession(true);
136
+ if (options.autoResetOnWindowOpen) {
137
+ app.resetNextPageSession(true);
138
+ }
139
+ if (options.resetTabOnWindowOpen) {
140
+ sessStorage.removeItem(options.session_tabid_key || '__openreplay_tabid');
141
+ }
134
142
  wOpen.call(window, ...args);
135
143
  app.resetNextPageSession(false);
144
+ sessStorage.setItem(options.session_tabid_key || '__openreplay_tabid', tabId);
136
145
  };
137
146
  });
138
147
  app.attachStopCallback(() => {
139
148
  window.open = wOpen;
140
149
  });
141
150
  }
142
- else {
143
- app.attachStartCallback(() => {
144
- // this function causes new tab to inherit sessionStorage completely,
145
- // we don't really want that so we remove tabId for a short period
146
- // @ts-ignore
147
- window.open = function (...args) {
148
- var _a;
149
- const tabId = app.getTabId();
150
- const sessStorage = (_a = app.sessionStorage) !== null && _a !== void 0 ? _a : window.sessionStorage;
151
- sessStorage.removeItem(options.session_tabid_key || '__openreplay_tabid');
152
- wOpen.call(window, ...args);
153
- sessStorage.setItem(options.session_tabid_key || '__openreplay_tabid', tabId);
154
- };
155
- });
156
- }
157
151
  }
158
152
  else {
159
153
  console.log("OpenReplay: browser doesn't support API required for tracking or doNotTrack is set to 1.");
@@ -163,7 +157,7 @@ export default class API {
163
157
  // no-cors issue only with text/plain or not-set Content-Type
164
158
  // req.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
165
159
  req.send(JSON.stringify({
166
- trackerVersion: '9.0.11-beta.0',
160
+ trackerVersion: '9.0.11-beta.14',
167
161
  projectKey: options.projectKey,
168
162
  doNotTrack,
169
163
  // TODO: add precise reason (an exact API missing)
package/lib/utils.d.ts CHANGED
@@ -16,3 +16,4 @@ export declare function hasOpenreplayAttribute(e: Element, attr: string): boolea
16
16
  **/
17
17
  export declare function canAccessIframe(iframe: HTMLIFrameElement): boolean;
18
18
  export declare function generateRandomId(len?: number): string;
19
+ export declare function inIframe(): boolean;
package/lib/utils.js CHANGED
@@ -81,3 +81,11 @@ export function generateRandomId(len) {
81
81
  safeCrypto.getRandomValues(arr);
82
82
  return Array.from(arr, dec2hex).join('');
83
83
  }
84
+ export function inIframe() {
85
+ try {
86
+ return window.self !== window.top;
87
+ }
88
+ catch (e) {
89
+ return true;
90
+ }
91
+ }
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": "9.0.11-beta.0",
4
+ "version": "9.0.11-beta.14",
5
5
  "keywords": [
6
6
  "logging",
7
7
  "replay"