diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 00000000..2a628ade --- /dev/null +++ b/index.d.ts @@ -0,0 +1,6 @@ +// TODO: remove this when mitt updated +declare namespace mitt { + interface MittStatic { + (all?: { [key: string]: Array }): Emitter; + } +} diff --git a/package.json b/package.json index 32393d9d..9ebe6615 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "rrweb", "version": "0.3.0", "description": "record and replay the web", - "main": "index.js", + "main": "dist/index.js", + "module": "dist/module.js", "scripts": { "test": "TS_NODE_CACHE=false TS_NODE_FILES=true mocha -r ts-node/register test/**/*.ts", "bundle": "rollup --config" @@ -38,6 +39,7 @@ "typescript": "^3.1.1" }, "dependencies": { - "rrweb-snapshot": "file:../snapshot" + "mitt": "^1.1.3", + "rrweb-snapshot": "^0.4.2" } } diff --git a/rollup.config.js b/rollup.config.js index bb670017..a06d92f9 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -22,21 +22,21 @@ export default [ ], }, { - input: './src/replay/index.ts', + input: './src/index.ts', plugins: [typescript(), resolve()], output: [ { format: 'cjs', - file: './dist/replay/index.js', + file: './dist/index.js', }, { format: 'esm', - file: './dist/replay/module.js', + file: './dist/module.js', }, { - name: 'replay', + name: 'rrweb', format: 'iife', - file: './dist/replay/browser.js', + file: './dist/browser.js', }, ], }, diff --git a/src/replay/index.ts b/src/replay/index.ts index 85082e81..481fe235 100644 --- a/src/replay/index.ts +++ b/src/replay/index.ts @@ -1,4 +1,5 @@ import { rebuild, serializeNodeWithId } from 'rrweb-snapshot'; +import * as mittProxy from 'mitt'; import { later, clear } from './timer'; import { EventType, @@ -8,27 +9,44 @@ import { eventWithTime, MouseInteractions, playerConfig, + playerMetaData, + viewportResizeDimention, } from '../types'; import { mirror, getIdNodeMap } from '../utils'; +// https://github.com/rollup/rollup/issues/1267#issuecomment-296395734 +// tslint:disable-next-line +const mitt = (mittProxy as any).default || mittProxy; + const defaultConfig: playerConfig = { speed: 1, + root: document.body, }; export class Replayer { + public wrapper: HTMLDivElement; + private events: eventWithTime[] = []; private config: playerConfig; - private wrapper: HTMLDivElement; private iframe: HTMLIFrameElement; private mouse: HTMLDivElement; private startTime: number = 0; private timerIds: number[] = []; + private emitter: mitt.Emitter = mitt(); - constructor(events: eventWithTime[], config: playerConfig = defaultConfig) { + constructor(events: eventWithTime[], config?: Partial) { this.events = events; - this.config = config; + this.handleResize = this.handleResize.bind(this); + + this.setConfig(Object.assign({}, defaultConfig, config)); + this.setupDom(); + this.emitter.on('resize', this.handleResize as mitt.Handler); + } + + public on(event: string, handler: mitt.Handler) { + this.emitter.on(event, handler); } public setConfig(config: Partial) { @@ -38,16 +56,30 @@ export class Replayer { }; } + public getMetaData(): playerMetaData { + if (this.events.length < 2) { + return { + totalTime: 0, + }; + } + const firstEvent = this.events[0]; + const lastEvent = this.events[this.events.length - 1]; + return { + totalTime: lastEvent.timestamp - firstEvent.timestamp, + }; + } + public play() { - this.setupDom(); for (const event of this.events) { switch (event.type) { case EventType.DomContentLoaded: this.startTime = event.timestamp; break; case EventType.Load: - this.iframe.width = `${event.data.width}px`; - this.iframe.height = `${event.data.height}px`; + this.emitter.emit('resize', { + width: event.data.width, + height: event.data.height, + }); break; case EventType.FullSnapshot: this.later(() => { @@ -72,7 +104,7 @@ export class Replayer { private setupDom() { this.wrapper = document.createElement('div'); this.wrapper.classList.add('replayer-wrapper'); - document.body.appendChild(this.wrapper); + this.config.root.appendChild(this.wrapper); this.mouse = document.createElement('div'); this.mouse.classList.add('replayer-mouse'); @@ -82,6 +114,11 @@ export class Replayer { this.wrapper.appendChild(this.iframe); } + private handleResize(dimension: viewportResizeDimention) { + this.iframe.width = `${dimension.width}px`; + this.iframe.height = `${dimension.height}px`; + } + private later(cb: () => void, delayMs: number) { const id = later(cb, delayMs, this.config.speed); this.timerIds.push(id); @@ -213,8 +250,10 @@ export class Replayer { break; } case IncrementalSource.ViewportResize: - this.iframe.width = `${d.width}px`; - this.iframe.height = `${d.height}px`; + this.emitter.emit('resize', { + width: d.width, + height: d.height, + }); break; case IncrementalSource.Input: { const target: HTMLInputElement = (mirror.getNode( diff --git a/src/types.ts b/src/types.ts index ae9ed1fd..517d46d1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -206,4 +206,9 @@ export type hookResetter = () => void; export type playerConfig = { speed: number; + root: Element; +}; + +export type playerMetaData = { + totalTime: number; };