@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/client",
3
- "version": "0.185.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
- "socket.io-client": "4.7.5"
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,10 @@
1
+ /* @flow */
2
+ import FingerprintJS from "@fingerprintjs/fingerprintjs"
3
+
4
+ const get_fingerprint = async() => {
5
+ const fp = await FingerprintJS.load()
6
+ const fp_result = await fp.get()
7
+ return fp_result.visitorId
8
+ }
9
+
10
+ export default get_fingerprint
@@ -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