diff --git a/frontend/taipy-gui/base/src/app.ts b/frontend/taipy-gui/base/src/app.ts index 9a9447960b..ba2409f91e 100644 --- a/frontend/taipy-gui/base/src/app.ts +++ b/frontend/taipy-gui/base/src/app.ts @@ -6,11 +6,13 @@ import { Socket, io } from "socket.io-client"; import { DataManager, ModuleData } from "./dataManager"; import { initSocket } from "./socket"; import { TaipyWsAdapter, WsAdapter } from "./wsAdapter"; +import { WsMessageType } from "../../src/context/wsUtils"; export type OnInitHandler = (taipyApp: TaipyApp) => void; export type OnChangeHandler = (taipyApp: TaipyApp, encodedName: string, value: unknown) => void; export type OnNotifyHandler = (taipyApp: TaipyApp, type: string, message: string) => void; export type OnReloadHandler = (taipyApp: TaipyApp, removedChanges: ModuleData) => void; +export type OnWsStatusUpdate = (taipyApp: TaipyApp, messageQueue: string[]) => void; type Route = [string, string]; export class TaipyApp { @@ -19,6 +21,8 @@ export class TaipyApp { _onChange: OnChangeHandler | undefined; _onNotify: OnNotifyHandler | undefined; _onReload: OnReloadHandler | undefined; + _onWsStatusUpdate: OnWsStatusUpdate | undefined; + _ackList: string[]; variableData: DataManager | undefined; functionData: DataManager | undefined; appId: string; @@ -33,7 +37,7 @@ export class TaipyApp { onInit: OnInitHandler | undefined = undefined, onChange: OnChangeHandler | undefined = undefined, path: string | undefined = undefined, - socket: Socket | undefined = undefined, + socket: Socket | undefined = undefined ) { socket = socket || io("/", { autoConnect: false }); this.onInit = onInit; @@ -48,6 +52,7 @@ export class TaipyApp { this.path = path; this.socket = socket; this.wsAdapters = [new TaipyWsAdapter()]; + this._ackList = []; // Init socket io connection initSocket(socket, this); } @@ -91,11 +96,21 @@ export class TaipyApp { } set onReload(handler: OnReloadHandler | undefined) { if (handler !== undefined && handler?.length !== 2) { - throw new Error("_onReload() requires two parameters"); + throw new Error("onReload() requires two parameters"); } this._onReload = handler; } + get onWsStatusUpdate() { + return this._onWsStatusUpdate; + } + set onWsStatusUpdate(handler: OnWsStatusUpdate | undefined) { + if (handler !== undefined && handler?.length !== 2) { + throw new Error("onWsStatusUpdate() requires two parameters"); + } + this._onWsStatusUpdate = handler; + } + // Utility methods init() { this.clientId = ""; @@ -103,15 +118,26 @@ export class TaipyApp { this.appId = ""; this.routes = undefined; const id = getLocalStorageValue(TAIPY_CLIENT_ID, ""); - sendWsMessage(this.socket, "ID", TAIPY_CLIENT_ID, id, id, undefined, false); - sendWsMessage(this.socket, "AID", "connect", "", id, undefined, false); - sendWsMessage(this.socket, "GR", "", "", id, undefined, false); + this.sendWsMessage("ID", TAIPY_CLIENT_ID, id); + this.sendWsMessage("AID", "connect", ""); + this.sendWsMessage("GR", "", ""); if (id !== "") { this.clientId = id; this.updateContext(this.path); } } + sendWsMessage(type: WsMessageType, id: string, payload: unknown, context: string | undefined = undefined) { + if (context === undefined) { + context = this.context; + } + const ackId = sendWsMessage(this.socket, type, id, payload, this.clientId, context); + if (ackId) { + this._ackList.push(ackId); + this.onWsStatusUpdate && this.onWsStatusUpdate(this, this._ackList); + } + } + // Public methods registerWsAdapter(wsAdapter: WsAdapter) { this.wsAdapters.unshift(wsAdapter); @@ -153,7 +179,7 @@ export class TaipyApp { // This update will only send the request to Taipy Gui backend // the actual update will be handled when the backend responds update(encodedName: string, value: unknown) { - sendWsMessage(this.socket, "U", encodedName, { value: value }, this.clientId, this.context); + this.sendWsMessage("U", encodedName, { value: value }); } getContext() { @@ -164,12 +190,12 @@ export class TaipyApp { if (!path || path === "") { path = window.location.pathname.slice(1); } - sendWsMessage(this.socket, "GMC", "get_module_context", { path: path || "/" }, this.clientId); + this.sendWsMessage("GMC", "get_module_context", { path: path || "/" }); } trigger(actionName: string, triggerId: string, payload: Record = {}) { payload["action"] = actionName; - sendWsMessage(this.socket, "A", triggerId, payload, this.clientId, this.context); + this.sendWsMessage("A", triggerId, payload); } upload(encodedName: string, files: FileList, progressCallback: (val: number) => void) { @@ -179,6 +205,10 @@ export class TaipyApp { getPageMetadata() { return this.metadata; } + + getWsStatus() { + return this._ackList; + } } export const createApp = (onInit?: OnInitHandler, onChange?: OnChangeHandler, path?: string, socket?: Socket) => { diff --git a/frontend/taipy-gui/base/src/exports.ts b/frontend/taipy-gui/base/src/exports.ts index 3343eb83ae..9f702a4390 100644 --- a/frontend/taipy-gui/base/src/exports.ts +++ b/frontend/taipy-gui/base/src/exports.ts @@ -1,9 +1,7 @@ import { WsAdapter } from "./wsAdapter"; -import { sendWsMessage } from "../../src/context/wsUtils"; // import { TaipyApp } from "./app"; export { WsAdapter, - sendWsMessage, // TaipyApp, }; diff --git a/frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts b/frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts index 4568e91566..e6c9a0c509 100644 --- a/frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts +++ b/frontend/taipy-gui/base/src/packaging/taipy-gui-base.d.ts @@ -20,10 +20,29 @@ declare class DataManager { getAllData(): Record; update(encodedName: string, value: unknown): void; } +export type WsMessageType = + | "A" + | "U" + | "DU" + | "MU" + | "RU" + | "AL" + | "BL" + | "NA" + | "ID" + | "MS" + | "DF" + | "PR" + | "ACK" + | "GMC" + | "GDT" + | "AID" + | "GR"; export type OnInitHandler = (taipyApp: TaipyApp) => void; export type OnChangeHandler = (taipyApp: TaipyApp, encodedName: string, value: unknown) => void; export type OnNotifyHandler = (taipyApp: TaipyApp, type: string, message: string) => void; export type OnReloadHandler = (taipyApp: TaipyApp, removedChanges: ModuleData) => void; +export type OnWsStatusUpdate = (taipyApp: TaipyApp, messageQueue: string[]) => void; export type Route = [string, string]; export declare class TaipyApp { socket: Socket; @@ -31,11 +50,14 @@ export declare class TaipyApp { _onChange: OnChangeHandler | undefined; _onNotify: OnNotifyHandler | undefined; _onReload: OnReloadHandler | undefined; + _onWsStatusUpdate: OnWsStatusUpdate | undefined; + _ackList: string[]; variableData: DataManager | undefined; functionData: DataManager | undefined; appId: string; clientId: string; context: string; + metadata: Record; path: string | undefined; routes: Route[] | undefined; wsAdapters: WsAdapter[]; @@ -53,7 +75,10 @@ export declare class TaipyApp { set onNotify(handler: OnNotifyHandler | undefined); get onReload(): OnReloadHandler | undefined; set onReload(handler: OnReloadHandler | undefined); + get onWsStatusUpdate(): OnWsStatusUpdate | undefined; + set onWsStatusUpdate(handler: OnWsStatusUpdate | undefined); init(): void; + sendWsMessage(type: WsMessageType | string, id: string, payload: unknown, context?: string | undefined): void; registerWsAdapter(wsAdapter: WsAdapter): void; getEncodedName(varName: string, module: string): string | undefined; getName(encodedName: string): [string, string] | undefined; @@ -68,28 +93,11 @@ export declare class TaipyApp { updateContext(path?: string | undefined): void; trigger(actionName: string, triggerId: string, payload?: Record): void; upload(encodedName: string, files: FileList, progressCallback: (val: number) => void): Promise; - getPageMetadata(): any; + getPageMetadata(): Record; + getWsStatus(): string[]; } -export type WsMessageType = - | "A" - | "U" - | "DU" - | "MU" - | "RU" - | "AL" - | "BL" - | "NA" - | "ID" - | "MS" - | "DF" - | "PR" - | "ACK" - | "GMC" - | "GDT" - | "AID" - | "GR"; export interface WsMessage { - type: WsMessageType | str; + type: WsMessageType | string; name: string; payload: Record | unknown; propagate: boolean; @@ -97,16 +105,6 @@ export interface WsMessage { module_context: string; ack_id?: string; } -export declare const sendWsMessage: ( - socket: Socket | undefined, - type: WsMessageType | str, - name: string, - payload: Record | unknown, - id: string, - moduleContext?: string, - propagate?: boolean, - serverAck?: (val: unknown) => void -) => string; export declare abstract class WsAdapter { abstract supportedMessageTypes: string[]; abstract handleWsMessage(message: WsMessage, app: TaipyApp): boolean; diff --git a/frontend/taipy-gui/base/src/socket.ts b/frontend/taipy-gui/base/src/socket.ts index 194f73e772..f6aff46f4e 100644 --- a/frontend/taipy-gui/base/src/socket.ts +++ b/frontend/taipy-gui/base/src/socket.ts @@ -1,5 +1,5 @@ import { Socket } from "socket.io-client"; -import { WsMessage, sendWsMessage } from "../../src/context/wsUtils"; +import { WsMessage } from "../../src/context/wsUtils"; import { TaipyApp } from "./app"; export const initSocket = (socket: Socket, taipyApp: TaipyApp) => { @@ -11,7 +11,7 @@ export const initSocket = (socket: Socket, taipyApp: TaipyApp) => { // Send a request to get App ID to verify that the app has not been reloaded socket.io.on("reconnect", () => { console.log("WebSocket reconnected"); - sendWsMessage(socket, "AID", "reconnect", taipyApp.appId, taipyApp.clientId, taipyApp.context); + taipyApp.sendWsMessage("AID", "reconnect", taipyApp.appId); }); // try to reconnect on connect_error socket.on("connect_error", (err) => { diff --git a/frontend/taipy-gui/base/src/wsAdapter.ts b/frontend/taipy-gui/base/src/wsAdapter.ts index 09b446c525..3ecf3a44c1 100644 --- a/frontend/taipy-gui/base/src/wsAdapter.ts +++ b/frontend/taipy-gui/base/src/wsAdapter.ts @@ -1,7 +1,7 @@ import merge from "lodash/merge"; import { TaipyApp } from "./app"; import { IdMessage, storeClientId } from "../../src/context/utils"; -import { WsMessage, sendWsMessage } from "../../src/context/wsUtils"; +import { WsMessage } from "../../src/context/wsUtils"; import { DataManager, ModuleData } from "./dataManager"; export abstract class WsAdapter { @@ -25,7 +25,7 @@ export class TaipyWsAdapter extends WsAdapter { initWsMessageTypes: string[]; constructor() { super(); - this.supportedMessageTypes = ["MU", "ID", "GMC", "GDT", "AID", "GR", "AL"]; + this.supportedMessageTypes = ["MU", "ID", "GMC", "GDT", "AID", "GR", "AL", "ACK"]; this.initWsMessageTypes = ["ID", "AID", "GMC"]; } handleWsMessage(message: WsMessage, taipyApp: TaipyApp): boolean { @@ -81,6 +81,10 @@ export class TaipyWsAdapter extends WsAdapter { } else if (message.type === "AL" && taipyApp.onNotify) { const payload = message as AlertMessage; taipyApp.onNotify(taipyApp, payload.atype, payload.message); + } else if (message.type === "ACK") { + const {id} = message as unknown as Record; + taipyApp._ackList = taipyApp._ackList.filter((v) => v !== id); + taipyApp.onWsStatusUpdate && taipyApp.onWsStatusUpdate(taipyApp, taipyApp._ackList); } this.postWsMessageProcessing(message, taipyApp); return true; @@ -96,7 +100,7 @@ export class TaipyWsAdapter extends WsAdapter { taipyApp.context !== "" && taipyApp.routes !== undefined ) { - sendWsMessage(taipyApp.socket, "GDT", "get_data_tree", {}, taipyApp.clientId, taipyApp.context); + taipyApp.sendWsMessage("GDT", "get_data_tree", {}); } } } diff --git a/taipy/gui/gui.py b/taipy/gui/gui.py index e54d168cef..fbe80ab8d9 100644 --- a/taipy/gui/gui.py +++ b/taipy/gui/gui.py @@ -1121,7 +1121,8 @@ def __handle_ws_get_module_context(self, payload: t.Any): { "type": _WsType.GET_MODULE_CONTEXT.value, "payload": {"context": mc, "metadata": meta_return}, - } + }, + send_back_only=True, ) def __get_variable_tree(self, data: t.Dict[str, t.Any]): @@ -1165,7 +1166,8 @@ def __handle_ws_get_data_tree(self): "variable": self.__get_variable_tree(data), "function": self.__get_variable_tree(function_data), }, - } + }, + send_back_only=True, ) def __handle_ws_app_id(self, message: t.Any): @@ -1180,7 +1182,8 @@ def __handle_ws_app_id(self, message: t.Any): { "type": _WsType.APP_ID.value, "payload": {"name": name, "id": app_id}, - } + }, + send_back_only=True, ) def __handle_ws_get_routes(self): @@ -1198,7 +1201,8 @@ def __handle_ws_get_routes(self): { "type": _WsType.GET_ROUTES.value, "payload": routes, - } + }, + send_back_only=True, ) def __send_ws(self, payload: dict, allow_grouping=True, send_back_only=False) -> None: