Websockets (#7393)

* Initial commit of websockets

* Move app_menu to the active_calls websockets

* Fix hangup function

* Remove connection wait-state on web socket server so events can process

* Add timestamp and debug level to console for service debug output

* Remove debug exit

* Fix typo for ws_client instead of ws_server

* Update app_config.php

* Fix typo and remove empty function

* Remove call to empty function

* Fix the menu to point to the correct location

* Remove Logging Class

* Rename service file

* Rename service file

* Fix the in progress browser request

* Fix browser reload and implement 'active_calls' default values

* Add apply_filter function

* Create new permission_filter object

* In progress active calls now use filter

* Add invalid_uuid_exception class

* add event_key_filter to honor user permissions

* add and_link and or_link for filters

* Fix disconnected subscriber and add filters to honor permissions

* Add $key and $value for filter

* define a service name

* catch throwable instead of exception

* Add $key and $value for filter and allow returning null

* Update permission checks when loading page

* Add apply_filter function to honor subscriber permissions

* Add create_filter_chain_for function to honor subscriber permissions

* Add apply_filter function to honor subscriber permissions

* Add apply_filter function to honor subscriber permissions

* create interface to allow filterable payload

* create interface to define functions required for websocket services

* Pass in service class when creating a service token

* Allow key/name and return null for filter

* Adjust subscriber exceptions to return the ID of the subscriber

* Add event filter to filter chain

* Add command line options for ip and port for websockets and switch

* update service to use is_a syntax

* initial commit of base class for websockets system services

* initial commit of the system cpu status service

* remove extra line feed

* fix path on active_calls

* initial proof of concept for cpu status updated by websockets

* Allow returning null

* Use default settings to set the interval for cpu status broadcast

* Improve the CPU percent function for Linux systems

* Show more debug information

* Allow child processes to re-connect to the web socket service

* Fix websockets as plural instead of singular

* Add class name list-row

* Update active_calls.php

* Update active_calls.php

* Update websocket_client.js

* Update app_config.php

* Update app_menu.php

* Update debian-websockets.service

* Update debian-active_calls.service

---------

Co-authored-by: FusionPBX <markjcrane@gmail.com>
This commit is contained in:
frytimo
2025-06-24 16:07:57 -03:00
committed by GitHub
parent 86f0561e0c
commit d5286a12bc
44 changed files with 9419 additions and 22 deletions

View File

@@ -0,0 +1,116 @@
class ws_client {
constructor(url, token) {
this.ws = new WebSocket(url);
this.ws.addEventListener('message', this._onMessage.bind(this));
this._nextId = 1;
this._pending = new Map();
this._eventHandlers = new Map();
// The token is submitted on every request
this.token = token;
}
// internal message handler called when event occurs on the socket
_onMessage(ev) {
let message;
let switch_event;
try {
console.log(ev.data);
message = JSON.parse(ev.data);
// check for authentication request
if (message.status_code === 407) {
console.log('Authentication Required');
return;
}
switch_event = message.payload;
//console.log('envelope received: ',env);
} catch (err) {
console.error('Error parsing JSON data:', err);
//console.error('Invalid JSON:', ev.data);
return;
}
// Pull out the request_id first
const rid = message.request_id ?? null;
// If this is the response to a pending request
if (rid && this._pending.has(rid)) {
// Destructure with defaults in case they're missing
const {
service,
topic = '',
status = 'ok',
code = 200,
payload = {}
} = message;
const {resolve, reject} = this._pending.get(rid);
this._pending.delete(rid);
if (status === 'ok' && code >= 200 && code < 300) {
resolve({service, topic, payload, code, message});
} else {
const err = new Error(message || `Error ${code}`);
err.code = code;
reject(err);
}
return;
}
// Otherwise it's a serverpushed event…
// e.g. env.service === 'event' or env.topic is your event name
this._dispatchEvent(message);
}
// Send a request to the websocket server using JSON string
request(service, topic = null, payload = {}) {
const request_id = String(this._nextId++);
const env = {
request_id: request_id,
service,
...(topic !== null ? {topic} : {}),
token: this.token,
payload: payload
};
const raw = JSON.stringify(env);
this.ws.send(raw);
return new Promise((resolve, reject) => {
this._pending.set(request_id, {resolve, reject});
// TODO: get timeout working to reject if no response in X ms
});
}
subscribe(topic) {
return this.request('active.calls', topic);
}
unsubscribe(topic) {
return this.request('active.calls', topic);
}
// register a callback for server-pushes
onEvent(topic, handler) {
console.log('registering event listener for ' + topic);
if (!this._eventHandlers.has(topic)) {
this._eventHandlers.set(topic, []);
}
this._eventHandlers.get(topic).push(handler);
}
/**
* Dispatch a serverpush event envelope to all registered handlers.
* @param {object} env
*/
_dispatchEvent(message) {
const service = message.service_name;
const topic = message.topic;
const handlers = this._eventHandlers.get(topic) || [];
for (const fn of handlers) {
try {
fn(message.payload);
} catch (err) {
console.error(`Error in handler for "${topic}":`, err);
}
}
}
}