@rpcbase/client 0.185.0 → 0.187.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/rpc.js +32 -0
- package/rr-trace/get_fingerprint.js +10 -0
- package/rr-trace/get_perf_vitals.js +24 -0
- package/rr-trace/get_session_id.js +29 -0
- package/rr-trace/index.js +138 -0
- package/rr-trace/write_session_data.js +86 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpcbase/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.187.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"test": "../../node_modules/.bin/wireit"
|
|
6
6
|
},
|
|
@@ -97,7 +97,9 @@
|
|
|
97
97
|
"pouchdb-core": "8.0.1",
|
|
98
98
|
"pouchdb-find": "8.0.1",
|
|
99
99
|
"react-i18next": "15.0.0",
|
|
100
|
-
"
|
|
100
|
+
"rrweb": "1.1.3",
|
|
101
|
+
"socket.io-client": "4.7.5",
|
|
102
|
+
"web-vitals": "3.1.1"
|
|
101
103
|
},
|
|
102
104
|
"devDependencies": {
|
|
103
105
|
"@babel/core": "7.24.8",
|
package/rpc.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
// import assert from "assert"
|
|
3
|
+
import axios from "axios"
|
|
4
|
+
|
|
5
|
+
import {SERVER_PORT} from "env"
|
|
6
|
+
|
|
7
|
+
export const rpcClientPost = async(rpcName, payload) => {
|
|
8
|
+
const baseUrl = `http://localhost:${SERVER_PORT}`
|
|
9
|
+
const rpcUrl = `/rpc/${rpcName.replace("server/", "")}`
|
|
10
|
+
|
|
11
|
+
const reqUrl = `${baseUrl}${rpcUrl}`
|
|
12
|
+
|
|
13
|
+
let res
|
|
14
|
+
try {
|
|
15
|
+
res = await axios.post(reqUrl, payload, {
|
|
16
|
+
// headers: {"x-native-app": "yes"},
|
|
17
|
+
withCredentials: true,
|
|
18
|
+
headers: {
|
|
19
|
+
"user-agent": "rb-rpc-client/dev"
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.log("RPC got err", err)
|
|
24
|
+
console.log(err.message)
|
|
25
|
+
console.log(err.response)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// console.log("LABASE", baseUrl)
|
|
29
|
+
// console.log("GOT RPC CALL", rpcName, payload)
|
|
30
|
+
// console.log("LARES", res.data)
|
|
31
|
+
return res.data
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import {getTTFB, getFCP} from "web-vitals"
|
|
3
|
+
|
|
4
|
+
let _ttfb = null
|
|
5
|
+
let _fcp = null
|
|
6
|
+
|
|
7
|
+
getTTFB(({value}) => {
|
|
8
|
+
_ttfb = value
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
getFCP(({value}) => {
|
|
12
|
+
_fcp = value
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const get_perf_vitals = async() => {
|
|
16
|
+
return {
|
|
17
|
+
perf_vitals: {
|
|
18
|
+
ttfb: _ttfb,
|
|
19
|
+
fcp: _fcp,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default get_perf_vitals
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import * as uuid from "uuid"
|
|
3
|
+
|
|
4
|
+
const SESSION_STORAGE_KEY = "rb::rr-trace-session-uuid"
|
|
5
|
+
const LOCAL_STORAGE_KEY = "rb::rr-trace-storage-uuid"
|
|
6
|
+
|
|
7
|
+
// runtime: regenrated each time the page realoads
|
|
8
|
+
const runtime_id = uuid.v4()
|
|
9
|
+
// session
|
|
10
|
+
let session_id = sessionStorage.getItem(SESSION_STORAGE_KEY)
|
|
11
|
+
if (!session_id) {
|
|
12
|
+
session_id = uuid.v4()
|
|
13
|
+
sessionStorage.setItem(SESSION_STORAGE_KEY, session_id)
|
|
14
|
+
}
|
|
15
|
+
// storage id: kept as long as the local storage isn't cleared
|
|
16
|
+
let storage_id = localStorage.getItem(LOCAL_STORAGE_KEY)
|
|
17
|
+
if (!storage_id) {
|
|
18
|
+
storage_id = uuid.v4()
|
|
19
|
+
localStorage.setItem(LOCAL_STORAGE_KEY, storage_id)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// TODO: add user_id if is authenticated
|
|
23
|
+
|
|
24
|
+
const get_session_id = () => {
|
|
25
|
+
return {runtime_id, session_id, storage_id}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
export default get_session_id
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import * as rrweb from "rrweb"
|
|
3
|
+
|
|
4
|
+
import * as env from "env"
|
|
5
|
+
|
|
6
|
+
import write_session_data from "./write_session_data"
|
|
7
|
+
import get_session_id from "./get_session_id"
|
|
8
|
+
|
|
9
|
+
const SAVE_INTERVAL = 11000
|
|
10
|
+
// TODO: sessions should not last longer than 10 minutes
|
|
11
|
+
const MAX_SESSION_LENGTH = 30 * 1000 // 10s
|
|
12
|
+
|
|
13
|
+
// when was the session started
|
|
14
|
+
const start_time = Date.now()
|
|
15
|
+
|
|
16
|
+
// rr events store
|
|
17
|
+
const events = []
|
|
18
|
+
|
|
19
|
+
//
|
|
20
|
+
const is_session_over = () => {
|
|
21
|
+
const now = Date.now()
|
|
22
|
+
const delta = now - start_time
|
|
23
|
+
return delta > MAX_SESSION_LENGTH
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// POST payload to rr-trace backend
|
|
27
|
+
const send_payload = async(payload) => {
|
|
28
|
+
if (!env.RR_TRACE_URL) {
|
|
29
|
+
console.log("send_payload: RR_TRACE_URL is not set, returning")
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// TODO: body compression
|
|
34
|
+
const body = JSON.stringify(payload)
|
|
35
|
+
|
|
36
|
+
const url = `${env.RR_TRACE_URL}/api/v1/write_events`
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const fetch_response = await fetch(url, {
|
|
40
|
+
method: "POST",
|
|
41
|
+
cache: "no-cache",
|
|
42
|
+
credentials: "include",
|
|
43
|
+
headers: {
|
|
44
|
+
"Content-Type": "application/json"
|
|
45
|
+
},
|
|
46
|
+
referrerPolicy: "no-referrer",
|
|
47
|
+
body,
|
|
48
|
+
})
|
|
49
|
+
const json = await fetch_response.json()
|
|
50
|
+
if (json.status !== "ok") {
|
|
51
|
+
console.log("status not ok", json)
|
|
52
|
+
}
|
|
53
|
+
} catch(err) {
|
|
54
|
+
console.log(err)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
// while has events, shift array, add to new array
|
|
60
|
+
// send events to backend
|
|
61
|
+
const send_events = async() => {
|
|
62
|
+
const events_payload = []
|
|
63
|
+
|
|
64
|
+
let should_break = false
|
|
65
|
+
// force stop loop after a short delay
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
should_break = true
|
|
68
|
+
}, 100)
|
|
69
|
+
|
|
70
|
+
while (events.length > 0 && !should_break) {
|
|
71
|
+
const ev = events.shift()
|
|
72
|
+
events_payload.push(ev)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (events_payload.length > 0) {
|
|
76
|
+
// console.log("SEND PAYLOAD", JSON.stringify(events_payload, null, 2))
|
|
77
|
+
await send_payload({
|
|
78
|
+
events: events_payload,
|
|
79
|
+
session_id: get_session_id(),
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const init = async() => {
|
|
85
|
+
// rrweb record
|
|
86
|
+
const stop_recording = rrweb.record({
|
|
87
|
+
inlineStylesheet: false,
|
|
88
|
+
emit(event) {
|
|
89
|
+
// if event contains html snapshot, replace the vendor script tag ?
|
|
90
|
+
// if (event.type === 2 && event.data?.node?.childNodes) {
|
|
91
|
+
// console.log("got dom snapshot", event)
|
|
92
|
+
// }
|
|
93
|
+
events.push(event)
|
|
94
|
+
|
|
95
|
+
if (is_session_over()) {
|
|
96
|
+
console.log("session exceeded timeout, stopping recording")
|
|
97
|
+
stop_recording()
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
await write_session_data()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
const delay = (time) => new Promise((resolve) => setTimeout(resolve, time))
|
|
107
|
+
|
|
108
|
+
// send new events to backend every 1500 ms + random delay
|
|
109
|
+
const send_interval = setInterval(async() => {
|
|
110
|
+
const random_time = Math.floor(Math.random() * 500)
|
|
111
|
+
await delay(random_time)
|
|
112
|
+
|
|
113
|
+
send_events()
|
|
114
|
+
|
|
115
|
+
if (is_session_over()) {
|
|
116
|
+
console.log("session timeout, clear interval")
|
|
117
|
+
clearInterval(send_interval)
|
|
118
|
+
}
|
|
119
|
+
}, SAVE_INTERVAL)
|
|
120
|
+
|
|
121
|
+
export default {init}
|
|
122
|
+
|
|
123
|
+
// TODO:
|
|
124
|
+
// send last events before window unload
|
|
125
|
+
// fetch + keepalive with 64kb max payload and 30s timeout
|
|
126
|
+
// window.addEventListener("beforeunload", async(event) => {
|
|
127
|
+
// const res = await hello({hello: "world"})
|
|
128
|
+
// console.log("unload alert res", res)
|
|
129
|
+
// // event.returnValue = "hello wlert"
|
|
130
|
+
// })
|
|
131
|
+
|
|
132
|
+
// window.onunload = function() {
|
|
133
|
+
// fetch("/analytics", {
|
|
134
|
+
// method: 'POST',
|
|
135
|
+
// body: "statistics",
|
|
136
|
+
// keepalive: true
|
|
137
|
+
// });
|
|
138
|
+
// };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* @flow */
|
|
2
|
+
import {RR_TRACE_URL} from "env"
|
|
3
|
+
|
|
4
|
+
import get_fingerprint from "./get_fingerprint"
|
|
5
|
+
import get_perf_vitals from "./get_perf_vitals"
|
|
6
|
+
import get_session_id from "./get_session_id"
|
|
7
|
+
|
|
8
|
+
const get_screen = () => ({
|
|
9
|
+
screen_w: window.screen.width,
|
|
10
|
+
screen_h: window.screen.height,
|
|
11
|
+
window_w: window.innerWidth,
|
|
12
|
+
window_h: window.innerHeight,
|
|
13
|
+
pixel_ratio: window.devicePixelRatio,
|
|
14
|
+
orientation: window.screen.orientation?.type,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const get_analytics_data = async() => {
|
|
18
|
+
const payload = {
|
|
19
|
+
date_now: Date.now()
|
|
20
|
+
}
|
|
21
|
+
// screen info
|
|
22
|
+
Object.assign(payload, get_screen())
|
|
23
|
+
|
|
24
|
+
// nav info
|
|
25
|
+
Object.assign(payload, {
|
|
26
|
+
referrer: window.document.referrer,
|
|
27
|
+
location: window.location.href,
|
|
28
|
+
language: navigator.language,
|
|
29
|
+
user_agent: navigator.userAgent,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Battery (WARNING: battery api is deprecated)
|
|
33
|
+
if (typeof navigator.getBattery === "function") {
|
|
34
|
+
const battery = await navigator.getBattery()
|
|
35
|
+
Object.assign(payload, {
|
|
36
|
+
battery_charging: battery.charging,
|
|
37
|
+
battery_level: battery.level
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// get fingerprint
|
|
42
|
+
// TODO: save fingerprint to localStorage and check if it changed across sessions
|
|
43
|
+
const visitor_id = await get_fingerprint()
|
|
44
|
+
payload.visitor_id = visitor_id
|
|
45
|
+
|
|
46
|
+
// runtime ids
|
|
47
|
+
Object.assign(payload, {...get_session_id()})
|
|
48
|
+
|
|
49
|
+
const perf_vitals = await get_perf_vitals()
|
|
50
|
+
Object.assign(payload, perf_vitals)
|
|
51
|
+
|
|
52
|
+
return payload
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
const write_session_data = async() => {
|
|
57
|
+
if (!RR_TRACE_URL) {
|
|
58
|
+
console.log("RR_TRACE_URL is not set, skipping")
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const payload = await get_analytics_data()
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const url = `${RR_TRACE_URL}/api/v1/write_session_data`
|
|
66
|
+
const fetch_response = await fetch(url, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
cache: "no-cache",
|
|
69
|
+
credentials: "include",
|
|
70
|
+
headers: {
|
|
71
|
+
"Content-Type": "application/json"
|
|
72
|
+
},
|
|
73
|
+
referrerPolicy: "no-referrer",
|
|
74
|
+
body: JSON.stringify(payload), // TODO: compress + encrypt
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
console.log("write_session_data: got fetch response", fetch_response)
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.log("failed to fetch write_session_data", err)
|
|
80
|
+
console.log("Error", JSON.stringify(err))
|
|
81
|
+
console.log(err)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default write_session_data
|