implement pong for backend
This commit is contained in:
3
src/models/Pong.ts
Normal file
3
src/models/Pong.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface PongMessage {
|
||||||
|
type: 'pong';
|
||||||
|
}
|
@@ -11,12 +11,37 @@ import {
|
|||||||
import { ref, reactive } from 'vue';
|
import { ref, reactive } from 'vue';
|
||||||
import type { Subs } from 'src/models/Subscribe';
|
import type { Subs } from 'src/models/Subscribe';
|
||||||
import type { Sets } from 'src/models/Set';
|
import type { Sets } from 'src/models/Set';
|
||||||
|
import type { PongMessage } from 'src/models/Pong';
|
||||||
|
|
||||||
const pendingResponses = new Map<string, (data: Response | undefined) => void>();
|
const pendingResponses = new Map<string, (data: Response | undefined) => void>();
|
||||||
export const lastKnownValues = reactive(new Map<string, string>());
|
const lastKnownValues: Record<string, string> = reactive({});
|
||||||
|
|
||||||
export let socket: WebSocket | null = null;
|
export let socket: WebSocket | null = null;
|
||||||
const isConnected = ref(false);
|
const isConnected = ref(false);
|
||||||
|
let lastPongTime = Date.now();
|
||||||
|
|
||||||
|
function pingLoop(interval: number = 5000) {
|
||||||
|
// Start sending ping every 5 seconds
|
||||||
|
setInterval(() => {
|
||||||
|
if (!socket || socket.readyState !== WebSocket.OPEN) return;
|
||||||
|
|
||||||
|
// If no pong received in last 10 seconds, close
|
||||||
|
if (Date.now() - lastPongTime > interval + 10000) {
|
||||||
|
console.warn('No pong response, closing socket...');
|
||||||
|
socket.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket.send(JSON.stringify({ type: 'ping' }));
|
||||||
|
}, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPong(msg: PongMessage | undefined | null) {
|
||||||
|
if (msg?.type === 'pong') {
|
||||||
|
lastPongTime = Date.now();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
export function initWebSocket(url: string, $q?: QVueGlobals) {
|
export function initWebSocket(url: string, $q?: QVueGlobals) {
|
||||||
const connect = () => {
|
const connect = () => {
|
||||||
@@ -25,6 +50,8 @@ export function initWebSocket(url: string, $q?: QVueGlobals) {
|
|||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
console.log('WebSocket connected');
|
console.log('WebSocket connected');
|
||||||
isConnected.value = true;
|
isConnected.value = true;
|
||||||
|
// Start sending ping every 5 seconds
|
||||||
|
pingLoop(5000);
|
||||||
};
|
};
|
||||||
socket.onclose = () => {
|
socket.onclose = () => {
|
||||||
isConnected.value = false;
|
isConnected.value = false;
|
||||||
@@ -49,43 +76,42 @@ export function initWebSocket(url: string, $q?: QVueGlobals) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
socket.onmessage = (event) => {
|
socket.onmessage = (event) => {
|
||||||
const message = JSON.parse(event.data);
|
if (typeof event.data === 'string') {
|
||||||
const id = message.id;
|
const message = JSON.parse(event.data);
|
||||||
|
|
||||||
if (id && pendingResponses.has(id)) {
|
// Handle pong
|
||||||
pendingResponses.get(id)?.(message); // resolve the promise
|
if (isPong(message)) return;
|
||||||
pendingResponses.delete(id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.publish) {
|
const id = message.id;
|
||||||
let changed = false;
|
if (id && pendingResponses.has(id)) {
|
||||||
|
pendingResponses.get(id)?.(message); // resolve the promise
|
||||||
|
pendingResponses.delete(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message.publish) {
|
||||||
|
(message.publish as Publish[]).forEach((pub) => {
|
||||||
|
const uuid = pub.uuid;
|
||||||
|
const value = pub.value ?? '';
|
||||||
|
|
||||||
(message.publish as Publish[]).forEach((pub) => {
|
if (uuid === undefined) {
|
||||||
const uuid = pub.uuid;
|
return;
|
||||||
const value = pub.value ?? '';
|
|
||||||
|
|
||||||
if (uuid === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldValue = lastKnownValues.get(String(uuid));
|
|
||||||
if (oldValue !== value) {
|
|
||||||
lastKnownValues.set(uuid, value); // this is now reactive
|
|
||||||
|
|
||||||
const existing = getSubscriptionsByUuid(pub.uuid);
|
|
||||||
if (existing) {
|
|
||||||
existing.value = value;
|
|
||||||
} else {
|
|
||||||
getAllSubscriptions()?.push({ value, uuid: uuid });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changed = true;
|
const oldValue = lastKnownValues[uuid];
|
||||||
}
|
if (oldValue !== value) {
|
||||||
});
|
lastKnownValues[uuid] = value; // this is now reactive
|
||||||
|
if (pub.uuid) {
|
||||||
|
const existing = getSubscriptionsByUuid(pub.uuid);
|
||||||
|
|
||||||
if (changed) {
|
if (existing.value) {
|
||||||
dbmData.value = buildTree(getAllSubscriptions()); // rebuild reactive tree
|
existing.value.value = value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getAllSubscriptions().push({ value, uuid: uuid });
|
||||||
|
}
|
||||||
|
dbmData.splice(0, dbmData.length, ...buildTree(getAllSubscriptions())); // rebuild reactive tree
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
50
src/utils/number-helpers.ts
Normal file
50
src/utils/number-helpers.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
|
export function separate16BitUint(value: number): { highByte: number; lowByte: number } {
|
||||||
|
// Ensure the value is treated as a 16-bit unsigned integer
|
||||||
|
// (optional, but good for clarity and safety if 'value' might be outside 0-65535)
|
||||||
|
const normalizedValue = value & 0xffff; // Mask to ensure it's within 16 bits
|
||||||
|
|
||||||
|
// Extract the low byte (least significant 8 bits)
|
||||||
|
// This is simply the value modulo 256, or bitwise AND with 0xFF
|
||||||
|
const lowByte = normalizedValue & 0xff;
|
||||||
|
|
||||||
|
// Extract the high byte (most significant 8 bits)
|
||||||
|
// Right shift by 8 bits to move the high byte into the low byte's position,
|
||||||
|
// then mask with 0xFF to get just those 8 bits.
|
||||||
|
const highByte = (normalizedValue >> 8) & 0xff;
|
||||||
|
|
||||||
|
return { highByte, lowByte };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function combineBytesTo16BitUint(highByte: number, lowByte: number): number {
|
||||||
|
// Ensure both bytes are within the 0-255 range for safety
|
||||||
|
const safeHighByte = highByte & 0xff;
|
||||||
|
const safeLowByte = lowByte & 0xff;
|
||||||
|
|
||||||
|
// Shift the high byte 8 bits to the left to place it in the higher position.
|
||||||
|
// Example: if highByte is 0xA4 (10100100), after shifting it becomes 0xA400 (1010010000000000).
|
||||||
|
const shiftedHighByte = safeHighByte << 8;
|
||||||
|
|
||||||
|
// Combine the shifted high byte with the low byte using a bitwise OR.
|
||||||
|
// Example: if shiftedHighByte is 0xA400 and lowByte is 0x78 (01111000),
|
||||||
|
// the result is 0xA478 (1010010001111000).
|
||||||
|
const combinedValue = shiftedHighByte | safeLowByte;
|
||||||
|
|
||||||
|
// Optional: Mask the result to ensure it's strictly within the 16-bit unsigned range (0 to 65535).
|
||||||
|
// This is good practice as JavaScript numbers are 64-bit floats, and this ensures
|
||||||
|
// the value wraps correctly if intermediate operations somehow exceeded 16 bits.
|
||||||
|
return combinedValue & 0xffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addOne(val: Ref<number>, limit: number) {
|
||||||
|
if (val.value < limit) {
|
||||||
|
val.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function substractOne(val: Ref<number>, limit: number) {
|
||||||
|
if (val.value > limit) {
|
||||||
|
val.value--;
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user