Merge branch 'dev' into api_insights

This commit is contained in:
Kraiem Taha Yassine 2021-09-21 17:27:06 +02:00 committed by GitHub
commit 747c2ff22a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 154 additions and 3135 deletions

View file

@ -25,7 +25,7 @@ ENV TZ=UTC \
MAXMINDDB_FILE=/root/geoip.mmdb \
UAPARSER_FILE=/root/regexes.yaml \
HTTP_PORT=80 \
BEACON_SIZE_LIMIT=1000000 \
BEACON_SIZE_LIMIT=7000000 \
KAFKA_USE_SSL=true \
REDIS_STREAMS_MAX_LEN=3000 \
TOPIC_RAW=raw \

View file

@ -66,13 +66,12 @@ func ResolveCSS(baseURL string, css string) string {
css = rewriteLinks(css, func(rawurl string) string {
return ResolveURL(baseURL, rawurl)
})
return strings.Replace(css, ":hover", ".-asayer-hover", -1)
return strings.Replace(css, ":hover", ".-openreplay-hover", -1)
}
func (r *Rewriter) RewriteCSS(sessionID uint64, baseurl string, css string) string {
css = rewriteLinks(css, func(rawurl string) string {
url , _ := r.RewriteURL(sessionID, baseurl, rawurl)
return url
return r.RewriteURL(sessionID, baseurl, rawurl)
})
return strings.Replace(css, ":hover", ".-asayer-hover", -1)
return strings.Replace(css, ":hover", ".-openreplay-hover", -1)
}

View file

@ -50,23 +50,15 @@ func GetFullCachableURL(baseURL string, relativeURL string) (string, bool) {
if !isRelativeCachable(relativeURL) {
return "", false
}
return ResolveURL(baseURL, relativeURL), true
fullURL := ResolveURL(baseURL, relativeURL)
if !isCachable(fullURL) {
return "", false
}
return fullURL, true
}
const OPENREPLAY_QUERY_START = "OPENREPLAY_QUERY"
func getCachePath(rawurl string) string {
return "/" + strings.ReplaceAll(url.QueryEscape(rawurl), "%", "!") // s3 keys are ok with "!"
// u, _ := url.Parse(rawurl)
// s := "/" + u.Scheme + "/" + u.Hostname() + u.Path
// if u.RawQuery != "" {
// if (s[len(s) - 1] != '/') {
// s += "/"
// }
// s += OPENREPLAY_QUERY_START + url.PathEscape(u.RawQuery)
// }
// return s
}
func getCachePathWithKey(sessionID uint64, rawurl string) string {
@ -82,14 +74,10 @@ func GetCachePathForAssets(sessionID uint64, rawurl string) string {
}
func (r *Rewriter) RewriteURL(sessionID uint64, baseURL string, relativeURL string) (string, bool) {
// TODO: put it in one check within GetFullCachableURL
if !isRelativeCachable(relativeURL) {
return relativeURL, false
}
fullURL := ResolveURL(baseURL, relativeURL)
if !isCachable(fullURL) {
return relativeURL, false
func (r *Rewriter) RewriteURL(sessionID uint64, baseURL string, relativeURL string) string {
fullURL, cachable := GetFullCachableURL(baseURL, relativeURL)
if !cachable {
return relativeURL
}
u := url.URL{
@ -98,6 +86,6 @@ func (r *Rewriter) RewriteURL(sessionID uint64, baseURL string, relativeURL stri
Scheme: r.assetsURL.Scheme,
}
return u.String(), true
return u.String()
}

View file

@ -21,11 +21,8 @@ func sendAssetsForCacheFromCSS(sessionID uint64, baseURL string, css string) {
func handleURL(sessionID uint64, baseURL string, url string) string {
if CACHE_ASSESTS {
rewrittenURL, isCachable := rewriter.RewriteURL(sessionID, baseURL, url)
if isCachable {
sendAssetForCache(sessionID, baseURL, url)
}
return rewrittenURL
sendAssetForCache(sessionID, baseURL, url)
return rewriter.RewriteURL(sessionID, baseURL, url)
}
return assets.ResolveURL(baseURL, url)
}

View file

@ -34,11 +34,12 @@ func startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) {
Reset bool `json:"reset"`
}
type response struct {
Timestamp int64 `json:"timestamp"`
Delay int64 `json:"delay"`
Token string `json:"token"`
UserUUID string `json:"userUUID"`
SessionID string `json:"sessionID"`
Timestamp int64 `json:"timestamp"`
Delay int64 `json:"delay"`
Token string `json:"token"`
UserUUID string `json:"userUUID"`
SessionID string `json:"sessionID"`
BeaconSizeLimit int64 `json:"beaconSizeLimit"`
}
startTime := time.Now()
@ -115,6 +116,7 @@ func startSessionHandlerWeb(w http.ResponseWriter, r *http.Request) {
Token: tokenizer.Compose(*tokenData),
UserUUID: userUUID,
SessionID: strconv.FormatUint(tokenData.ID, 10),
BeaconSizeLimit: BEACON_SIZE_LIMIT,
})
}

View file

@ -8,6 +8,8 @@ import (
"os/signal"
"syscall"
"golang.org/x/net/http2"
"openreplay/backend/pkg/env"
"openreplay/backend/pkg/flakeid"
@ -131,6 +133,7 @@ func main() {
}
}),
}
http2.ConfigureServer(server, nil)
go func() {
if err := server.ListenAndServe(); err != nil {
log.Fatalf("Server error: %v\n", err)

View file

@ -7,6 +7,7 @@ import ListWalker from './ListWalker';
type MouseMoveTimed = MouseMove & Timed;
const HOVER_CLASS = "-openreplay-hover";
const HOVER_CLASS_DEPR = "-asayer-hover";
export default class MouseManager extends ListWalker<MouseMoveTimed> {
private hoverElements: Array<Element> = [];
@ -19,8 +20,14 @@ export default class MouseManager extends ListWalker<MouseMoveTimed> {
const diffAdd = curHoverElements.filter(elem => !this.hoverElements.includes(elem));
const diffRemove = this.hoverElements.filter(elem => !curHoverElements.includes(elem));
this.hoverElements = curHoverElements;
diffAdd.forEach(elem => elem.classList.add(HOVER_CLASS));
diffRemove.forEach(elem => elem.classList.remove(HOVER_CLASS));
diffAdd.forEach(elem => {
elem.classList.add(HOVER_CLASS)
elem.classList.add(HOVER_CLASS_DEPR)
});
diffRemove.forEach(elem => {
elem.classList.remove(HOVER_CLASS)
elem.classList.remove(HOVER_CLASS_DEPR)
});
}
reset(): void {

View file

@ -10,28 +10,31 @@ npm i -D @openreplay/sourcemap-uploader
## CLI
Upload sourcemap for one file:
### Upload a sourcemap for one file:
```
sourcemap-uploader -s https://opnereplay.mycompany.com/api -k API_KEY -p PROJECT_KEY file -m ./dist/index.js.map -u https://myapp.com/index.js
```
Upload all sourcemaps in a given directory. The URL must correspond to the root where you upload JS files from the directory. In other words, if you have your `app-42.js` along with the `app-42.js.map` in the `./build` folder and then want to upload it to your OpenReplay instance so it can be reachable through the link `https://myapp.com/static/app-42.js`, then the command should be like:
### Upload all sourcemaps in a given directory.
The URL must correspond to the root where you upload JS files from the directory. In other words, if you have your `app-42.js` along with the `app-42.js.map` in the `./build` folder and then want to upload it to your OpenReplay instance so it can be reachable through the link `https://myapp.com/static/app-42.js`, then the command should be like:
```
sourcemap-uploader -s https://opnereplay.mycompany.com/api -k API_KEY -p PROJECT_KEY dir -m ./build -u https://myapp.com/static
```
- Use `-s` (`--server`) to specify the URL of your OpenReplay instance (make to append it with /api)
- Use `-s` (`--server`) to specify the URL of your OpenReplay instance (append it with /api).
**Do not use this parameter if you use SaaS version of the OpenRplay**
- Use `-v` (`--verbose`) to see the logs.
## NPM
There are two functions inside `index.js` of the package:
There are two functions you can export from the package:
```
uploadFile(api_key, project_key, sourcemap_file_path, js_file_url)
uploadDir(api_key, project_key, sourcemap_dir_path, js_dir_url)
uploadFile(api_key, project_key, sourcemap_file_path, js_file_url, [server])
uploadDir(api_key, project_key, sourcemap_dir_path, js_dir_url, [server])
```
Both functions return Promise.
Both functions return Promise with a result value to be the list of files for which sourcemaps were uploaded.

View file

@ -58,15 +58,9 @@ const { command, api_key, project_key, server, verbose, ...args } = parser.parse
global._VERBOSE = !!verbose;
try {
global.SERVER = new URL(server || "https://api.openreplay.com");
} catch (e) {
console.error(`Sourcemap Uploader: server URL parse error. ${e}`)
}
(command === 'file'
? uploadFile(api_key, project_key, args.sourcemap_file_path, args.js_file_url)
: uploadDir(api_key, project_key, args.sourcemap_dir_path, args.js_dir_url)
? uploadFile(api_key, project_key, args.sourcemap_file_path, args.js_file_url, server)
: uploadDir(api_key, project_key, args.sourcemap_dir_path, args.js_dir_url, server)
)
.then((sourceFiles) =>
sourceFiles.length > 0

View file

@ -3,12 +3,12 @@ const readFile = require('./lib/readFile.js'),
uploadSourcemaps = require('./lib/uploadSourcemaps.js');
module.exports = {
async uploadFile(api_key, project_key, sourcemap_file_path, js_file_url) {
async uploadFile(api_key, project_key, sourcemap_file_path, js_file_url, server) {
const sourcemap = await readFile(sourcemap_file_path, js_file_url);
return uploadSourcemaps(api_key, project_key, [sourcemap]);
return uploadSourcemaps(api_key, project_key, [sourcemap], server);
},
async uploadDir(api_key, project_key, sourcemap_dir_path, js_dir_url) {
async uploadDir(api_key, project_key, sourcemap_dir_path, js_dir_url, server) {
const sourcemaps = await readDir(sourcemap_dir_path, js_dir_url);
return uploadSourcemaps(api_key, project_key, sourcemaps);
return uploadSourcemaps(api_key, project_key, sourcemaps, server);
},
};

View file

@ -1,15 +1,22 @@
const https = require('https');
const getUploadURLs = (api_key, project_key, js_file_urls) =>
const getUploadURLs = (api_key, project_key, js_file_urls, server) =>
new Promise((resolve, reject) => {
if (js_file_urls.length === 0) {
resolve([]);
}
const pathPrefix = (global.SERVER.pathname + "/").replace(/\/+/g, '/');
let serverURL;
try {
serverURL = new URL(server);
} catch(e) {
return reject(`Failed to parse server URL "${server}".`)
}
const pathPrefix = (serverURL.pathname + "/").replace(/\/+/g, '/');
const options = {
method: 'PUT',
hostname: global.SERVER.host,
hostname: serverURL.host,
path: pathPrefix + `${project_key}/sourcemaps/`,
headers: { Authorization: api_key, 'Content-Type': 'application/json' },
}
@ -74,11 +81,12 @@ const uploadSourcemap = (upload_url, body) =>
req.end();
});
module.exports = (api_key, project_key, sourcemaps) =>
module.exports = (api_key, project_key, sourcemaps, server) =>
getUploadURLs(
api_key,
project_key,
sourcemaps.map(({ js_file_url }) => js_file_url),
server || "https://api.openreplay.com",
).then(upload_urls =>
Promise.all(
upload_urls.map((upload_url, i) =>

View file

@ -1,6 +1,6 @@
{
"name": "@openreplay/sourcemap-uploader",
"version": "3.0.5",
"version": "3.0.6",
"description": "NPM module to upload your JS sourcemaps files to OpenReplay",
"bin": "cli.js",
"main": "index.js",

View file

@ -1,6 +1,6 @@
{
"name": "@openreplay/tracker-assist",
"version": "3.0.1",
"version": "3.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -30,6 +30,12 @@
"js-tokens": "^4.0.0"
}
},
"@medv/finder": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@medv/finder/-/finder-2.1.0.tgz",
"integrity": "sha512-Egrg5XO4kLol24b1Kv50HDfi5hW0yQ6aWSsO0Hea1eJ4rogKElIN0M86FdVnGF4XIGYyA7QWx0MgbOzVPA0qkA==",
"dev": true
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -57,11 +63,12 @@
}
},
"@openreplay/tracker": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@openreplay/tracker/-/tracker-3.0.5.tgz",
"integrity": "sha512-hIY7DnQmm7bCe6v+e257WD7OdNuBOWUZ15Q3yUEdyxu7xDNG7brbak9pS97qCt3VY9xGK0RvW/j3ANlRPk8aVg==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@openreplay/tracker/-/tracker-3.3.0.tgz",
"integrity": "sha512-g9sOG01VaiRLw4TcUbux8j3moa7gsGs8rjZPEVJ5SJqxjje9R7tpUD5UId9ne7QdHSoiHfrWFk3TNRLpXyvImg==",
"dev": true,
"requires": {
"@medv/finder": "^2.0.0",
"error-stack-parser": "^2.0.6"
}
},

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker-assist",
"description": "Tracker plugin for screen assistance through the WebRTC",
"version": "3.0.3",
"version": "3.1.1",
"keywords": [
"WebRTC",
"assistance",
@ -24,10 +24,10 @@
"peerjs": "^1.3.2"
},
"peerDependencies": {
"@openreplay/tracker": "^3.1.0"
"@openreplay/tracker": "^3.3.0"
},
"devDependencies": {
"@openreplay/tracker": "^3.0.5",
"@openreplay/tracker": "^3.3.0",
"prettier": "^1.18.2",
"replace-in-files-cli": "^1.0.0",
"typescript": "^3.6.4"

View file

@ -1,7 +1,7 @@
const declineIcon = `<svg xmlns="http://www.w3.org/2000/svg" height="22" width="22" viewBox="0 0 128 128" ><g id="Circle_Grid" data-name="Circle Grid"><circle cx="64" cy="64" fill="#ef5261" r="64"/></g><g id="icon"><path d="m57.831 70.1c8.79 8.79 17.405 12.356 20.508 9.253l4.261-4.26a7.516 7.516 0 0 1 10.629 0l9.566 9.566a7.516 7.516 0 0 1 0 10.629l-7.453 7.453c-7.042 7.042-27.87-2.358-47.832-22.319-9.976-9.981-16.519-19.382-20.748-28.222s-5.086-16.091-1.567-19.61l7.453-7.453a7.516 7.516 0 0 1 10.629 0l9.566 9.563a7.516 7.516 0 0 1 0 10.629l-4.264 4.271c-3.103 3.1.462 11.714 9.252 20.5z" fill="#eeefee"/></g></svg>`;
export default class Confirm {
export default class ConfirmWindow {
private wrapper: HTMLDivElement;
constructor(text: string, styles?: Object) {

View file

@ -1,92 +0,0 @@
const declineIcon = `<svg xmlns="http://www.w3.org/2000/svg" height="22" width="22" viewBox="0 0 128 128" ><g id="Circle_Grid" data-name="Circle Grid"><circle cx="64" cy="64" fill="#ef5261" r="64"/></g><g id="icon"><path d="m57.831 70.1c8.79 8.79 17.405 12.356 20.508 9.253l4.261-4.26a7.516 7.516 0 0 1 10.629 0l9.566 9.566a7.516 7.516 0 0 1 0 10.629l-7.453 7.453c-7.042 7.042-27.87-2.358-47.832-22.319-9.976-9.981-16.519-19.382-20.748-28.222s-5.086-16.091-1.567-19.61l7.453-7.453a7.516 7.516 0 0 1 10.629 0l9.566 9.563a7.516 7.516 0 0 1 0 10.629l-4.264 4.271c-3.103 3.1.462 11.714 9.252 20.5z" fill="#eeefee"/></g></svg>`;
export default class Confirm {
private wrapper: HTMLDivElement;
constructor(text: string, styles?: Object) {
const wrapper = document.createElement('div');
const popup = document.createElement('div');
const p = document.createElement('p');
p.innerText = text;
const buttons = document.createElement('div');
const answerBtn = document.createElement('button');
answerBtn.innerHTML = declineIcon.replace('fill="#ef5261"', 'fill="green"');
const declineBtn = document.createElement('button');
declineBtn.innerHTML = declineIcon;
buttons.appendChild(answerBtn);
buttons.appendChild(declineBtn);
popup.appendChild(p);
popup.appendChild(buttons);
const btnStyles = {
borderRadius: "50%",
width: "22px",
height: "22px",
background: "transparent",
padding: 0,
margin: 0,
border: 0,
cursor: "pointer",
}
Object.assign(answerBtn.style, btnStyles);
Object.assign(declineBtn.style, btnStyles);
Object.assign(buttons.style, {
marginTop: "10px",
display: "flex",
alignItems: "center",
justifyContent: "space-evenly",
});
Object.assign(popup.style, {
position: "relative",
pointerEvents: "auto",
margin: "4em auto",
width: "90%",
maxWidth: "400px",
padding: "25px 30px",
background: "black",
opacity: ".75",
color: "white",
textAlign: "center",
borderRadius: ".25em .25em .4em .4em",
boxShadow: "0 0 20px rgb(0 0 0 / 20%)",
}, styles);
Object.assign(wrapper.style, {
position: "fixed",
left: 0,
top: 0,
height: "100%",
width: "100%",
pointerEvents: "none",
zIndex: 2147483647 - 1,
})
wrapper.appendChild(popup);
this.wrapper = wrapper;
answerBtn.onclick = () => {
this.remove();
this.callback(true);
}
declineBtn.onclick = () => {
this.remove();
this.callback(false);
}
}
mount() {
document.body.appendChild(this.wrapper);
}
private callback: (result: boolean) => void = ()=>{};
onAnswer(callback: (result: boolean) => void) {
this.callback = callback;
}
remove() {
if (!this.wrapper.parentElement) { return; }
document.body.removeChild(this.wrapper);
}
}

View file

@ -5,7 +5,7 @@ import type Message from '@openreplay/tracker';
import Mouse from './Mouse';
import CallWindow from './CallWindow';
import Confirm from './Confirm';
import ConfirmWindow from './ConfirmWindow';
export interface Options {
@ -34,20 +34,28 @@ export default function(opts: Partial<Options> = {}) {
return;
}
let assistDemandedRestart = false;
let peer : Peer | null = null;
app.attachStopCallback(function() {
if (assistDemandedRestart) { return; }
peer && peer.destroy();
});
app.attachStartCallback(function() {
// @ts-ignore
if (assistDemandedRestart) { return; }
const peerID = `${app.projectKey}-${app.getSessionID()}`
const peer = new Peer(peerID, {
peer = new Peer(peerID, {
// @ts-ignore
host: app.getHost(),
path: '/assist',
port: location.protocol === 'http:' && appOptions.__DISABLE_SECURE_MODE ? 80 : 443,
});
console.log('OpenReplay tracker-assist peerID:', peerID)
peer.on('error', e => console.log("OpenReplay tracker-assist peer error: ", e.type, e))
peer.on('connection', function(conn) {
window.addEventListener("beforeunload", () => conn.open && conn.send("unload"));
peer.on('error', e => console.log("OpenReplay tracker-assist peer error: ", e.type, e))
console.log('OpenReplay tracker-assist: Connecting...')
conn.on('open', function() {
@ -66,9 +74,12 @@ export default function(opts: Partial<Options> = {}) {
buffering = false;
}
}
assistDemandedRestart = true;
app.stop();
//@ts-ignore (should update tracker dependency)
app.addCommitCallback((messages: Array<Message>): void => {
if (!conn.open) { return; } // TODO: clear commit callbacks on connection close
let i = 0;
while (i < messages.length) {
buffer.push(messages.slice(i, i+=1000));
@ -78,30 +89,31 @@ export default function(opts: Partial<Options> = {}) {
sendNext();
}
});
app.start();
app.start().then(() => { assistDemandedRestart = false; });
});
});
let calling: CallingState = CallingState.False;
let callingState: CallingState = CallingState.False;
peer.on('call', function(call) {
if (!peer) { return; }
const dataConn: DataConnection | undefined = peer
.connections[call.peer].find(c => c.type === 'data');
if (calling !== CallingState.False || !dataConn) {
if (callingState !== CallingState.False || !dataConn) {
call.close();
return;
}
calling = CallingState.Requesting;
const notifyCallEnd = () => {
dataConn.open && dataConn.send("call_end");
}
const confirm = new Confirm(options.confirmText, options.confirmStyle);
callingState = CallingState.Requesting;
const confirm = new ConfirmWindow(options.confirmText, options.confirmStyle);
dataConn.on('data', (data) => { // if call closed by a caller before confirm
if (data === "call_end") {
//console.log('OpenReplay tracker-assist: receiving callend onconfirm')
calling = CallingState.False;
callingState = CallingState.False;
confirm.remove();
}
});
@ -110,7 +122,7 @@ export default function(opts: Partial<Options> = {}) {
if (!agreed || !dataConn.open) {
call.close();
notifyCallEnd();
calling = CallingState.False;
callingState = CallingState.False;
return;
}
@ -123,7 +135,7 @@ export default function(opts: Partial<Options> = {}) {
mouse.remove();
callUI?.remove();
lStream.getTracks().forEach(t => t.stop());
calling = CallingState.False;
callingState = CallingState.False;
}
const initiateCallEnd = () => {
//console.log("callend initiated")

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
{
"name": "@openreplay/tracker",
"description": "The OpenReplay tracker main package",
"version": "3.2.5",
"version": "3.3.0",
"keywords": [
"logging",
"replay"
@ -30,9 +30,6 @@
"@typescript-eslint/parser": "^2.34.0",
"eslint": "^6.8.0",
"eslint-plugin-prettier": "^3.1.4",
"gulp": "^4.0.2",
"gulp-typescript": "^6.0.0-alpha.1",
"merge2": "^1.4.1",
"prettier": "^2.0.0",
"replace-in-files": "^2.0.3",
"rollup": "^2.17.0",

View file

@ -11,6 +11,12 @@ import type { Options as ObserverOptions } from './observer';
import type { Options as WebworkerOptions, WorkerMessageData } from '../../messages/webworker';
interface OnStartInfo {
sessionID: string,
sessionToken: string,
userUUID: string,
}
export type Options = {
revID: string;
node_id: string;
@ -20,7 +26,7 @@ export type Options = {
ingestPoint: string;
__is_snippet: boolean;
__debug_report_edp: string | null;
onStart?: (info: { sessionID: string, sessionToken: string, userUUID: string }) => void;
onStart?: (info: OnStartInfo) => void;
} & ObserverOptions & WebworkerOptions;
type Callback = () => void;
@ -203,7 +209,7 @@ export default class App {
active(): boolean {
return this.isActive;
}
_start(reset: boolean): void { // TODO: return a promise instead of onStart handling
private _start(reset: boolean): Promise<OnStartInfo> {
if (!this.isActive) {
this.isActive = true;
if (!this.worker) {
@ -227,7 +233,7 @@ export default class App {
connAttemptGap: this.options.connAttemptGap,
}
this.worker.postMessage(messageData); // brings delay of 10th ms?
window.fetch(this.options.ingestPoint + '/v1/web/start', {
return window.fetch(this.options.ingestPoint + '/v1/web/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@ -275,28 +281,34 @@ export default class App {
this.ticker.start();
log("OpenReplay tracking started.");
const onStartInfo = { sessionToken: token, userUUID, sessionID };
if (typeof this.options.onStart === 'function') {
this.options.onStart({ sessionToken: token, userUUID, sessionID });
this.options.onStart(onStartInfo);
}
return onStartInfo;
})
.catch(e => {
this.stop();
this.sendDebugReport("session_start", e);
throw e;
})
}
return Promise.reject("Player is active");
}
start(reset: boolean = false): void {
start(reset: boolean = false): Promise<OnStartInfo> {
if (!document.hidden) {
this._start(reset);
return this._start(reset);
} else {
const onVisibilityChange = () => {
if (!document.hidden) {
document.removeEventListener("visibilitychange", onVisibilityChange);
this._start(reset);
return new Promise((resolve) => {
const onVisibilityChange = () => {
if (!document.hidden) {
document.removeEventListener("visibilitychange", onVisibilityChange);
resolve(this._start(reset));
}
}
}
document.addEventListener("visibilitychange", onVisibilityChange);
document.addEventListener("visibilitychange", onVisibilityChange);
});
}
}
stop(): void {

View file

@ -1,6 +1,7 @@
import { isURL } from '../utils';
import App from '../app';
import { ResourceTiming, PageLoadTiming, PageRenderTiming } from '../../messages';
import type Message from '../../messages/message';
// Inspired by https://github.com/WPO-Foundation/RUM-SpeedIndex/blob/master/src/rum-speedindex.js
@ -104,21 +105,28 @@ export default function (app: App, opts: Partial<Options>): void {
if (!('PerformanceObserver' in window)) {
options.captureResourceTimings = false;
}
if (!options.captureResourceTimings) {
options.capturePageLoadTimings = false;
options.capturePageRenderTimings = false;
}
if (!options.captureResourceTimings) { return } // Resources are necessary for all timings
let resources: ResourcesTimeMap | null = options.captureResourceTimings
? {}
: null;
const mQueue: Message[] = []
function sendOnStart(m: Message) {
if (app.active()) {
app.send(m)
} else {
mQueue.push(m)
}
}
app.attachStartCallback(function() {
mQueue.forEach(m => app.send(m))
})
let resources: ResourcesTimeMap | null = {}
function resourceTiming(entry: PerformanceResourceTiming): void {
if (entry.duration <= 0 || !isURL(entry.name) || app.isServiceURL(entry.name)) return;
if (resources !== null) {
resources[entry.name] = entry.startTime + entry.duration;
}
app.send(new
sendOnStart(new
ResourceTiming(
entry.startTime + performance.timing.navigationStart,
entry.duration,
@ -136,20 +144,17 @@ export default function (app: App, opts: Partial<Options>): void {
);
}
const observer: PerformanceObserver | null = options.captureResourceTimings
? new PerformanceObserver((list) =>
list.getEntries().forEach(resourceTiming),
)
: null;
if (observer !== null) {
performance.getEntriesByType('resource').forEach(resourceTiming);
observer.observe({ entryTypes: ['resource'] });
}
const observer: PerformanceObserver = new PerformanceObserver(
(list) => list.getEntries().forEach(resourceTiming),
)
performance.getEntriesByType('resource').forEach(resourceTiming)
observer.observe({ entryTypes: ['resource'] })
let firstPaint = 0,
firstContentfulPaint = 0;
if (options.capturePageLoadTimings && observer !== null) {
if (options.capturePageLoadTimings) {
let pageLoadTimingSent: boolean = false;
app.ticker.attach(() => {
@ -200,7 +205,7 @@ export default function (app: App, opts: Partial<Options>): void {
}, 30);
}
if (options.capturePageRenderTimings && observer !== null) {
if (options.capturePageRenderTimings) {
let visuallyComplete = 0,
interactiveWindowStartTime = 0,
interactiveWindowTickTime: number | null = 0,

View file

@ -24,7 +24,7 @@ const peerServer = ExpressPeerServer(server, {
debug: true,
path: '/',
proxied: true,
allow_discovery: true
allow_discovery: false
});
peerServer.on('connection', peerConnection);
peerServer.on('disconnect', peerDisconnect);