Skip to content

Commit

Permalink
Auto Page Detection for Single Page Application (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hongyan Jiang authored and GitHub Enterprise committed Jun 7, 2024
1 parent 3fb8fa1 commit ee6da13
Show file tree
Hide file tree
Showing 25 changed files with 671 additions and 75 deletions.
6 changes: 4 additions & 2 deletions .concourse/tasks/sonar-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ run:
-Dsonar.host.url=https://sonarqube.instana.io/ \
-Dsonar.login="${SONARQUBE_TOKEN}" \
-Dsonar.branch.name="${BRANCH_NAME}" \
-Dsonar.sources=./lib \
-Dsonar.sources=lib/ \
-Dsonar.exclusions=lib/__mocks__/** \
-Dsonar.javascript.lcov.reportPaths=./coverage/lcov.info \
-Dsonar.dynamicAnalysis=reuseReports
Expand All @@ -85,7 +86,8 @@ run:
-Dsonar.pullrequest.key="${PR_KEY}" \
-Dsonar.pullrequest.branch="${BRANCH_NAME}" \
-Dsonar.pullrequest.base="${PR_BASE}" \
-Dsonar.sources=./lib \
-Dsonar.sources=lib/ \
-Dsonar.exclusions=lib/__mocks__/** \
-Dsonar.javascript.lcov.reportPaths=./coverage/lcov.info \
-Dsonar.dynamicAnalysis=reuseReports
fi
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.7.1

- Auto detect page transitions on route change.

## 1.7.0

- Migrate the project to typescript.
Expand Down
13 changes: 13 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* eslint-env node */

const config = {
testEnvironment: 'jsdom',
globals: {
DEBUG: true
},
moduleNameMapper: {
'^@lib/(.*)$': '<rootDir>/lib/$1'
}
};

module.exports = config;
16 changes: 11 additions & 5 deletions lib/__mocks__/browser.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-env node */

export const encodeURIComponent = global.encodeURIComponent;
export const inEncodeURIComponent = global.encodeURIComponent;
export const win = global.window;
export const doc = win.document;
export const nav = global.navigator;

export const xhrRequests: Array<any> = [];
export const XMLHttpRequest = function() {
export const XMLHttpRequest = function () {
const requestFunctions: {
open?: (...args: any[]) => void;
send?: (...args: any[]) => void;
Expand All @@ -15,8 +15,12 @@ export const XMLHttpRequest = function() {
// Using the prototype chain to avoid adding mock functions to the jest snapshots.
const request = Object.create(requestFunctions);
request.requestHeader = [];
requestFunctions.open = (...args: any[]) => request.open = args;
requestFunctions.send = (...args: any[]) => request.send = args;
requestFunctions.open = (...args: any[]) => {
request.open = args;
};
requestFunctions.send = (...args: any[]) => {
request.send = args;
};
requestFunctions.setRequestHeader = (...args: any[]) => request.requestHeader.push(args);
xhrRequests.push(request);
return request;
Expand Down Expand Up @@ -49,7 +53,9 @@ export function reset() {

localStorageStore = {};
localStorage.getItem = k => localStorageStore[k];
localStorage.setItem = (k, v) => {localStorageStore[k] = v;};
localStorage.setItem = (k, v) => {
localStorageStore[k] = v;
};
localStorage.removeItem = k => delete localStorageStore[k];
}

Expand Down
6 changes: 3 additions & 3 deletions lib/browser.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// aliasing globals for improved minifications

export const win: typeof window = window;
export const doc: any = win.document;
export const nav: any = navigator;
export const encodeURIComponent: (arg: string) => string = win.encodeURIComponent;
export const doc: typeof win.document = win.document;
export const nav: typeof navigator = navigator;
export const inEncodeURIComponent: (arg: string) => string = win.encodeURIComponent;
export const XMLHttpRequest = win.XMLHttpRequest;
export const originalFetch = win.fetch;
export const localStorage: Storage | null = (function () {
Expand Down
3 changes: 3 additions & 0 deletions lib/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export function processCommand(command: any[]): any {
case 'wrapEventHandlers':
vars.wrapEventHandlers = command[1];
break;
case 'autoPageDetection':
vars.autoPageDetection = command[1];
break;
case 'wrapTimers':
vars.wrapTimers = command[1];
break;
Expand Down
58 changes: 49 additions & 9 deletions lib/commonBeaconProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import {hasOwnProperty} from './util';
import {getActivePhase} from './fsm';
import {warn} from './debug';
import vars from './vars';
import {isAutoPageDetectionEnabled} from './hooks/autoPageDetection';

const maximumNumberOfMetaDataFields = 25;
const maximumLengthPerMetaDataField = 1024;

const languages = determineLanguages();

// Internal Meta data
const maximumNumberOfInternalMetaDataFields = 128;
const maximumLengthPerInternalMetaDataField = 1024;

export function addCommonBeaconProperties(beacon: Partial<Beacon>) {
if (vars.reportingBackends && vars.reportingBackends.length > 0) {
const reportingBackend: ReportingBackend = vars.reportingBackends[0];
Expand All @@ -34,38 +39,71 @@ export function addCommonBeaconProperties(beacon: Partial<Beacon>) {
beacon['agv'] = vars.agentVersion;
// Google Closure compiler is not yet aware of these globals. Make sure it doesn't
// mangle them.
if (nav['connection'] && nav['connection']['effectiveType']) {
beacon['ct'] = nav['connection']['effectiveType'];
const anyNav = nav as any;
if (anyNav['connection'] && anyNav['connection']['effectiveType']) {
beacon['ct'] = anyNav['connection']['effectiveType'];
}

if (doc.visibilityState) {
beacon['h'] = doc.visibilityState === 'hidden' ? 1 : 0;
}

addMetaDataToBeacon(beacon, vars.meta);

if (isAutoPageDetectionEnabled()) {
// uf field will be a comma separated string if more than one use features are supported
beacon['uf'] = 'sn';
}
}

function determineLanguages() {
if (nav.languages && nav.languages.length > 0) {
return nav.languages.slice(0, 5).join(',');
}

if (typeof nav.userLanguage === 'string') {
return [nav.userLanguage].join(',');
const anyNav = nav as any;
if (typeof anyNav.userLanguage === 'string') {
return [anyNav.userLanguage].join(',');
}

return undefined;
}

export function addMetaDataToBeacon(beacon: Partial<Beacon>, meta: Meta) {
addMetaDataImpl(beacon, meta);
}

export function addInternalMetaDataToBeacon(beacon: Partial<Beacon>, meta: Meta) {
const options = {
keyPrefix: 'im_',
maxFields: maximumNumberOfInternalMetaDataFields,
maxLengthPerField: maximumLengthPerInternalMetaDataField,
maxFieldsWarningMsg:
'Maximum number of internal meta data fields exceeded. Not all internal meta data fields will be transmitted.'
};
addMetaDataImpl(beacon, meta, options);
}

function addMetaDataImpl(
beacon: Partial<Beacon>,
meta: Meta,
options?: {keyPrefix?: string; maxFields?: number; maxLengthPerField?: number; maxFieldsWarningMsg?: string}
) {
const keyPrefix = options?.keyPrefix || 'm_';
const maxFields = options?.maxFields || maximumNumberOfMetaDataFields;
const maxLength = options?.maxLengthPerField || maximumLengthPerMetaDataField;
const maxFieldsWarningMsg =
options?.maxFieldsWarningMsg ||
'Maximum number of meta data fields exceeded. Not all meta data fields will be transmitted.';

let i = 0;

for (const key in meta) {
if (hasOwnProperty(meta, key)) {
i++;
if (i > maximumNumberOfMetaDataFields) {
if (i > maxFields) {
if (DEBUG) {
warn('Maximum number of meta data fields exceeded. Not all meta data fields will be transmitted.');
warn(maxFieldsWarningMsg);
}
return;
}
Expand All @@ -83,20 +121,22 @@ export function addMetaDataToBeacon(beacon: Partial<Beacon>, meta: Meta) {
serializedValue = win.JSON.stringify(meta[key]);
} catch (e) {
if (DEBUG) {
warn('JSON serialization of meta data',
warn(
'JSON serialization of meta data',
key,
meta[key],
'failed due to',
e,
'. This value will not be transmitted.');
'. This value will not be transmitted.'
);
}
continue;
}
} else {
serializedValue = String(meta[key]);
}

beacon['m_' + key] = serializedValue.substring(0, maximumLengthPerMetaDataField);
beacon[keyPrefix + key] = serializedValue.substring(0, maxLength);
}
}
}
4 changes: 2 additions & 2 deletions lib/hooks/XMLHttpRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ export function instrumentXMLHttpRequest() {

export function captureHttpHeaders(beacon: Partial<XhrBeacon>, headerString: string) {
const lines = headerString.trim().split(/[\r\n]+/);
for (let i = 0; i < lines.length; i++) {
const items = lines[i].split(': ', 2);
for (const line of lines) {
const items = line.split(': ', 2);
if (matchesAny(vars.headersToCapture, items[0])) {
beacon['h_' + items[0].toLowerCase()] = items[1];
}
Expand Down
Loading

0 comments on commit ee6da13

Please sign in to comment.