Improve logging in VoiceNotesPlaybackContext
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
parent
7c9ec90e8c
commit
ca0468ec27
3 changed files with 62 additions and 16 deletions
|
@ -7,6 +7,7 @@ import LRU from 'lru-cache';
|
||||||
|
|
||||||
import type { WaveformCache } from '../types/Audio';
|
import type { WaveformCache } from '../types/Audio';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
|
import { redactAttachmentUrl } from '../util/privacy';
|
||||||
|
|
||||||
const MAX_WAVEFORM_COUNT = 1000;
|
const MAX_WAVEFORM_COUNT = 1000;
|
||||||
const MAX_PARALLEL_COMPUTE = 8;
|
const MAX_PARALLEL_COMPUTE = 8;
|
||||||
|
@ -43,7 +44,7 @@ async function getAudioDuration(
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const blob = new Blob([buffer]);
|
const blob = new Blob([buffer]);
|
||||||
const blobURL = URL.createObjectURL(blob);
|
const blobURL = URL.createObjectURL(blob);
|
||||||
|
const urlForLogging = redactAttachmentUrl(url);
|
||||||
const audio = new Audio();
|
const audio = new Audio();
|
||||||
audio.muted = true;
|
audio.muted = true;
|
||||||
audio.src = blobURL;
|
audio.src = blobURL;
|
||||||
|
@ -55,14 +56,14 @@ async function getAudioDuration(
|
||||||
|
|
||||||
audio.addEventListener('error', event => {
|
audio.addEventListener('error', event => {
|
||||||
const error = new Error(
|
const error = new Error(
|
||||||
`Failed to load audio from: ${url} due to error: ${event.type}`
|
`Failed to load audio from: ${urlForLogging} due to error: ${event.type}`
|
||||||
);
|
);
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Number.isNaN(audio.duration)) {
|
if (Number.isNaN(audio.duration)) {
|
||||||
throw new Error(`Invalid audio duration for: ${url}`);
|
throw new Error(`Invalid audio duration for: ${urlForLogging}`);
|
||||||
}
|
}
|
||||||
return audio.duration;
|
return audio.duration;
|
||||||
}
|
}
|
||||||
|
@ -83,12 +84,15 @@ async function doComputePeaks(
|
||||||
): Promise<ComputePeaksResult> {
|
): Promise<ComputePeaksResult> {
|
||||||
const cacheKey = `${url}:${barCount}`;
|
const cacheKey = `${url}:${barCount}`;
|
||||||
const existing = waveformCache.get(cacheKey);
|
const existing = waveformCache.get(cacheKey);
|
||||||
|
const urlForLogging = redactAttachmentUrl(url);
|
||||||
|
|
||||||
|
const logId = `GlobalAudioContext(${urlForLogging})`;
|
||||||
if (existing) {
|
if (existing) {
|
||||||
log.info('GlobalAudioContext: waveform cache hit', url);
|
log.info(`${logId}: waveform cache hit`);
|
||||||
return Promise.resolve(existing);
|
return Promise.resolve(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('GlobalAudioContext: waveform cache miss', url);
|
log.info(`${logId}: waveform cache miss`);
|
||||||
|
|
||||||
// Load and decode `url` into a raw PCM
|
// Load and decode `url` into a raw PCM
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
@ -98,9 +102,7 @@ async function doComputePeaks(
|
||||||
|
|
||||||
const peaks = new Array(barCount).fill(0);
|
const peaks = new Array(barCount).fill(0);
|
||||||
if (duration > MAX_AUDIO_DURATION) {
|
if (duration > MAX_AUDIO_DURATION) {
|
||||||
log.info(
|
log.info(`${logId}: duration ${duration}s is too long`);
|
||||||
`GlobalAudioContext: audio ${url} duration ${duration}s is too long`
|
|
||||||
);
|
|
||||||
const emptyResult = { peaks, duration };
|
const emptyResult = { peaks, duration };
|
||||||
waveformCache.set(cacheKey, emptyResult);
|
waveformCache.set(cacheKey, emptyResult);
|
||||||
return emptyResult;
|
return emptyResult;
|
||||||
|
@ -153,17 +155,15 @@ export async function computePeaks(
|
||||||
barCount: number
|
barCount: number
|
||||||
): Promise<ComputePeaksResult> {
|
): Promise<ComputePeaksResult> {
|
||||||
const computeKey = `${url}:${barCount}`;
|
const computeKey = `${url}:${barCount}`;
|
||||||
|
const logId = `VoiceNotesPlaybackContext(${redactAttachmentUrl(url)})`;
|
||||||
|
|
||||||
const pending = inProgressMap.get(computeKey);
|
const pending = inProgressMap.get(computeKey);
|
||||||
if (pending) {
|
if (pending) {
|
||||||
log.info(
|
log.info(`${logId}: already computing peaks`);
|
||||||
'VoiceNotesPlaybackContext: already computing peaks for',
|
|
||||||
computeKey
|
|
||||||
);
|
|
||||||
return pending;
|
return pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info('VoiceNotesPlaybackContext: queue computing peaks for', computeKey);
|
log.info(`${logId}: queueing computing peaks`);
|
||||||
const promise = computeQueue.add(() => doComputePeaks(url, barCount));
|
const promise = computeQueue.add(() => doComputePeaks(url, barCount));
|
||||||
|
|
||||||
inProgressMap.set(computeKey, promise);
|
inProgressMap.set(computeKey, promise);
|
||||||
|
|
|
@ -103,6 +103,30 @@ describe('Privacy', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('redactAttachmentUrlKeys', () => {
|
||||||
|
it('should redact key= values ', () => {
|
||||||
|
const text =
|
||||||
|
'Log line with url attachment://v2/e6/abcdee64?key=hxKJ9cTfK0v3KEsnzJ2j%2F4Crwe0yu&size=39360&contentType=png ' +
|
||||||
|
'and another already partially redacted attachment://v2/e6/[REDACTED]?LOCALKEY=hxKJ9cTfK0v3KEsnzJ2j%2F4Crwe0yu&size=39360&contentType=png';
|
||||||
|
|
||||||
|
const actual = Privacy.redactAttachmentUrlKeys(text);
|
||||||
|
const expected =
|
||||||
|
'Log line with url attachment://v2/e6/abcdee64?key=[REDACTED] ' +
|
||||||
|
'and another already partially redacted attachment://v2/e6/[REDACTED]?LOCALKEY=[REDACTED]';
|
||||||
|
assert.equal(actual, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('redactAttachmentUrl', () => {
|
||||||
|
it('should remove search params ', () => {
|
||||||
|
const url =
|
||||||
|
'attachment://v2/e6/abcdee64?key=hxKJ9cTfK0v3KEsnzJ2j%2F4Crwe0yu&size=39360&contentType=png';
|
||||||
|
const actual = Privacy.redactAttachmentUrl(url);
|
||||||
|
const expected = 'attachment://v2/e6/abcdee64';
|
||||||
|
assert.equal(actual, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('redactAll', () => {
|
describe('redactAll', () => {
|
||||||
it('should redact all sensitive information', () => {
|
it('should redact all sensitive information', () => {
|
||||||
const encodedAppRootPath = APP_ROOT_PATH.replace(/ /g, '%20');
|
const encodedAppRootPath = APP_ROOT_PATH.replace(/ /g, '%20');
|
||||||
|
@ -114,7 +138,8 @@ describe('Privacy', () => {
|
||||||
`path2 file:///${encodedAppRootPath}/js/background.js.` +
|
`path2 file:///${encodedAppRootPath}/js/background.js.` +
|
||||||
'phone2 +13334445566 lorem\n' +
|
'phone2 +13334445566 lorem\n' +
|
||||||
'group2 group(abcdefghij) doloret\n' +
|
'group2 group(abcdefghij) doloret\n' +
|
||||||
'path3 sensitive-path/attachment.noindex\n';
|
'path3 sensitive-path/attachment.noindex\n' +
|
||||||
|
'attachment://v2/ab/abcde?key=specialkey\n';
|
||||||
|
|
||||||
const actual = Privacy.redactAll(text);
|
const actual = Privacy.redactAll(text);
|
||||||
const expected =
|
const expected =
|
||||||
|
@ -125,7 +150,8 @@ describe('Privacy', () => {
|
||||||
'path2 [REDACTED]/js/background.js.' +
|
'path2 [REDACTED]/js/background.js.' +
|
||||||
'phone2 +[REDACTED]566 lorem\n' +
|
'phone2 +[REDACTED]566 lorem\n' +
|
||||||
'group2 group([REDACTED]hij) doloret\n' +
|
'group2 group([REDACTED]hij) doloret\n' +
|
||||||
'path3 [REDACTED]/attachment.noindex\n';
|
'path3 [REDACTED]/attachment.noindex\n' +
|
||||||
|
'attachment://v2/ab/abcde?key=[REDACTED]\n';
|
||||||
assert.equal(actual, expected);
|
assert.equal(actual, expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,6 +22,7 @@ const GROUP_V2_ID_PATTERN = /(groupv2\()([^=)]+)(=?=?\))/g;
|
||||||
const CALL_LINK_ROOM_ID_PATTERN = /[0-9A-F]{61}([0-9A-F]{3})/gi;
|
const CALL_LINK_ROOM_ID_PATTERN = /[0-9A-F]{61}([0-9A-F]{3})/gi;
|
||||||
const CALL_LINK_ROOT_KEY_PATTERN =
|
const CALL_LINK_ROOT_KEY_PATTERN =
|
||||||
/([A-Z]{4})-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}/gi;
|
/([A-Z]{4})-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}-[A-Z]{4}/gi;
|
||||||
|
const ATTACHMENT_URL_KEY_PATTERN = /(attachment:\/\/[^\s]+key=)([^\s]+)/gi;
|
||||||
const REDACTION_PLACEHOLDER = '[REDACTED]';
|
const REDACTION_PLACEHOLDER = '[REDACTED]';
|
||||||
|
|
||||||
export type RedactFunction = (value: string) => string;
|
export type RedactFunction = (value: string) => string;
|
||||||
|
@ -163,6 +164,14 @@ export const redactCallLinkRootKeys = (text: string): string => {
|
||||||
return text.replace(CALL_LINK_ROOT_KEY_PATTERN, `${REDACTION_PLACEHOLDER}$1`);
|
return text.replace(CALL_LINK_ROOT_KEY_PATTERN, `${REDACTION_PLACEHOLDER}$1`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const redactAttachmentUrlKeys = (text: string): string => {
|
||||||
|
if (!isString(text)) {
|
||||||
|
throw new TypeError("'text' must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.replace(ATTACHMENT_URL_KEY_PATTERN, `$1${REDACTION_PLACEHOLDER}`);
|
||||||
|
};
|
||||||
|
|
||||||
export const redactCdnKey = (cdnKey: string): string => {
|
export const redactCdnKey = (cdnKey: string): string => {
|
||||||
return `${REDACTION_PLACEHOLDER}${cdnKey.slice(-3)}`;
|
return `${REDACTION_PLACEHOLDER}${cdnKey.slice(-3)}`;
|
||||||
};
|
};
|
||||||
|
@ -171,6 +180,16 @@ export const redactGenericText = (text: string): string => {
|
||||||
return `${REDACTION_PLACEHOLDER}${text.slice(-3)}`;
|
return `${REDACTION_PLACEHOLDER}${text.slice(-3)}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const redactAttachmentUrl = (urlString: string): string => {
|
||||||
|
try {
|
||||||
|
const url = new URL(urlString);
|
||||||
|
url.search = '';
|
||||||
|
return url.toString();
|
||||||
|
} catch {
|
||||||
|
return REDACTION_PLACEHOLDER;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const createRedactSensitivePaths = (
|
const createRedactSensitivePaths = (
|
||||||
paths: ReadonlyArray<string>
|
paths: ReadonlyArray<string>
|
||||||
): RedactFunction => {
|
): RedactFunction => {
|
||||||
|
@ -194,7 +213,8 @@ export const redactAll: RedactFunction = compose(
|
||||||
redactPhoneNumbers,
|
redactPhoneNumbers,
|
||||||
redactUuids,
|
redactUuids,
|
||||||
redactCallLinkRoomIds,
|
redactCallLinkRoomIds,
|
||||||
redactCallLinkRootKeys
|
redactCallLinkRootKeys,
|
||||||
|
redactAttachmentUrlKeys
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeNewlines: RedactFunction = text => text.replace(/\r?\n|\r/g, '');
|
const removeNewlines: RedactFunction = text => text.replace(/\r?\n|\r/g, '');
|
||||||
|
|
Loading…
Reference in a new issue