clarity-js 0.7.3 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clarity-js",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "An analytics library that uses web page interactions to generate aggregated insights",
5
5
  "author": "Microsoft Corp.",
6
6
  "license": "MIT",
@@ -10,7 +10,6 @@ let config: Config = {
10
10
  mask: [],
11
11
  unmask: [],
12
12
  regions: [],
13
- extract: [],
14
13
  cookies: [],
15
14
  fraud: true,
16
15
  checksum: [],
@@ -1,2 +1,2 @@
1
- let version = "0.7.3";
1
+ let version = "0.7.4";
2
2
  export default version;
@@ -110,7 +110,7 @@ export default function(event: Event): void {
110
110
  let extractKeys = extract.keys;
111
111
  for (let e of extractKeys) {
112
112
  tokens.push(e);
113
- tokens.push(extract.data[e]);
113
+ tokens.push([].concat(...extract.data[e]));
114
114
  }
115
115
  extract.reset();
116
116
  queue(tokens, false);
@@ -1,38 +1,36 @@
1
1
  import { ExtractSource, Syntax, Type } from "@clarity-types/core";
2
2
  import { Event, Setting, ExtractData } from "@clarity-types/data";
3
- import config from "@src/core/config";
4
3
  import encode from "./encode";
5
4
  import * as internal from "@src/diagnostic/internal";
6
5
  import { Code, Constant, Severity } from "@clarity-types/data";
7
6
 
8
7
  export let data: ExtractData = {};
9
- export let keys: (number | string)[] = [];
10
-
11
- let variables : { [key: number]: Syntax[] } = {};
12
- let selectors : { [key: number]: string } = {};
13
- export let fragments: string[] = [];
8
+ export let keys: number[] = [];
14
9
 
10
+ let variables : { [key: number]: { [key: number]: Syntax[] }} = {};
11
+ let selectors : { [key: number]: { [key: number]: string }} = {};
15
12
  export function start(): void {
13
+ reset();
14
+ }
15
+
16
+ export function trigger(input: string): void {
16
17
  try {
17
- let e = config.extract;
18
- if (!e) { return; }
19
- for (let i = 0; i < e.length; i+=3) {
20
- let source = e[i] as ExtractSource;
21
- let key = e[i+1] as number;
18
+ var parts = input && input.length > 0 ? input.split(/ (.*)/) : [Constant.Empty];
19
+ var key = parseInt(parts[0]);
20
+ var values = parts.length > 1 ? JSON.parse(parts[1]) : {};
21
+ variables[key] = {};
22
+ selectors[key] = {};
23
+ for (var v in values) {
24
+ let id = parseInt(v);
25
+ let value = values[v] as string;
26
+ let source = value.startsWith(Constant.Tilde) ? ExtractSource.Javascript : ExtractSource.Text;
22
27
  switch (source) {
23
28
  case ExtractSource.Javascript:
24
- let variable = e[i+2] as string;
25
- variables[key] = parse(variable);
26
- break;
27
- case ExtractSource.Cookie:
28
- /*Todo: Add cookie extract logic*/
29
+ let variable = value.substring(1, value.length);
30
+ variables[key][id] = parse(variable);
29
31
  break;
30
32
  case ExtractSource.Text:
31
- let match = e[i+2] as string;
32
- selectors[key] = match;
33
- break;
34
- case ExtractSource.Fragment:
35
- fragments = e[i+2] as string[];
33
+ selectors[key][id] = value;
36
34
  break;
37
35
  }
38
36
  }
@@ -49,13 +47,25 @@ export function clone(v: Syntax[]): Syntax[] {
49
47
  export function compute(): void {
50
48
  try {
51
49
  for (let v in variables) {
52
- let value = str(evaluate(clone(variables[v])));
53
- if (value) { update(v, value); }
54
- }
50
+ let key = parseInt(v);
51
+ if (!(key in keys)) {
52
+ let variableData = variables[key];
53
+ for (let v in variableData) {
54
+ let variableKey = parseInt(v);
55
+ let value = str(evaluate(clone(variableData[variableKey])));
56
+ if (value) { update(key, variableKey, value); }
57
+ }
55
58
 
56
- for (let s in selectors) {
57
- let node = document.querySelector(selectors[s] as string) as HTMLElement;
58
- if (node) { update(s, node.innerText); }
59
+ let selectorData = selectors[key];
60
+ for (let s in selectorData) {
61
+ let selectorKey = parseInt(s);
62
+ let nodes = document.querySelectorAll(selectorData[selectorKey]) as NodeListOf<HTMLElement>;
63
+ if (nodes) {
64
+ let text = Array.from(nodes).map(e => e.innerText)
65
+ update(key, selectorKey, text.join(Constant.Seperator).substring(0, Setting.ExtractLimit));
66
+ }
67
+ }
68
+ }
59
69
  }
60
70
  }
61
71
  catch (e) { internal.log(Code.Selector, Severity.Warning, e ? e.name : null); }
@@ -64,21 +74,22 @@ export function compute(): void {
64
74
  }
65
75
 
66
76
  export function reset(): void {
77
+ data = {};
67
78
  keys = [];
79
+ variables = {};
80
+ selectors = {};
68
81
  }
69
82
 
70
- export function update(key: string, value: string | number, force: boolean = false): void {
71
- if (!(key in data) || (key in data && data[key] !== value) || force ) {
72
- data[key] = value;
83
+ export function update(key: number, subkey: number, value: string): void {
84
+ if (!(key in data)) {
85
+ data[key] = []
73
86
  keys.push(key);
74
87
  }
88
+ data[key].push([subkey, value]);
75
89
  }
76
90
 
77
91
  export function stop(): void {
78
- data = {};
79
- keys = [];
80
- variables = {};
81
- selectors = {};
92
+ reset();
82
93
  }
83
94
 
84
95
  function parse(variable: string): Syntax[] {
@@ -15,6 +15,7 @@ import * as metric from "@src/data/metric";
15
15
  import * as ping from "@src/data/ping";
16
16
  import * as timeline from "@src/interaction/timeline";
17
17
  import * as region from "@src/layout/region";
18
+ import * as extract from "@src/data/extract";
18
19
 
19
20
  let discoverBytes: number = 0;
20
21
  let playbackBytes: number = 0;
@@ -242,19 +243,26 @@ function delay(): number {
242
243
  }
243
244
 
244
245
  function response(payload: string): void {
245
- let parts = payload && payload.length > 0 ? payload.split(" ") : [Constant.Empty];
246
- switch (parts[0]) {
247
- case Constant.End:
248
- // Clear out session storage and end the session so we can start fresh the next time
249
- limit.trigger(Check.Server);
250
- break;
251
- case Constant.Upgrade:
252
- // Upgrade current session to send back playback information
253
- clarity.upgrade(Constant.Auto);
254
- break;
255
- case Constant.Action:
256
- // Invoke action callback, if configured and has a valid value
257
- if (config.action && parts.length > 1) { config.action(parts[1]); }
258
- break;
246
+ let lines = payload && payload.length > 0 ? payload.split("\n") : [];
247
+ for (var line of lines)
248
+ {
249
+ let parts = line && line.length > 0 ? line.split(/ (.*)/) : [Constant.Empty];
250
+ switch (parts[0]) {
251
+ case Constant.End:
252
+ // Clear out session storage and end the session so we can start fresh the next time
253
+ limit.trigger(Check.Server);
254
+ break;
255
+ case Constant.Upgrade:
256
+ // Upgrade current session to send back playback information
257
+ clarity.upgrade(Constant.Auto);
258
+ break;
259
+ case Constant.Action:
260
+ // Invoke action callback, if configured and has a valid value
261
+ if (config.action && parts.length > 1) { config.action(parts[1]); }
262
+ break;
263
+ case Constant.Extract:
264
+ if (parts.length > 1) { extract.trigger(parts[1]); }
265
+ break;
266
+ }
259
267
  }
260
268
  }
package/src/layout/dom.ts CHANGED
@@ -6,8 +6,6 @@ import hash from "@src/core/hash";
6
6
  import * as internal from "@src/diagnostic/internal";
7
7
  import * as region from "@src/layout/region";
8
8
  import * as selector from "@src/layout/selector";
9
- import * as mutation from "@src/layout/mutation";
10
- import * as extract from "@src/data/extract";
11
9
  let index: number = 1;
12
10
  let nodes: Node[] = [];
13
11
  let values: NodeValue[] = [];
@@ -15,7 +13,6 @@ let updateMap: number[] = [];
15
13
  let hashMap: { [hash: string]: number } = {};
16
14
  let override = [];
17
15
  let unmask = [];
18
- let updatedFragments: { [fragment: number]: string } = {};
19
16
  let maskText = [];
20
17
  let maskExclude = [];
21
18
  let maskDisable = [];
@@ -92,14 +89,12 @@ export function add(node: Node, parent: Node, data: NodeInfo, source: Source): v
92
89
  let previousId = getPreviousId(node);
93
90
  let parentValue: NodeValue = null;
94
91
  let regionId = region.exists(node) ? id : null;
95
- let fragmentId = null;
96
92
  let fraudId = fraudMap.has(node) ? fraudMap.get(node) : null;
97
93
  let privacyId = config.content ? Privacy.Sensitive : Privacy.TextImage
98
94
  if (parentId >= 0 && values[parentId]) {
99
95
  parentValue = values[parentId];
100
96
  parentValue.children.push(id);
101
97
  regionId = regionId === null ? parentValue.region : regionId;
102
- fragmentId = parentValue.fragment;
103
98
  fraudId = fraudId === null ? parentValue.metadata.fraud : fraudId;
104
99
  privacyId = parentValue.metadata.privacy;
105
100
  }
@@ -121,13 +116,12 @@ export function add(node: Node, parent: Node, data: NodeInfo, source: Source): v
121
116
  hash: null,
122
117
  region: regionId,
123
118
  metadata: { active: true, suspend: false, privacy: privacyId, position: null, fraud: fraudId, size: null },
124
- fragment: fragmentId,
125
119
  };
126
120
 
127
121
  privacy(node, values[id], parentValue);
128
122
  updateSelector(values[id]);
129
123
  size(values[id]);
130
- track(id, source, values[id].fragment);
124
+ track(id, source);
131
125
  }
132
126
 
133
127
  export function update(node: Node, parent: Node, data: NodeInfo, source: Source): void {
@@ -181,14 +175,9 @@ export function update(node: Node, parent: Node, data: NodeInfo, source: Source)
181
175
  }
182
176
  }
183
177
 
184
- // track node if it is a part of scheduled fragment mutation
185
- if(value.fragment && updatedFragments[value.fragment]) {
186
- changed = true;
187
- }
188
-
189
178
  // Update selector
190
179
  updateSelector(value);
191
- track(id, source, values[id].fragment, changed, parentChanged);
180
+ track(id, source, changed, parentChanged);
192
181
  }
193
182
  }
194
183
 
@@ -299,10 +288,6 @@ function updateSelector(value: NodeValue): void {
299
288
  value.selector = [selector.get(s, Selector.Alpha), selector.get(s, Selector.Beta)];
300
289
  value.hash = value.selector.map(x => x ? hash(x) : null) as [string, string];
301
290
  value.hash.forEach(h => hashMap[h] = value.id);
302
- // Match fragment configuration against both alpha and beta hash
303
- if (value.hash.some(h => extract.fragments.indexOf(h) !== -1)) {
304
- value.fragment = value.id;
305
- }
306
291
  }
307
292
 
308
293
  export function hashText(hash: string): string {
@@ -344,11 +329,7 @@ export function updates(): NodeValue[] {
344
329
  if (id in values) { output.push(values[id]); }
345
330
  }
346
331
  updateMap = [];
347
- for (let id in updatedFragments) {
348
- extract.update(updatedFragments[id], id, true)
349
- }
350
-
351
- updatedFragments = {}
332
+
352
333
  return output;
353
334
  }
354
335
 
@@ -377,19 +358,7 @@ function getPreviousId(node: Node): number {
377
358
  return id;
378
359
  }
379
360
 
380
- function track(id: number, source: Source, fragment: number = null, changed: boolean = true, parentChanged: boolean = false): void {
381
- // if updated node is a part of fragment and the fragment is not being tracked currently, schedule a mutation on the fragment node
382
- if (fragment && !updatedFragments[fragment]) {
383
- let node = getNode(fragment)
384
- let value = getValue(fragment);
385
- if (node && value) {
386
- mutation.schedule(node, true);
387
- value.hash.forEach(h => {
388
- if(extract.fragments.indexOf(h) !== -1) { updatedFragments[fragment] = h;}
389
- });
390
- }
391
- }
392
-
361
+ function track(id: number, source: Source, changed: boolean = true, parentChanged: boolean = false): void {
393
362
  // Keep track of the order in which mutations happened, they may not be sequential
394
363
  // Edge case: If an element is added later on, and pre-discovered element is moved as a child.
395
364
  // In that case, we need to reorder the pre-discovered element in the update list to keep visualization consistent.
@@ -210,7 +210,7 @@ async function processNodeList(list: NodeList, source: Source, timer: Timer): Pr
210
210
  }
211
211
  }
212
212
 
213
- export function schedule(node: Node, fragment: boolean = false): Node {
213
+ export function schedule(node: Node): Node {
214
214
  // Only schedule manual trigger for this node if it's not already in the queue
215
215
  if (queue.indexOf(node) < 0) { queue.push(node); }
216
216
 
@@ -218,19 +218,19 @@ export function schedule(node: Node, fragment: boolean = false): Node {
218
218
  // It's common for a webpage to call multiple synchronous "insertRule" / "deleteRule" calls.
219
219
  // And in those cases we do not wish to monitor changes multiple times for the same node.
220
220
  if (timeout) { clearTimeout(timeout); }
221
- timeout = setTimeout(() => { trigger(fragment) }, Setting.LookAhead);
221
+ timeout = setTimeout(() => { trigger() }, Setting.LookAhead);
222
222
 
223
223
  return node;
224
224
  }
225
225
 
226
- function trigger(fragment: boolean): void {
226
+ function trigger(): void {
227
227
  for (let node of queue) {
228
228
  // Generate a mutation for this node only if it still exists
229
229
  if (node) {
230
230
  let shadowRoot = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
231
231
  // Skip re-processing shadowRoot if it was already discovered
232
232
  if (shadowRoot && dom.has(node)) { continue; }
233
- generate(node, shadowRoot || fragment ? Constant.ChildList : Constant.CharacterData);
233
+ generate(node, shadowRoot ? Constant.ChildList : Constant.CharacterData);
234
234
  }
235
235
  }
236
236
  queue = [];
package/types/core.d.ts CHANGED
@@ -5,7 +5,6 @@ type TaskResolve = () => void;
5
5
  type UploadCallback = (data: string) => void;
6
6
  type Region = [number /* RegionId */, string /* Query Selector */];
7
7
  type Checksum = [number /* FraudId */, string /* Query Selector */];
8
- export type Extract = ExtractSource /* Extraction Source */ | number /* Extract Id */ | string | string[] /* Hash or Query Selector or String Token */;
9
8
 
10
9
  /* Enum */
11
10
 
@@ -127,7 +126,6 @@ export interface Config {
127
126
  mask?: string[];
128
127
  unmask?: string[];
129
128
  regions?: Region[];
130
- extract?: Extract[];
131
129
  cookies?: string[];
132
130
  fraud?: boolean;
133
131
  checksum?: Checksum[];
package/types/data.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Time } from "@clarity-types/core";
2
2
  export type Target = (number | Node);
3
- export type Token = (string | number | number[] | string[]);
3
+ export type Token = (string | number | number[] | string[] | (string | number)[]);
4
4
  export type DecodedToken = (any | any[]);
5
5
 
6
6
  export type MetadataCallback = (data: Metadata, playback: boolean) => void;
@@ -263,6 +263,7 @@ export const enum Constant {
263
263
  End = "END",
264
264
  Upgrade = "UPGRADE",
265
265
  Action = "ACTION",
266
+ Extract = "EXTRACT",
266
267
  UserId = "userId",
267
268
  SessionId = "sessionId",
268
269
  PageId = "pageId",
@@ -284,7 +285,8 @@ export const enum Constant {
284
285
  Tilde = "~",
285
286
  ArrayStart = "[",
286
287
  ConditionStart = "{",
287
- ConditionEnd = "}"
288
+ ConditionEnd = "}",
289
+ Seperator = "<SEP>"
288
290
  }
289
291
 
290
292
  export const enum XMLReadyState {
@@ -404,7 +406,7 @@ export interface UpgradeData {
404
406
  }
405
407
 
406
408
  export interface ExtractData {
407
- [key: string]: string | number;
409
+ [key: number]: [number, string][]; // Array of [id, value] for every extracted data
408
410
  }
409
411
 
410
412
  export interface UploadData {
package/types/layout.d.ts CHANGED
@@ -168,7 +168,6 @@ export interface NodeValue {
168
168
  hash: [string, string];
169
169
  region: number;
170
170
  metadata: NodeMeta;
171
- fragment: number;
172
171
  }
173
172
 
174
173
  export interface NodeMeta {