Skip to content

Commit a522149

Browse files
committed
Some more work on metrics (WIP).
1 parent 0e97d37 commit a522149

File tree

6 files changed

+122
-9
lines changed

6 files changed

+122
-9
lines changed

src/editor/index.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
stringify,
1010
fixEditorHeight,
1111
getSelectedAlgorithm,
12-
disableUnsupportedAlgorithms
12+
disableUnsupportedAlgorithms,
13+
getSafeTokenInfo
1314
} from './utils.js';
1415
import { sign, verify, decode } from './jwt.js';
1516
import EventManager from './event-manager.js';
@@ -19,6 +20,7 @@ import {
1920
minSecretLengthCheck,
2021
setupSecretLengthTooltip
2122
} from './secret-length-tooltip.js';
23+
import * as metrics from '../metrics.js';
2224
import {
2325
algorithmSelect,
2426
signatureStatusElement,
@@ -46,6 +48,17 @@ import log from 'loglevel';
4648
// passed to the event manager.
4749
const eventManager = new EventManager();
4850

51+
function trackToken(jwt, operation) {
52+
const tokenInfo = getSafeTokenInfo(jwt);
53+
54+
metric.track('editor-jwt-tracked', {
55+
operation: operation,
56+
tokenInfo: tokenInfo
57+
});
58+
59+
return tokenInfo.hash;
60+
}
61+
4962
function isSharedSecretAlgorithm(algorithm) {
5063
return algorithm && algorithm.indexOf('HS') === 0;
5164
}
@@ -169,6 +182,8 @@ function setAlgorithmInHeader(algorithm) {
169182
function algorithmChangeHandler() {
170183
const algorithm = getSelectedAlgorithm();
171184

185+
metrics.track('editor-algorithm-selected', { algorithm: algorithm });
186+
172187
displaySecretOrKeys(algorithm);
173188

174189
if(isDefaultToken(getTrimmedValue(tokenEditor))) {
@@ -223,13 +238,15 @@ function encodeToken() {
223238
sign(header, payload, key, secretBase64Checkbox.checked).then(encoded => {
224239
eventManager.withDisabledEvents(() => {
225240
tokenEditor.setValue(encoded);
241+
trackToken(encoded, 'encode');
226242
});
227243
}).catch(e => {
228244
eventManager.withDisabledEvents(() => {
229245
log.warn('Failed to sign/encode token: ', e);
230246
markAsInvalid();
231247
tokenEditor.setValue('');
232-
})
248+
});
249+
metrics.track('editor-encoding-error');
233250
}).finally(() => {
234251
verifyToken();
235252
});
@@ -244,9 +261,15 @@ function decodeToken() {
244261
const jwt = getTrimmedValue(tokenEditor);
245262
const decoded = decode(jwt);
246263

264+
const tokenHash = trackToken(jwt, 'decode');
265+
247266
selectAlgorithm(decoded.header.alg);
248267
if(isPublicKeyAlgorithm(decoded.header.alg)) {
249268
downloadPublicKeyIfPossible(decoded).then(publicKey => {
269+
metrics.track('editor-jwt-public-key-downloaded', {
270+
tokenHash: tokenHash
271+
});
272+
250273
eventManager.withDisabledEvents(() => {
251274
publicKeyTextArea.value = publicKey;
252275
verifyToken();
@@ -259,11 +282,20 @@ function decodeToken() {
259282

260283
if(decoded.errors) {
261284
markAsInvalidWithElement(editorElement, false);
285+
metrics.track('editor-jwt-invalid', {
286+
reason: `partial decode`,
287+
tokenHash: tokenHash
288+
});
262289
} else {
263290
verifyToken();
264291
}
265292
} catch(e) {
266293
log.warn('Failed to decode token: ', e);
294+
295+
metrics.track('editor-jwt-invalid', {
296+
reason: `failed to decode token`,
297+
tokenHash: trackToken(jwt)
298+
});
267299
}
268300
});
269301
}
@@ -272,8 +304,14 @@ function verifyToken() {
272304
const jwt = getTrimmedValue(tokenEditor);
273305
const decoded = decode(jwt);
274306

307+
const tokenHash = trackToken(jwt, 'verify');
308+
275309
if(!decoded.header.alg || decoded.header.alg === 'none') {
276310
markAsInvalid();
311+
metrics.track('editor-jwt-invalid', {
312+
reason: `header.alg value is ${decoded.header.alg}`,
313+
tokenHash: tokenHash
314+
});
277315
return;
278316
}
279317

@@ -285,8 +323,15 @@ function verifyToken() {
285323
verify(jwt, publicKeyOrSecret, secretBase64Checkbox.checked).then(valid => {
286324
if(valid) {
287325
markAsValid();
326+
metrics.track('editor-jwt-verified', {
327+
tokenHash: tokenHash
328+
});
288329
} else {
289330
markAsInvalid();
331+
metrics.track('editor-jwt-invalid', {
332+
reason: 'invalid signature',
333+
tokenHash: tokenHash
334+
});
290335
}
291336
});
292337
}

src/editor/utils.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { isWideScreen } from '../utils.js';
2+
import * as metrics from '../metrics.js';
3+
import * as jwt from './jwt.js';
4+
import forge from 'node-forge';
25
import {
36
algorithmSelect,
47
algorithmEs512,
58
editorElement,
69
decodedElement
710
} from '../dom-elements.js';
811

12+
const sha256 = forge.md.sha256.create();
13+
914
export function getTrimmedValue(instance) {
1015
const value = instance.getValue();
1116
if (!value) {
@@ -40,5 +45,42 @@ export function disableUnsupportedAlgorithms() {
4045
// TODO: test supported algorithms in runtime
4146
if(isSafari()) {
4247
algorithmEs512.disabled = true;
48+
metrics.track('editor-disabled-es512-safari');
49+
}
50+
}
51+
52+
export function getSafeTokenInfo(jwt) {
53+
try {
54+
sha256.start();
55+
sha256.update(jwt);
56+
57+
const result = {
58+
hash: sha256.digest().toHex()
59+
};
60+
61+
try {
62+
const decoded = jwt.decode(jwt);
63+
64+
return Object.assign(result, {
65+
decodedWithErrors: decoded.errors,
66+
header: {
67+
alg: decoded.header.alg,
68+
},
69+
payload: {
70+
// TODO
71+
}
72+
});
73+
} catch(e) {
74+
return Object.assign(result, {
75+
error: 'error decoding token',
76+
});
77+
}
78+
} catch(e) {
79+
sha256.start();
80+
81+
return {
82+
error: 'error reading token',
83+
hash: sha256.digest().toHex() // Hash for empty string
84+
};
4385
}
4486
}

src/website/metrics.js renamed to src/metrics.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Analytics from 'analytics-node';
2+
import log from 'loglevel';
23

34
let analytics;
45

@@ -8,6 +9,10 @@ export function init(key) {
89

910
export function track(event, data) {
1011
if(analytics) {
11-
analytics.track(event, data);
12+
try {
13+
analytics.track(event, data);
14+
} catch(e) {
15+
log.error(`Metrics library error: ${e}`);
16+
}
1217
}
1318
}

src/share-button.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import { copyTokenLink } from './utils.js';
22
import { getTokenEditorValue } from './editor';
3+
import * as metrics from './metrics.js';
34

45
import strings from './strings.js';
56

67
export function setupShareJwtButton(shareJwtElement, shareJwtTextElement) {
78
shareJwtElement.addEventListener('click', event => {
89
event.preventDefault();
910

11+
metrics.track('editor-share-button-clicked');
12+
1013
const value = getTokenEditorValue();
1114
if(value.token) {
1215
// If the selected algorithm does not use public keys, publicKey will be
1316
// undefined.
1417
const copiedUrl = copyTokenLink(value.token, value.publicKey);
1518

16-
// We cannot read the clipboard in headless Chrome,
19+
// We cannot read the clipboard in headless Chrome,
1720
// so we use this to let functional tests see the URL. See:
18-
// https://github.com/GoogleChrome/puppeteer/issues/2147
21+
// https://github.com/GoogleChrome/puppeteer/issues/2147
1922
if(!window.test) {
2023
window.test = {};
2124
}

src/website/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import '../google-analytics.js';
2-
import * as metrics from './metrics.js';
2+
import * as metrics from '../metrics.js';
33
import { setupNavbar } from './navbar.js';
44
import { setupExtensionButton } from './extension.js';
55
import { setupLibraries } from './libraries.js';
@@ -74,7 +74,7 @@ function setupMetrics() {
7474
}
7575

7676
if(isPartiallyInViewport(debuggerSection)) {
77-
once('debugger-visible', () => metrics.track('debugger-visible-once'));
77+
once('editor-visible', () => metrics.track('editor-visible-once'));
7878
}
7979
});
8080
}

src/website/libraries.js

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { safeLocalStorageSetItem } from '../utils.js';
22
import { httpGet } from '../utils.js';
3-
import * as metrics from './metrics.js';
3+
import * as metrics from '../metrics.js';
44
import {
55
starsElements,
66
librariesElement,
@@ -79,7 +79,25 @@ function getStarsForGitHubRepos() {
7979
}
8080

8181
function setupMetrics() {
82-
// TODO for clicks
82+
const tracked = [{
83+
selector: '.version p a',
84+
event: 'libraries-jwt-vulns-link-clicked'
85+
}, {
86+
selector: '.maintainer a',
87+
event: 'libraries-maintainer-link-clicked'
88+
}, {
89+
selector: '.repository a',
90+
event: 'libraries-repository-link-clicked'
91+
}];
92+
93+
tracked.forEach(t => {
94+
const els = document.querySelectorAll(t.selector);
95+
Array.prototype.forEach.call(els, el => {
96+
el.addEventListener('click', () => {
97+
metrics.track(t.event);
98+
});
99+
});
100+
});
83101
}
84102

85103
export function setupLibraries() {

0 commit comments

Comments
 (0)