wasm.websocket.objects.pas 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. {
  2. This file is part of the Free Component Library
  3. Webassembly Websocket - Simple objects around the low-level API
  4. Copyright (c) 2024 by Michael Van Canneyt [email protected]
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit wasm.websocket.objects;
  12. {$mode ObjFPC}{$H+}
  13. interface
  14. uses
  15. {$IFDEF FPC_DOTTEDUNITS}
  16. System.Classes, System.SysUtils, wasm.websocket.api, wasm.websocket.shared;
  17. {$ELSE}
  18. Classes, SysUtils, wasm.websocket.api, wasm.websocket.shared;
  19. {$ENDIF}
  20. Type
  21. EWasmWebsocket = Class(Exception);
  22. TWasmWebSocketManager = Class;
  23. TWasmWebsocket = Class;
  24. TWasmWebSocketManagerClass = Class of TWasmWebSocketManager;
  25. TWasmWebSocketClass = Class of TWasmWebsocket;
  26. TWasmWebsocketErrorEvent = procedure(Sender : TObject) of object;
  27. TWasmWebsocketMessageEvent = procedure(Sender : TObject; const IsString : Boolean; aPayload : TBytes) of object;
  28. TWasmWebsocketOpenEvent = procedure(Sender : TObject) of object;
  29. TWasmWebsocketCloseEvent = procedure(Sender : TObject; aCode : Integer; const aReason : string; aClean : Boolean) of object;
  30. { TWasmWebsocket }
  31. TWasmWebsocket = class(TComponent)
  32. private
  33. FOnClose: TWasmWebsocketCloseEvent;
  34. FOnError: TWasmWebsocketErrorEvent;
  35. FOnMessage: TWasmWebsocketMessageEvent;
  36. FOnOpen: TWasmWebsocketOpenEvent;
  37. FProtocols: String;
  38. FURL: String;
  39. FWebSocketID: TWasmWebSocketID;
  40. FClosed : Boolean;
  41. procedure DoSendMessage(aBytes: TBytes; aType: longint);
  42. Protected
  43. procedure CheckWebsocketRes(aResult: TWasmWebsocketResult; const aMsg: String; aLogOnly: Boolean=false);
  44. Procedure DoOpen(const aURL : String; const aProtocols : String); virtual;
  45. Procedure DoClose(aCode : Longint; aReason: UTF8String; aRaiseError : Boolean); virtual;
  46. // Called from host
  47. Procedure HandleError; virtual;
  48. procedure HandleOpen; virtual;
  49. procedure HandleMessage(aType : Longint; aMessage : TBytes); virtual;
  50. procedure HandleClose(aCode : Longint; aReason : string; aIsClean : Boolean); virtual;
  51. Public
  52. Constructor create(aOwner : TComponent); override;
  53. Destructor Destroy; override;
  54. Procedure Open(const aURL : String; const aProtocols : String);
  55. Procedure Close(aCode : Longint; aReason: UTF8String);
  56. Procedure SendMessage(aBytes : TBytes);
  57. Procedure SendMessage(const aString : String);
  58. Property WebSocketID : TWasmWebSocketID Read FWebSocketID;
  59. Property OnError : TWasmWebsocketErrorEvent Read FOnError Write FOnError;
  60. Property OnMessage : TWasmWebsocketMessageEvent Read FOnMessage Write FOnMessage;
  61. Property OnClose : TWasmWebsocketCloseEvent Read FOnClose Write FOnClose;
  62. Property OnOpen : TWasmWebsocketOpenEvent Read FOnOpen Write FOnOpen;
  63. Property URL : String Read FURL;
  64. Property Protocols : String Read FProtocols;
  65. end;
  66. { TWasmWebSocketManager }
  67. TWasmWebSocketManager = class(TObject)
  68. private
  69. class var _Instance : TWasmWebSocketManager;
  70. class function GetInstance: TWasmWebSocketManager; static;
  71. private
  72. Flist : TFPList; // Todo: change to thread list.
  73. protected
  74. class procedure HandleClose(aWebSocketID: TWasmWebSocketID; aUserData: Pointer; aCode: Longint; const aReason: String; aClean: Boolean); static;
  75. class procedure HandleError(aWebSocketID: TWasmWebSocketID; aUserData: Pointer); static;
  76. class procedure HandleMessage(aWebSocketID: TWasmWebSocketID; aUserData: Pointer; aMessageType: TWasmWebSocketMessageType; aMessage: TBytes); static;
  77. class procedure HandleOpen(aWebSocketID: TWasmWebSocketID; aUserData: Pointer); static;
  78. procedure RegisterWebSocket(aWebSocket : TWasmWebSocket);
  79. procedure UnRegisterWebSocket(aWebSocket : TWasmWebSocket);
  80. function IsValidWebSocket(aWebSocketID: TWasmWebSocketID; aUserData: Pointer) : Boolean;
  81. Public
  82. class constructor init;
  83. constructor create; virtual;
  84. destructor destroy; override;
  85. class var DefaultInstanceType : TWasmWebSocketManagerClass;
  86. Class Property Instance : TWasmWebSocketManager Read GetInstance;
  87. end;
  88. implementation
  89. { TWasmWebsocket }
  90. constructor TWasmWebsocket.create(aOwner : TComponent);
  91. begin
  92. Inherited;
  93. TWasmWebSocketManager.Instance.RegisterWebSocket(Self);
  94. FClosed:=False
  95. end;
  96. procedure TWasmWebsocket.DoClose(aCode: Longint; aReason: UTF8String; aRaiseError: Boolean);
  97. var
  98. Res : TWasmWebsocketResult;
  99. begin
  100. if FWebSocketID=0 then
  101. exit;
  102. Res:=__wasm_websocket_close(FWebSocketID,aCode,PByte(PAnsiChar(aReason)),Length(aReason));
  103. CheckWebsocketRes(Res,'close',not aRaiseError);
  104. end;
  105. procedure TWasmWebsocket.HandleError;
  106. begin
  107. if assigned(FonError) then
  108. FOnError(Self);
  109. end;
  110. procedure TWasmWebsocket.HandleOpen;
  111. begin
  112. if assigned(FonOpen) then
  113. FOnOpen(Self);
  114. end;
  115. procedure TWasmWebsocket.HandleMessage(aType: Longint; aMessage: TBytes);
  116. begin
  117. if assigned(FOnMessage) then
  118. FOnMessage(Self,aType=WASMWS_MESSAGE_TYPE_TEXT,aMessage);
  119. end;
  120. procedure TWasmWebsocket.HandleClose(aCode: Longint; aReason: string; aIsClean: Boolean);
  121. begin
  122. FClosed:=True;
  123. if assigned(FonClose) then
  124. FOnClose(Self,aCode,aReason,aIsClean);
  125. end;
  126. procedure TWasmWebsocket.DoOpen(const aURL: String; const aProtocols: String);
  127. var
  128. lURL,lProtocols : UTF8String;
  129. begin
  130. FURL:=aURL;
  131. FProtocols:=aProtocols;
  132. lURL:=UTF8Encode(aURL);
  133. lProtocols:=UTF8Encode(aProtocols);
  134. if __wasm_websocket_allocate(PByte(lURL),Length(lURL),PByte(lProtocols),Length(lProtocols),Self,@FWebSocketID)<>WASMWS_RESULT_SUCCESS then
  135. Raise EWasmWebsocket.CreateFmt('Failed to allocate websocket for URL %s',[aURL]);
  136. end;
  137. destructor TWasmWebsocket.Destroy;
  138. var
  139. Res : TWasmWebsocketResult;
  140. begin
  141. if not FClosed then
  142. DoClose(0,'',False);
  143. res:=__wasm_websocket_deallocate(FWebSocketID);
  144. CheckWebsocketRes(Res,'Deallocating websocket',True);
  145. FWebSocketID:=0;
  146. TWasmWebSocketManager.Instance.UnRegisterWebSocket(Self);
  147. inherited Destroy;
  148. end;
  149. procedure TWasmWebsocket.Open(const aURL: String; const aProtocols: String);
  150. begin
  151. DoOpen(aURL,aProtocols);
  152. end;
  153. procedure TWasmWebsocket.Close(aCode: Longint; aReason: UTF8String);
  154. begin
  155. DoClose(aCode,aReason,True);
  156. FClosed:=True;
  157. end;
  158. procedure TWasmWebsocket.CheckWebsocketRes(aResult : TWasmWebsocketResult; const aMsg :String; aLogOnly : Boolean = false);
  159. var
  160. Err : String;
  161. begin
  162. if aResult=WASMWS_RESULT_SUCCESS then
  163. Exit;
  164. Err:=Format('Websocket %d (URL: %s) got error %d: %s',[FWebSocketID,FURL,aResult,aMsg]);
  165. __wasmwebsocket_log(wllError,Err);
  166. if not aLogOnly then
  167. Raise EWasmWebsocket.Create(Err);
  168. end;
  169. procedure TWasmWebsocket.DoSendMessage(aBytes: TBytes; aType : longint);
  170. const
  171. aTypes : Array[Boolean] of string = ('binary','text');
  172. var
  173. Res : TWasmWebsocketResult;
  174. DataLen : Longint;
  175. begin
  176. DataLen:=Length(aBytes);
  177. if DataLen=0 then
  178. exit;
  179. Res:=__wasm_websocket_send(FWebsocketID,PByte(aBytes),DataLen,aType);
  180. CheckWebsocketRes(Res,'Failed to send '+aTypes[aType=WASMWS_MESSAGE_TYPE_TEXT]+' data on websocket');
  181. end;
  182. procedure TWasmWebsocket.SendMessage(aBytes: TBytes);
  183. begin
  184. DoSendMessage(aBytes,WASMWS_MESSAGE_TYPE_BINARY);
  185. end;
  186. procedure TWasmWebsocket.SendMessage(const aString: String);
  187. var
  188. Res : TWasmWebsocketResult;
  189. Buf : TBytes;
  190. begin
  191. if Length(aString)=0 then
  192. exit;
  193. {$IF SIZEOF(CHAR)=1}
  194. Buf:=TEncoding.UTF8.GetAnsiBytes(aString);
  195. {$ELSE}
  196. Buf:=TEncoding.UTF8.GetBytes(aString);
  197. {$ENDIF}
  198. DoSendMessage(Buf,WASMWS_MESSAGE_TYPE_TEXT);
  199. end;
  200. { TWasmWebSocketManager }
  201. class function TWasmWebSocketManager.GetInstance: TWasmWebSocketManager; static;
  202. var
  203. C : TWasmWebSocketManagerClass;
  204. begin
  205. if _instance=nil then
  206. begin
  207. C:=DefaultInstanceType;
  208. if C=Nil then C:=TWasmWebSocketManager;
  209. _instance:=TWasmWebSocketManager.Create;
  210. end;
  211. end;
  212. procedure TWasmWebSocketManager.RegisterWebSocket(aWebSocket: TWasmWebSocket);
  213. begin
  214. Writeln(Format('adding websocket [%p]',[Pointer(aWebSocket)]));
  215. Flist.Add(aWebSocket);
  216. end;
  217. procedure TWasmWebSocketManager.UnRegisterWebSocket(aWebSocket: TWasmWebSocket);
  218. begin
  219. Flist.Remove(aWebSocket);
  220. end;
  221. function TWasmWebSocketManager.IsValidWebSocket(aWebSocketID: TWasmWebSocketID; aUserData: Pointer): Boolean;
  222. begin
  223. Result:=FList.IndexOf(aUserData)<>-1;
  224. If Result then
  225. Result:=TWasmWebSocket(aUserData).WebSocketID=aWebSocketID;
  226. if not Result then
  227. __wasmwebsocket_log(wllError,'Invalid websocket received: %d [%p]',[aWebsocketID,aUserData]);
  228. end;
  229. class procedure TWasmWebSocketManager.HandleError(aWebSocketID : TWasmWebSocketID; aUserData : Pointer);
  230. begin
  231. If Instance.IsValidWebSocket(aWebSocketID,aUserData) then
  232. TWasmWebSocket(aUserData).HandleError;
  233. end;
  234. class procedure TWasmWebSocketManager.HandleMessage(aWebSocketID : TWasmWebSocketID; aUserData : Pointer; aMessageType : TWasmWebSocketMessageType; aMessage : TBytes);
  235. begin
  236. If Instance.IsValidWebSocket(aWebSocketID,aUserData) then
  237. TWasmWebSocket(aUserData).HandleMessage(aMessageType,aMessage);
  238. end;
  239. class procedure TWasmWebSocketManager.HandleClose(aWebSocketID : TWasmWebSocketID; aUserData : Pointer; aCode: Longint; const aReason : String; aClean : Boolean);
  240. begin
  241. If Instance.IsValidWebSocket(aWebSocketID,aUserData) then
  242. TWasmWebSocket(aUserData).HandleClose(aCode,aReason,aClean)
  243. end;
  244. class procedure TWasmWebSocketManager.HandleOpen(aWebSocketID : TWasmWebSocketID; aUserData : Pointer);
  245. begin
  246. If Instance.IsValidWebSocket(aWebSocketID,aUserData) then
  247. TWasmWebSocket(aUserData).HandleOpen;
  248. end;
  249. class constructor TWasmWebSocketManager.init;
  250. begin
  251. WebSocketErrorCallback:=@HandleError;
  252. WebSocketMessageCallback:=@HandleMessage;
  253. WebSocketCloseCallback:=@HandleClose;
  254. WebSocketOpenCallback:=@HandleOpen;
  255. end;
  256. constructor TWasmWebSocketManager.create;
  257. begin
  258. Flist:=TFPList.Create;
  259. end;
  260. destructor TWasmWebSocketManager.destroy;
  261. begin
  262. FreeAndNil(Flist);
  263. inherited destroy;
  264. end;
  265. end.