diff --git a/.gitignore b/.gitignore index 9e8bee9d..e90dee52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,26 @@ -# Visual Studio Code temporary files -.vscode/ - -# Build results -build/ - -# npm packages -node_modules - -# npm logs -npm-debug.log* - -# yarn uses its own yarn.lock file -package-lock.json - -# yarn logs -yarn-error.log - -# chrome debug -debug.log +# Visual Studio Code temporary files +.vscode/ + +# Build results +build/ +clarity.js +decode.js +docs + +# npm packages +.rpt2_cache + +# npm packages +node_modules + +# npm logs +npm-debug.log* + +# yarn uses its own yarn.lock file +package-lock.json + +# yarn logs +yarn-error.log + +# chrome debug +debug.log diff --git a/src/clarity.ts b/src/clarity.ts index abe19278..a9ccd6e3 100644 --- a/src/clarity.ts +++ b/src/clarity.ts @@ -1,29 +1,117 @@ -import { IConfig } from "@clarity-types/config"; -import { State } from "@clarity-types/core"; -import { config } from "./config"; -import { activate, onCustomEvent, onSetPageInfo, onTrigger, state, teardown } from "./core"; -import { mapProperties } from "./utils"; -export { version } from "./core"; - -export function start(customConfig?: IConfig): void { - if (state !== State.Activated) { - mapProperties(customConfig, null, true, config); - activate(); - } -} - -export function stop(): void { - teardown(); -} - -export function trigger(key: string): void { - onTrigger(key); -} - -export function event(kvps: { [key: string]: any }): void { - onCustomEvent(kvps); -} - -export function setPageInfo(pageId: string, userId: string): void { - onSetPageInfo(pageId, userId); -} +import { Config } from "@clarity-types/core"; +import * as core from "@src/core"; +import configuration from "@src/core/config"; +import { bind } from "@src/core/event"; +import measure from "@src/core/measure"; +import * as task from "@src/core/task"; +import * as data from "@src/data"; +import * as diagnostic from "@src/diagnostic"; +import * as interaction from "@src/interaction"; +import * as layout from "@src/layout"; +import * as performance from "@src/performance"; + +const CLARITY = "clarity"; +let status = false; +/** + * Configures clarity Starts the appl + * @param override options to start the application with + * @returns true if config acepted + */ +export function config(override: Config): boolean { + // Process custom configuration overrides, if available + if (status) { return false; } + for (let key in override) { + if (key in configuration) { configuration[key] = override[key]; } + } + return true; +} + +export function start(override: Config = {}): void { + // Check that browser supports required APIs + // And, also that we are not attempting to start Clarity multiple times + if (core.check() && status === false) { + config(override); + status = true; + + core.start(); + data.start(); + measure(diagnostic.start)(); + measure(layout.start)(); + measure(interaction.start)(); + measure(performance.start)(); + } +} + +function restart(): void { + start(); + tag(CLARITY, "restart"); +} + +// Suspend ends the current Clarity instance after a configured timeout period +// The way it differs from the "end" call is that it starts listening to +// user interaction events as soon as it terminates existing clarity instance. +// On the next interaction, it automatically starts another instance under a different page id +// E.g. if configured timeout is 10m, and user stays inactive for an hour. +// In this case, we will suspend clarity after 10m of inactivity and after another 50m when user interacts again +// Clarity will restart and start another instance seamlessly. Effectively not missing any active time, but also +// not holding the session during inactive time periods. +export function suspend(): void { + tag(CLARITY, "suspend"); + end(); + bind(document, "mousemove", restart); + bind(document, "touchstart", restart); + bind(window, "resize", restart); + bind(window, "scroll", restart); + bind(window, "pageshow", restart); +} + +// By default Clarity is asynchronous and will yield by looking for requestIdleCallback. +// However, there can still be situations with single page apps where a user action can result +// in the whole DOM being destroyed and reconstructed. While Clarity will performan favorably out of the box, +// we do allow external clients to manually pause Clarity for that short burst of time and minimize +// performance impact even further. For reference, we are talking 10s of milliseconds optimization here, not seconds. +export function pause(): void { + if (status) { + tag(CLARITY, "pause"); + task.pause(); + } +} + +// This is how external clients can get out of pause state, and resume Clarity to continue monitoring the page +export function resume(): void { + if (status) { + task.resume(); + tag(CLARITY, "resume"); + } +} + +export function end(): void { + if (status) { + measure(performance.end)(); + measure(interaction.end)(); + measure(layout.end)(); + measure(diagnostic.end)(); + data.end(); + core.end(); + + status = false; + } +} + +export function tag(key: string, value: string): void { + // Do not process tags if Clarity is not already activated + if (status) { + measure(data.tag)(key, value); + } +} + +export function upgrade(key: string): void { + // Do not process upgrade call if Clarity is not already activated and in lean mode + if (status && configuration.lean) { + measure(data.upgrade)(key); + } +} + +export function active(): boolean { + return status; +} diff --git a/tsconfig.json b/tsconfig.json index 59038c1b..89ec33c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,24 +1,26 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "lib": ["es2015", "dom"], - "noImplicitAny": true, - "noUnusedLocals": true, - "suppressImplicitAnyIndexErrors": true, - "baseUrl": ".", - "paths": { - "@src/*": ["src/*"], - "@karma/*": ["karma/*"], - "@clarity-types/*": ["types/*"] - } - }, - "include":[ - "src/**/*.ts", - "types/**/*.d.ts", - "karma/**/*.ts", - ], - "atom": { - "rewriteTsconfig": false - } -} +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "lib": ["es2015", "dom"], + "noImplicitAny": true, + "noUnusedLocals": true, + "suppressImplicitAnyIndexErrors": true, + "baseUrl": ".", + "paths": { + "@src/*": ["src/*"], + "@decode/*": ["decode/*"], + "@karma/*": ["karma/*"], + "@clarity-types/*": ["types/*"] + } + }, + "include":[ + "src/**/*.ts", + "decode/**/*.ts", + "types/**/*.d.ts", + "karma/**/*.ts", + ], + "atom": { + "rewriteTsconfig": false + } +}