Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 41 additions & 22 deletions packages/all/test/cross-origin-iframe-packer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,35 +50,42 @@ interface IWindow extends Window {
}
type ExtraOptions = {
usePackFn?: boolean;
crossOrigin?: boolean;
};

async function injectRecordScript(
frame: puppeteer.Frame,
options?: ExtraOptions,
allowedOrigins?: string[],
) {
await frame.addScriptTag({
path: path.resolve(__dirname, '../dist/rrweb-all.umd.cjs'),
});
options = options || {};
await frame.evaluate((options) => {
(window as unknown as IWindow).snapshots = [];
const { record, pack } = (window as unknown as IWindow).rrweb;
const config: recordOptions<eventWithTime> = {
recordCrossOriginIframes: true,
recordCanvas: true,
emit(event) {
(window as unknown as IWindow).snapshots.push(event);
(window as unknown as IWindow).emit(event);
},
};
if (options.usePackFn) {
config.packFn = pack;
}
record(config);
}, options);
await frame.evaluate(
(options, allowedOrigins) => {
(window as unknown as IWindow).snapshots = [];
const { record, pack } = (window as unknown as IWindow).rrweb;
const config: recordOptions<eventWithTime> = {
recordCrossOriginIframes: !!allowedOrigins,
recordCanvas: true,
allowedOrigins: allowedOrigins,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this renamed? is this test not working or not running

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test was working since we had the fallback to all origins '*'. Ideally should have another assertion to check the target origin in the post message or another test entirely.

emit(event) {
(window as unknown as IWindow).snapshots.push(event);
(window as unknown as IWindow).emit(event);
},
};
if (options.usePackFn) {
config.packFn = pack;
}
record(config);
},
options,
allowedOrigins,
);

for (const child of frame.childFrames()) {
await injectRecordScript(child, options);
await injectRecordScript(child, options, allowedOrigins);
}
}

