Merge branch 'dev' into api_insights
This commit is contained in:
commit
747c2ff22a
22 changed files with 154 additions and 3135 deletions
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
15
tracker/tracker-assist/package-lock.json
generated
15
tracker/tracker-assist/package-lock.json
generated
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
2925
tracker/tracker/package-lock.json
generated
2925
tracker/tracker/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue