driftdb-react
Advanced tools
+7
-0
@@ -11,2 +11,8 @@ import React from "react"; | ||
| export declare function useConnectionStatus(): ConnectionStatus; | ||
| export declare function useLatency(): number | null; | ||
| type WrappedPresenceMessage<T> = { | ||
| value: T; | ||
| lastSeen: number; | ||
| }; | ||
| export declare function usePresence<T>(key: string, value: T): Record<string, WrappedPresenceMessage<T>>; | ||
| export declare function StatusIndicator(): JSX.Element; | ||
@@ -16,4 +22,5 @@ interface DriftDBProviderProps { | ||
| api: string; | ||
| room?: string; | ||
| } | ||
| export declare function DriftDBProvider(props: DriftDBProviderProps): JSX.Element; | ||
| export {}; |
+127
-13
@@ -26,3 +26,3 @@ "use strict"; | ||
| Object.defineProperty(exports, "__esModule", { value: true }); | ||
| exports.DriftDBProvider = exports.StatusIndicator = exports.useConnectionStatus = exports.useSharedReducer = exports.useUniqueClientId = exports.useSharedState = exports.RoomQRCode = exports.useDatabase = exports.DatabaseContext = void 0; | ||
| exports.DriftDBProvider = exports.StatusIndicator = exports.usePresence = exports.useLatency = exports.useConnectionStatus = exports.useSharedReducer = exports.useUniqueClientId = exports.useSharedState = exports.RoomQRCode = exports.useDatabase = exports.DatabaseContext = void 0; | ||
| const react_1 = __importStar(require("react")); | ||
@@ -90,3 +90,6 @@ const driftdb_1 = require("driftdb"); | ||
| sendUpdate() { | ||
| this.debounceTimeout = null; | ||
| if (this.debounceTimeout !== null) { | ||
| window.clearTimeout(this.debounceTimeout); | ||
| this.debounceTimeout = null; | ||
| } | ||
| this.db?.send({ | ||
@@ -153,3 +156,2 @@ type: "push", | ||
| const callback = (sequenceValue) => { | ||
| console.log('sv', sequenceValue); | ||
| if (sequenceValue.seq <= lastConfirmedSeq.current) { | ||
@@ -206,4 +208,107 @@ return; | ||
| exports.useConnectionStatus = useConnectionStatus; | ||
| function useLatency() { | ||
| const db = useDatabase(); | ||
| const [latency, setLatency] = (0, react_1.useState)(null); | ||
| react_1.default.useEffect(() => { | ||
| const updateLatency = async () => { | ||
| const result = await db?.testLatency(); | ||
| setLatency(result); | ||
| }; | ||
| const interval = setInterval(updateLatency, 5000); | ||
| updateLatency(); | ||
| return () => { | ||
| clearInterval(interval); | ||
| }; | ||
| }, [db]); | ||
| return latency; | ||
| } | ||
| exports.useLatency = useLatency; | ||
| const MIN_PRESENCE_INTERVAL = 100; | ||
| const MAX_PRESENCE_INTERVAL = 1000; | ||
| class PresenceListener { | ||
| constructor(state, db, key, clientId) { | ||
| this.state = state; | ||
| this.db = db; | ||
| this.key = key; | ||
| this.clientId = clientId; | ||
| // Time of the last update caused by a state change (not regular interval). | ||
| this.lastUpdate = 0; | ||
| this.nextUpdate = Date.now(); | ||
| this.updateHandle = setTimeout(() => { | ||
| this.update(); | ||
| }, 0); | ||
| } | ||
| update() { | ||
| this.db.send({ | ||
| type: "push", | ||
| action: { type: "relay" }, | ||
| value: { value: this.state, client: this.clientId }, | ||
| key: this.key | ||
| }); | ||
| this.nextUpdate = Date.now() + MAX_PRESENCE_INTERVAL; | ||
| this.lastUpdate = Date.now(); | ||
| this.updateHandle = setTimeout(() => { | ||
| this.update(); | ||
| }, MAX_PRESENCE_INTERVAL); | ||
| } | ||
| updateState(value) { | ||
| if (JSON.stringify(value) === JSON.stringify(this.state)) { | ||
| return; | ||
| } | ||
| this.state = value; | ||
| const nextUpdate = this.lastUpdate + MIN_PRESENCE_INTERVAL; | ||
| if (nextUpdate < this.nextUpdate) { | ||
| this.nextUpdate = nextUpdate; | ||
| clearTimeout(this.updateHandle); | ||
| this.updateHandle = setTimeout(() => { | ||
| this.update(); | ||
| }, this.nextUpdate - Date.now()); | ||
| } | ||
| } | ||
| } | ||
| function usePresence(key, value) { | ||
| const db = useDatabase(); | ||
| const clientId = useUniqueClientId(); | ||
| const [presence, setPresence] = (0, react_1.useState)({}); | ||
| const presenceListener = (0, react_1.useRef)(); | ||
| if (presenceListener.current === undefined) { | ||
| presenceListener.current = new PresenceListener(value, db, key, clientId); | ||
| } | ||
| presenceListener.current.updateState(value); | ||
| react_1.default.useEffect(() => { | ||
| const callback = (event) => { | ||
| let message = event.value; | ||
| if (message.client === clientId) { | ||
| // Ignore our own messages. | ||
| return; | ||
| } | ||
| setPresence((presence) => ({ ...presence, [message.client]: { | ||
| value: message.value, | ||
| lastSeen: Date.now() | ||
| } })); | ||
| }; | ||
| const interval = setInterval(() => { | ||
| setPresence((presence) => { | ||
| let newPresence = {}; | ||
| for (let client in presence) { | ||
| if (Date.now() - presence[client].lastSeen < MAX_PRESENCE_INTERVAL * 2) { | ||
| newPresence[client] = presence[client]; | ||
| } | ||
| } | ||
| return newPresence; | ||
| }); | ||
| }, MAX_PRESENCE_INTERVAL); | ||
| db.subscribe(key, callback); | ||
| return () => { | ||
| db.unsubscribe(key, callback); | ||
| clearInterval(interval); | ||
| }; | ||
| }, [key]); | ||
| return presence; | ||
| } | ||
| exports.usePresence = usePresence; | ||
| function StatusIndicator() { | ||
| const status = useConnectionStatus(); | ||
| const latency = useLatency(); | ||
| const latencyStr = latency === null ? "..." : Math.round(latency).toString(); | ||
| let color; | ||
@@ -221,8 +326,11 @@ if (status.connected) { | ||
| " ", | ||
| react_1.default.createElement("span", null, | ||
| react_1.default.createElement("a", { target: "_blank", rel: "noreferrer", style: { textDecoration: 'none', color: '#aaa', fontSize: "70%" }, href: status.debugUrl }, "(ui)"))) : null)); | ||
| react_1.default.createElement("span", { style: { fontSize: '70%', color: '#aaa' } }, | ||
| react_1.default.createElement("a", { target: "_blank", rel: "noreferrer", style: { textDecoration: 'none', color: '#aaa' }, href: status.debugUrl }, "(ui)"), | ||
| "(", | ||
| latencyStr, | ||
| "ms)")) : null)); | ||
| } | ||
| exports.StatusIndicator = StatusIndicator; | ||
| function DriftDBProvider(props) { | ||
| const dbRef = react_1.default.useRef(null); | ||
| const dbRef = (0, react_1.useRef)(null); | ||
| if (dbRef.current === null) { | ||
@@ -233,6 +341,10 @@ dbRef.current = new driftdb_1.DbConnection(); | ||
| let api = new api_1.Api(props.api); | ||
| const searchParams = new URLSearchParams(window.location.search); | ||
| let roomId = (searchParams.get(ROOM_ID_KEY) ?? | ||
| sessionStorage.getItem(ROOM_ID_KEY) ?? | ||
| null); | ||
| let roomId; | ||
| if (props.room) { | ||
| roomId = props.room; | ||
| } | ||
| else { | ||
| const searchParams = new URLSearchParams(window.location.search); | ||
| roomId = searchParams.get(ROOM_ID_KEY); | ||
| } | ||
| let promise; | ||
@@ -246,5 +358,7 @@ if (roomId) { | ||
| promise.then((result) => { | ||
| let url = new URL(window.location.href); | ||
| url.searchParams.set(ROOM_ID_KEY, result.room); | ||
| window.history.replaceState({}, "", url.toString()); | ||
| if (!props.room) { | ||
| let url = new URL(window.location.href); | ||
| url.searchParams.set(ROOM_ID_KEY, result.room); | ||
| window.history.replaceState({}, "", url.toString()); | ||
| } | ||
| dbRef.current?.connect(result.socket_url); | ||
@@ -251,0 +365,0 @@ }); |
+2
-2
| { | ||
| "name": "driftdb-react", | ||
| "version": "1.0.7", | ||
| "version": "1.0.8", | ||
| "description": "", | ||
@@ -16,3 +16,3 @@ "main": "dist/index.js", | ||
| "dependencies": { | ||
| "driftdb": "1.0.7" | ||
| "driftdb": "1.0.8" | ||
| }, | ||
@@ -19,0 +19,0 @@ "devDependencies": { |
15196
37.28%390
45.52%+ Added
- Removed
Updated