Expand All @@ -87,19 +94,24 @@ const setup = function (
content: string,
options?: ExtraOptions,
): ISuite {
const ctx = {} as ISuite;
const ctx = {} as ISuite & {
serverB: http.Server;
serverBURL: string;
};

beforeAll(async () => {
ctx.browser = await launchPuppeteer();
ctx.server = await startServer();
ctx.serverURL = getServerURL(ctx.server);
ctx.serverB = await startServer();
ctx.serverBURL = getServerURL(ctx.serverB);
});

beforeEach(async () => {
ctx.page = await ctx.browser.newPage();
await ctx.page.goto('about:blank');
await ctx.page.goto(`${ctx.serverURL}/html/blank.html`);
await ctx.page.setContent(
content.replace(/\{SERVER_URL\}/g, ctx.serverURL),
content.replace(/\{SERVER_URL\}/g, ctx.serverBURL),
);
ctx.events = [];
await ctx.page.exposeFunction('emit', (e: eventWithTime) => {
Expand All @@ -110,7 +122,10 @@ const setup = function (
});

ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
await injectRecordScript(ctx.page.mainFrame(), options);
const allowedOrigins = options?.crossOrigin
? [ctx.serverURL, ctx.serverBURL]
: undefined;
await injectRecordScript(ctx.page.mainFrame(), options, allowedOrigins);
});

afterEach(async () => {
Expand All @@ -120,6 +135,7 @@ const setup = function (
afterAll(async () => {
await ctx.browser.close();
ctx.server.close();
ctx.serverB.close();
});

return ctx;
Expand All @@ -137,7 +153,10 @@ describe('cross origin iframes & packer', function (this: ISuite) {
</body>
</html>
`;
const ctx = setup.call(this, content, { usePackFn: true });
const ctx = setup.call(this, content, {
usePackFn: true,
crossOrigin: true,
});

describe('should support packFn option in record()', () => {
it('', async () => {
Expand Down
32 changes: 32 additions & 0 deletions packages/rrweb/src/record/cross-origin-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
function toOrigin(url: string): string | null {
try {
const origin = new URL(url).origin;
return origin !== 'null' ? origin : null;
} catch {
return null;
}
}

export function buildAllowedOriginSet(origins: string[]): ReadonlySet<string> {
if (!Array.isArray(origins) || origins.length === 0) {
throw new Error(
'[rrweb] allowedIframeOrigins must be a non-empty array of origin strings.',
);
}

const set = new Set<string>();
for (let i = 0; i < origins.length; i++) {
const entry = origins[i];
if (typeof entry !== 'string') {
throw new Error(
`[rrweb] allowedIframeOrigins[${i}] must be a string, got ${typeof entry}.`,
);
}
const origin = toOrigin(entry);
if (origin) {
set.add(origin);
}
}

return Object.freeze(set);
}
8 changes: 7 additions & 1 deletion packages/rrweb/src/record/iframe-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@ export class IframeManager {
private loadListener?: (iframeEl: HTMLIFrameElement) => unknown;
private stylesheetManager: StylesheetManager;
private recordCrossOriginIframes: boolean;
private allowedIframeOrigins?: ReadonlySet<string>;

constructor(options: {
mirror: Mirror;
mutationCb: mutationCallBack;
stylesheetManager: StylesheetManager;
recordCrossOriginIframes: boolean;
wrappedEmit: (e: eventWithoutTime, isCheckout?: boolean) => void;
allowedIframeOrigins?: ReadonlySet<string>;
}) {
this.mutationCb = options.mutationCb;
this.wrappedEmit = options.wrappedEmit;
this.stylesheetManager = options.stylesheetManager;
this.recordCrossOriginIframes = options.recordCrossOriginIframes;
this.allowedIframeOrigins = options.allowedIframeOrigins;
this.crossOriginIframeStyleMirror = new CrossOriginIframeMirror(
this.stylesheetManager.styleMirror.generateId.bind(
this.stylesheetManager.styleMirror,
Expand Down Expand Up @@ -149,7 +152,10 @@ export class IframeManager {
if (
crossOriginMessageEvent.data.type !== 'rrweb' ||
// To filter out the rrweb messages which are forwarded by some sites.
crossOriginMessageEvent.origin !== crossOriginMessageEvent.data.origin
crossOriginMessageEvent.origin !== crossOriginMessageEvent.data.origin ||
// Drop messages from origins not in the allowlist.
(this.allowedIframeOrigins &&
!this.allowedIframeOrigins.has(crossOriginMessageEvent.origin))
)
return;

Expand Down
26 changes: 24 additions & 2 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
registerErrorHandler,
unregisterErrorHandler,
} from './error-handler';
import { buildAllowedOriginSet } from './cross-origin-utils';
import dom from '@rrweb/utils';

let wrappedEmit!: (e: eventWithoutTime, isCheckout?: boolean) => void;
Expand Down Expand Up @@ -92,7 +93,8 @@ function record<T = eventWithTime>(
mousemoveWait,
recordDOM = true,
recordCanvas = false,
recordCrossOriginIframes = false,
recordCrossOriginIframes: _recordCrossOriginIframes = false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was from before, but we don't need this anymore. reverted

allowedIframeOrigins,
recordAfter = options.recordAfter === 'DOMContentLoaded'
? options.recordAfter
: 'load',
Expand All @@ -107,6 +109,19 @@ function record<T = eventWithTime>(

registerErrorHandler(errorHandler);

const recordCrossOriginIframes = _recordCrossOriginIframes;
let validatedOrigins: ReadonlySet<string> | undefined;
if (
recordCrossOriginIframes &&
allowedIframeOrigins &&
allowedIframeOrigins.length > 0
) {
validatedOrigins = buildAllowedOriginSet(allowedIframeOrigins);
if (validatedOrigins.size === 0) {
validatedOrigins = undefined;
}
}

const inEmittingFrame = recordCrossOriginIframes
? window.parent === window
: true;
Expand Down Expand Up @@ -231,7 +246,13 @@ function record<T = eventWithTime>(
origin: window.location.origin,
isCheckout,
};
window.parent.postMessage(message, '*');
if (validatedOrigins) {
for (const targetOrigin of validatedOrigins) {
window.parent.postMessage(message, targetOrigin);
}
} else {
window.parent.postMessage(message, '*');
}
}

if (e.type === EventType.FullSnapshot) {
Expand Down Expand Up @@ -304,6 +325,7 @@ function record<T = eventWithTime>(
stylesheetManager: stylesheetManager,
recordCrossOriginIframes,
wrappedEmit,
allowedIframeOrigins: validatedOrigins,
});

/**
Expand Down
1 change: 1 addition & 0 deletions packages/rrweb/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export type recordOptions<T> = {
recordDOM?: boolean;
recordCanvas?: boolean;
recordCrossOriginIframes?: boolean;
allowedIframeOrigins?: string[];
recordAfter?: 'DOMContentLoaded' | 'load';
userTriggeredOnInput?: boolean;
collectFonts?: boolean;
Expand Down
Loading