-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ [RUMF-1175] collect reports and csp violation #1332
Merged
amortemousque
merged 26 commits into
main
from
aymeric/collect-reports-and-csp-violation
Mar 4, 2022
Merged
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
2f3b989
✨ Create report observable
amortemousque b391c17
Add reports in Log browser SDK
amortemousque 715c46b
Add report in RUM browser SDK
amortemousque fcd4950
Add new forwardReports config option
amortemousque f74020d
Move report api stubs in its own file
amortemousque 37180c2
Rum track report under FF
amortemousque 43ef98b
Fix configuration validation
amortemousque 875cdc3
Add tests
amortemousque e28db68
👌 Update browser support
amortemousque 2cdc941
👌 use toStackTraceString
amortemousque 998a897
Fix report init option allowedValues check
amortemousque e248579
Send report id instead of type + add more info for non error report
amortemousque 1049d11
👌 Simplify report types
amortemousque 5555282
👌 Add report test when init option is not specified
amortemousque c74dfd2
👌 Rename CustomReport to RawReport
amortemousque 5f2f273
Merge branch 'main' into aymeric/collect-reports-and-csp-violation
amortemousque 7c9a4f9
Update browser support
amortemousque 3b5571d
👌 Report should be UNHANDLED
amortemousque 9b236d8
👌 Add test on getFileFromStackTraceString
amortemousque a6f592f
Report id becomes subtype
amortemousque 885f8e2
👌 Add policy to the stack message
amortemousque ac86464
Merge branch 'main' into aymeric/collect-reports-and-csp-violation
amortemousque fe11c96
Fix eslint issue + update format
amortemousque 18d6978
Merge branch 'main' into aymeric/collect-reports-and-csp-violation
amortemousque 2079e55
Fix ie compatibility
amortemousque 7124d93
Merge branch 'main' into aymeric/collect-reports-and-csp-violation
amortemousque File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
export interface BrowserWindow { | ||
ReportingObserver?: ReportingObserverConstructor | ||
} | ||
|
||
export interface ReportingObserver { | ||
disconnect(): void | ||
observe(): void | ||
takeRecords(): Report[] | ||
} | ||
|
||
export interface ReportingObserverConstructor { | ||
new (callback: ReportingObserverCallback, option: ReportingObserverOption): ReportingObserver | ||
} | ||
|
||
export type ReportType = 'intervention' | 'deprecation' | ||
|
||
export interface Report { | ||
type: ReportType | ||
url: string | ||
body: DeprecationReportBody | InterventionReportBody | ||
} | ||
|
||
export interface ReportingObserverCallback { | ||
(reports: Report[], observer: ReportingObserver): void | ||
} | ||
|
||
export interface ReportingObserverOption { | ||
types: ReportType[] | ||
buffered: boolean | ||
} | ||
|
||
interface DeprecationReportBody { | ||
id: string | ||
message: string | ||
lineNumber: number | ||
columnNumber: number | ||
sourceFile: string | ||
anticipatedRemoval?: Date | ||
} | ||
|
||
interface InterventionReportBody { | ||
id: string | ||
message: string | ||
lineNumber: number | ||
columnNumber: number | ||
sourceFile: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import type { Subscription } from '../../tools/observable' | ||
import { stubReportingObserver, stubCspEventListener } from '../../../test/stubReportApis' | ||
import { initReportObservable, RawReportType } from './reportObservable' | ||
|
||
describe('report observable', () => { | ||
let reportingObserverStub: { reset(): void; raiseReport(type: string): void } | ||
let cspEventListenerStub: { dispatchEvent(): void } | ||
let consoleSubscription: Subscription | ||
let notifyReport: jasmine.Spy | ||
|
||
beforeEach(() => { | ||
reportingObserverStub = stubReportingObserver() | ||
cspEventListenerStub = stubCspEventListener() | ||
notifyReport = jasmine.createSpy('notifyReport') | ||
}) | ||
|
||
afterEach(() => { | ||
reportingObserverStub.reset() | ||
consoleSubscription.unsubscribe() | ||
}) | ||
;[RawReportType.deprecation, RawReportType.intervention].forEach((type) => { | ||
it(`should notify ${type} reports`, () => { | ||
consoleSubscription = initReportObservable([type]).subscribe(notifyReport) | ||
reportingObserverStub.raiseReport(type) | ||
|
||
const [report] = notifyReport.calls.mostRecent().args | ||
|
||
expect(report).toEqual( | ||
jasmine.objectContaining({ | ||
message: `${type}: foo bar`, | ||
subtype: 'NavigatorVibrate', | ||
type, | ||
}) | ||
) | ||
}) | ||
}) | ||
|
||
it(`should compute stack for ${RawReportType.intervention}`, () => { | ||
consoleSubscription = initReportObservable([RawReportType.intervention]).subscribe(notifyReport) | ||
reportingObserverStub.raiseReport(RawReportType.intervention) | ||
|
||
const [report] = notifyReport.calls.mostRecent().args | ||
|
||
expect(report.stack).toEqual(`NavigatorVibrate: foo bar | ||
at <anonymous> @ http://foo.bar/index.js:20:10`) | ||
}) | ||
|
||
it(`should notify ${RawReportType.cspViolation}`, () => { | ||
consoleSubscription = initReportObservable([RawReportType.cspViolation]).subscribe(notifyReport) | ||
cspEventListenerStub.dispatchEvent() | ||
|
||
expect(notifyReport).toHaveBeenCalledOnceWith({ | ||
message: `csp_violation: 'blob' blocked by 'worker-src' directive`, | ||
type: 'csp_violation', | ||
subtype: 'worker-src', | ||
stack: `worker-src: 'blob' blocked by 'worker-src' directive of the policy "worker-src 'none'" | ||
at <anonymous> @ http://foo.bar/index.js:17:8`, | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { toStackTraceString } from '../../tools/error' | ||
import { mergeObservables, Observable } from '../../tools/observable' | ||
import { DOM_EVENT, includes, addEventListener, safeTruncate } from '../../tools/utils' | ||
import { monitor } from '../internalMonitoring' | ||
import type { Report, BrowserWindow, ReportType } from './browser.types' | ||
|
||
export const RawReportType = { | ||
intervention: 'intervention', | ||
deprecation: 'deprecation', | ||
cspViolation: 'csp_violation', | ||
} as const | ||
|
||
export type RawReportType = typeof RawReportType[keyof typeof RawReportType] | ||
|
||
export interface RawReport { | ||
type: RawReportType | ||
subtype: string | ||
message: string | ||
stack?: string | ||
} | ||
|
||
export function initReportObservable(apis: RawReportType[]) { | ||
const observables: Array<Observable<RawReport>> = [] | ||
|
||
if (includes(apis, RawReportType.cspViolation)) { | ||
observables.push(createCspViolationReportObservable()) | ||
} | ||
|
||
const reportTypes = apis.filter((api: RawReportType): api is ReportType => api !== RawReportType.cspViolation) | ||
if (reportTypes.length) { | ||
observables.push(createReportObservable(reportTypes)) | ||
} | ||
|
||
return mergeObservables<RawReport>(...observables) | ||
} | ||
|
||
function createReportObservable(reportTypes: ReportType[]) { | ||
const observable = new Observable<RawReport>(() => { | ||
if (!(window as BrowserWindow).ReportingObserver) { | ||
return | ||
} | ||
|
||
const handleReports = monitor((reports: Report[]) => | ||
reports.forEach((report) => { | ||
observable.notify(buildRawReportFromReport(report)) | ||
}) | ||
) | ||
|
||
const observer = new (window as BrowserWindow).ReportingObserver!(handleReports, { | ||
types: reportTypes, | ||
buffered: true, | ||
}) | ||
|
||
observer.observe() | ||
return () => { | ||
observer.disconnect() | ||
} | ||
}) | ||
|
||
return observable | ||
} | ||
|
||
function createCspViolationReportObservable() { | ||
const observable = new Observable<RawReport>(() => { | ||
const handleCspViolation = monitor((event: SecurityPolicyViolationEvent) => { | ||
observable.notify(buildRawReportFromCspViolation(event)) | ||
}) | ||
|
||
const { stop } = addEventListener(document, DOM_EVENT.SECURITY_POLICY_VIOLATION, handleCspViolation) | ||
|
||
return stop | ||
}) | ||
return observable | ||
} | ||
|
||
function buildRawReportFromReport({ type, body }: Report): RawReport { | ||
return { | ||
type, | ||
subtype: body.id, | ||
message: `${type}: ${body.message}`, | ||
stack: buildStack(body.id, body.message, body.sourceFile, body.lineNumber, body.columnNumber), | ||
} | ||
} | ||
|
||
function buildRawReportFromCspViolation(event: SecurityPolicyViolationEvent): RawReport { | ||
const type = RawReportType.cspViolation | ||
const message = `'${event.blockedURI}' blocked by '${event.effectiveDirective}' directive` | ||
return { | ||
type: RawReportType.cspViolation, | ||
subtype: event.effectiveDirective, | ||
message: `${type}: ${message}`, | ||
stack: buildStack( | ||
event.effectiveDirective, | ||
`${message} of the policy "${safeTruncate(event.originalPolicy, 100, '...')}"`, | ||
event.sourceFile, | ||
event.lineNumber, | ||
event.columnNumber | ||
), | ||
} | ||
} | ||
|
||
function buildStack( | ||
name: string, | ||
message: string, | ||
sourceFile: string | undefined, | ||
lineNumber: number | undefined, | ||
columnNumber: number | undefined | ||
): string | undefined { | ||
return ( | ||
sourceFile && | ||
toStackTraceString({ | ||
name, | ||
message, | ||
stack: [ | ||
{ | ||
func: '?', | ||
url: sourceFile, | ||
line: lineNumber, | ||
column: columnNumber, | ||
}, | ||
], | ||
}) | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some thought, I think it is valuable to add the
originalPolicy
to the crafted message.Could be done like so:
${event.blockedURI}' blocked by '${event.effectiveDirective}' directive of the policy "${event.originalPolicy}"
This way we will have message like:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bcaudan @BenoitZugmeyer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some directives could list a large number of domains 🤔
I'd say that it could be added in the stack to keep a minimal message.
We should maybe also truncate the policy to avoid sending too much data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it to the stack. Let me know what you think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!