wasm.websocket.objects.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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. class var _NextID : TWasmWebSocketID;
  34. class function GetNextWebsocketID : TWasmWebSocketID;
  35. private
  36. FKeepDataAlive: Boolean;
  37. FOnClose: TWasmWebsocketCloseEvent;
  38. FOnError: TWasmWebsocketErrorEvent;
  39. FOnMessage: TWasmWebsocketMessageEvent;
  40. FOnOpen: TWasmWebsocketOpenEvent;
  41. FProtocols: String;
  42. FURL: String;
  43. FWebSocketID: TWasmWebSocketID;
  44. FClosed : Boolean;
  45. FData : Array of TBytes;
  46. FDataCount : Integer;
  47. procedure DoSendMessage(aBytes: TBytes; aType: longint);
  48. procedure SetKeepDataAlive(AValue: Boolean);
  49. Protected
  50. procedure CheckWebsocketRes(aResult: TWasmWebsocketResult; const aMsg: String; aLogOnly: Boolean=false);
  51. Procedure DoOpen(const aURL : String; const aProtocols : String); virtual;
  52. Procedure DoClose(aCode : Longint; aReason: UTF8String; aRaiseError : Boolean); virtual;
  53. // Called from host
  54. Procedure HandleError; virtual;
  55. procedure HandleOpen; virtual;
  56. procedure HandleMessage(aType : Longint; aMessage : TBytes); virtual;
  57. procedure HandleClose(aCode : Longint; aReason : string; aIsClean : Boolean); virtual;
  58. // Data management
  59. procedure ReleaseAllData;
  60. Procedure KeepData(const aData : TBytes);
  61. Public
  62. Constructor create(aOwner : TComponent); override;
  63. Destructor Destroy; override;
  64. Procedure Open(const aURL : String; const aProtocols : String);
  65. Procedure Close(aCode : Longint; aReason: UTF8String);
  66. Procedure SendMessage(aBytes : TBytes);
  67. Procedure SendMessage(const aString : String);
  68. function ReleaseData(const aData : TBytes) : boolean;
  69. Property WebSocketID : TWasmWebSocketID Read FWebSocketID;
  70. Property OnError : TWasmWebsocketErrorEvent Read FOnError Write FOnError;
  71. Property OnMessage : TWasmWebsocketMessageEvent Read FOnMessage Write FOnMessage;
  72. Property OnClose : TWasmWebsocketCloseEvent Read FOnClose Write FOnClose;
  73. Property OnOpen : TWasmWebsocketOpenEvent Read FOnOpen Write FOnOpen;
  74. Property URL : String Read FURL;
  75. Property Protocols : String Read FProtocols;
  76. Property KeepDataAlive : Boolean Read FKeepDataAlive Write SetKeepDataAlive;
  77. end;
  78. { TWasmWebSocketManager }
  79. TWasmWebSocketManager = class(TObject)
  80. private
  81. class var _Instance : TWasmWebSocketManager;
  82. class function GetInstance: TWasmWebSocketManager; static;
  83. private
  84. Flist : TFPList; // Todo: change to thread list.
  85. protected
  86. class procedure HandleReleasePackageCallBack(aWebsocketID: TWasmWebSocketID; aUserData: Pointer; aPacket: Pointer; var Result: boolean); static;
  87. class procedure HandleCloseCallBack(aWebSocketID: TWasmWebSocketID; aUserData: Pointer; aCode: Longint; const aReason: String; aClean: Boolean); static;
  88. class procedure HandleErrorCallBack(aWebSocketID: TWasmWebSocketID; aUserData: Pointer); static;
  89. class procedure HandleMessageCallBack(aWebSocketID: TWasmWebSocketID; aUserData: Pointer; aMessageType: TWasmWebSocketMessageType; aMessage: TBytes); static;
  90. class procedure HandleOpenCallBack(aWebSocketID: TWasmWebSocketID; aUserData: Pointer); static;
  91. function HandleReleasePacket(aSocket : TWasmWebSocket; aPacket : Pointer) : Boolean; virtual;
  92. procedure HandleClose(aSocket : TWasmWebSocket; aCode: Longint; const aReason: String; aClean: Boolean); virtual;
  93. procedure HandleError(aSocket: TWasmWebSocket); virtual;
  94. procedure HandleMessage(aSocket: TWasmWebSocket; aMessageType: TWasmWebSocketMessageType; aMessage: TBytes); virtual;
  95. procedure HandleOpen(aSocket: TWasmWebSocket); virtual;
  96. procedure RegisterWebSocket(aWebSocket : TWasmWebSocket);
  97. procedure UnRegisterWebSocket(aWebSocket : TWasmWebSocket);
  98. function IsValidWebSocket(aWebSocketID: TWasmWebSocketID; aUserData: Pointer) : Boolean;
  99. Public
  100. class constructor init;
  101. constructor create; virtual;
  102. destructor destroy; override;
  103. class var DefaultInstanceType : TWasmWebSocketManagerClass;
  104. Class Property Instance : TWasmWebSocketManager Read GetInstance;
  105. end;
  106. implementation
  107. { TWasmWebsocket }
  108. class procedure TWasmWebSocketManager.HandleReleasePackageCallBack(aWebsocketID: TWasmWebSocketID; aUserData: Pointer; aPacket: Pointer; var Result: boolean);
  109. begin
  110. If Instance.IsValidWebSocket(aWebSocketID,aUserData) then
  111. Result:=Instance.HandleReleasePacket(TWasmWebSocket(aUserData),aPacket);
  112. end;
  113. constructor TWasmWebsocket.create(aOwner : TComponent);
  114. begin
  115. Inherited;
  116. TWasmWebSocketManager.Instance.RegisterWebSocket(Self);
  117. FClosed:=False
  118. end;
  119. procedure TWasmWebsocket.DoClose(aCode: Longint; aReason: UTF8String; aRaiseError: Boolean);
  120. var
  121. Res : TWasmWebsocketResult;
  122. begin
  123. if FWebSocketID=0 then
  124. exit;
  125. Res:=__wasm_websocket_close(FWebSocketID,aCode,PByte(PAnsiChar(aReason)),Length(aReason));
  126. CheckWebsocketRes(Res,'close',not aRaiseError);
  127. end;
  128. procedure TWasmWebsocket.HandleError;
  129. begin
  130. if assigned(FonError) then
  131. FOnError(Self);
  132. end;
  133. procedure TWasmWebsocket.HandleOpen;
  134. begin
  135. if assigned(FonOpen) then
  136. FOnOpen(Self);
  137. end;
  138. procedure TWasmWebsocket.HandleMessage(aType: Longint; aMessage: TBytes);
  139. begin
  140. if assigned(FOnMessage) then
  141. FOnMessage(Self,aType=WASMWS_MESSAGE_TYPE_TEXT,aMessage);
  142. end;
  143. procedure TWasmWebsocket.HandleClose(aCode: Longint; aReason: string; aIsClean: Boolean);
  144. begin
  145. FClosed:=True;
  146. if assigned(FonClose) then
  147. FOnClose(Self,aCode,aReason,aIsClean);
  148. end;
  149. procedure TWasmWebsocket.DoOpen(const aURL: String; const aProtocols: String);
  150. var
  151. lURL,lProtocols : UTF8String;
  152. begin
  153. FURL:=aURL;
  154. FProtocols:=aProtocols;
  155. lURL:=UTF8Encode(aURL);
  156. lProtocols:=UTF8Encode(aProtocols);
  157. if __wasm_websocket_allocate(PByte(lURL),Length(lURL),PByte(lProtocols),Length(lProtocols),Self,FWebSocketID)<>WASMWS_RESULT_SUCCESS then
  158. Raise EWasmWebsocket.CreateFmt('Failed to allocate websocket for URL %s',[aURL]);
  159. end;
  160. destructor TWasmWebsocket.Destroy;
  161. var
  162. Res : TWasmWebsocketResult;
  163. begin
  164. if not FClosed then
  165. DoClose(0,'',False);
  166. res:=__wasm_websocket_deallocate(FWebSocketID);
  167. CheckWebsocketRes(Res,'Deallocating websocket',True);
  168. FWebSocketID:=0;
  169. TWasmWebSocketManager.Instance.UnRegisterWebSocket(Self);
  170. ReleaseAllData;
  171. inherited Destroy;
  172. end;
  173. procedure TWasmWebsocket.Open(const aURL: String; const aProtocols: String);
  174. begin
  175. FWebSocketID:=GetNextWebsocketID;
  176. DoOpen(aURL,aProtocols);
  177. end;
  178. procedure TWasmWebsocket.Close(aCode: Longint; aReason: UTF8String);
  179. begin
  180. DoClose(aCode,aReason,True);
  181. FClosed:=True;
  182. end;
  183. procedure TWasmWebsocket.CheckWebsocketRes(aResult : TWasmWebsocketResult; const aMsg :String; aLogOnly : Boolean = false);
  184. var
  185. Err : String;
  186. begin
  187. if aResult=WASMWS_RESULT_SUCCESS then
  188. Exit;
  189. Err:=Format('Websocket %d (URL: %s) got error %d: %s',[FWebSocketID,FURL,aResult,aMsg]);
  190. __wasmwebsocket_log(wllError,Err);
  191. if not aLogOnly then
  192. Raise EWasmWebsocket.Create(Err);
  193. end;
  194. class function TWasmWebsocket.GetNextWebsocketID: TWasmWebSocketID;
  195. begin
  196. Result:=InterlockedIncrement(_NextID);
  197. end;
  198. procedure TWasmWebsocket.DoSendMessage(aBytes: TBytes; aType : longint);
  199. const
  200. aTypes : Array[Boolean] of string = ('binary','text');
  201. var
  202. Res : TWasmWebsocketResult;
  203. DataLen : Longint;
  204. begin
  205. DataLen:=Length(aBytes);
  206. if DataLen=0 then
  207. exit;
  208. Res:=__wasm_websocket_send(FWebsocketID,PByte(aBytes),DataLen,aType);
  209. if (Res=WASMWS_RESULT_SUCCESS) and KeepDataAlive then
  210. KeepData(aBytes);
  211. CheckWebsocketRes(Res,'Failed to send '+aTypes[aType=WASMWS_MESSAGE_TYPE_TEXT]+' data on websocket');
  212. end;
  213. procedure TWasmWebsocket.SetKeepDataAlive(AValue: Boolean);
  214. begin
  215. if FKeepDataAlive=AValue then Exit;
  216. FKeepDataAlive:=AValue;
  217. if not FKeepDataAlive then
  218. ReleaseAllData;
  219. end;
  220. procedure TWasmWebsocket.ReleaseAllData;
  221. begin
  222. SetLength(FData,0);
  223. end;
  224. procedure TWasmWebsocket.KeepData(const aData: TBytes);
  225. var
  226. lLen : Integer;
  227. begin
  228. lLen:=Length(FData);
  229. if (FDataCount=lLen) then
  230. SetLength(FData,lLen+10);
  231. FData[FDataCount]:=aData;
  232. Inc(FDataCount);
  233. end;
  234. function TWasmWebsocket.ReleaseData(const aData: TBytes): boolean;
  235. var
  236. lIdx : Integer;
  237. begin
  238. Result:=False;
  239. lIdx:=FDataCount-1;
  240. While (lIdx>=0) and (FData[lIdx]<>aData) do
  241. Dec(lIdx);
  242. if (lIdx<0) then
  243. exit;
  244. if lIdx<FDataCount-1 then
  245. FData[lIdx]:=FData[FDataCount-1];
  246. FData[FDataCount-1]:=Nil;
  247. Dec(FDataCount);
  248. Result:=True;
  249. end;
  250. procedure TWasmWebsocket.SendMessage(aBytes: TBytes);
  251. begin
  252. DoSendMessage(aBytes,WASMWS_MESSAGE_TYPE_BINARY);
  253. end;
  254. procedure TWasmWebsocket.SendMessage(const aString: String);
  255. var
  256. Buf : TBytes;
  257. begin
  258. if Length(aString)=0 then
  259. exit;
  260. {$IF SIZEOF(CHAR)=1}
  261. Buf:=TEncoding.UTF8.GetAnsiBytes(aString);
  262. {$ELSE}
  263. Buf:=TEncoding.UTF8.GetBytes(aString);
  264. {$ENDIF}
  265. DoSendMessage(Buf,WASMWS_MESSAGE_TYPE_TEXT);
  266. end;
  267. { TWasmWebSocketManager }
  268. class function TWasmWebSocketManager.GetInstance: TWasmWebSocketManager; static;
  269. var
  270. C : TWasmWebSocketManagerClass;
  271. begin
  272. if _instance=nil then
  273. begin
  274. C:=DefaultInstanceType;
  275. if C=Nil then C:=TWasmWebSocketManager;
  276. _instance:=C.Create;
  277. end;
  278. Result:=_Instance;
  279. end;
  280. procedure TWasmWebSocketManager.RegisterWebSocket(aWebSocket: TWasmWebSocket);
  281. begin
  282. Flist.Add(aWebSocket);
  283. end;
  284. procedure TWasmWebSocketManager.UnRegisterWebSocket(aWebSocket: TWasmWebSocket);
  285. begin
  286. Flist.Remove(aWebSocket);
  287. end;
  288. function TWasmWebSocketManager.IsValidWebSocket(aWebSocketID: TWasmWebSocketID; aUserData: Pointer): Boolean;
  289. begin
  290. Result:=FList.IndexOf(aUserData)<>-1;
  291. If Result then
  292. Result:=TWasmWebSocket(aUserData).WebSocketID=aWebSocketID;
  293. if not Result then
  294. begin
  295. __wasmwebsocket_log(wllError,'Invalid websocket received: %d [%p]',[aWebsocketID,aUserData]);
  296. end;
  297. end;
  298. class procedure TWasmWebSocketManager.HandleErrorCallBack(aWebSocketID: TWasmWebSocketID; aUserData: Pointer);
  299. begin
  300. If Instance.IsValidWebSocket(aWebSocketID,aUserData) then
  301. Instance.HandleError(TWasmWebSocket(aUserData));
  302. end;
  303. class procedure TWasmWebSocketManager.HandleMessageCallBack(aWebSocketID: TWasmWebSocketID; aUserData: Pointer;
  304. aMessageType: TWasmWebSocketMessageType; aMessage: TBytes);
  305. begin
  306. If Instance.IsValidWebSocket(aWebSocketID,aUserData) then
  307. Instance.HandleMessage(TWasmWebSocket(aUserData),aMessageType,aMessage);
  308. end;
  309. class procedure TWasmWebSocketManager.HandleCloseCallBack(aWebSocketID: TWasmWebSocketID; aUserData: Pointer; aCode: Longint;
  310. const aReason: String; aClean: Boolean);
  311. begin
  312. If Instance.IsValidWebSocket(aWebSocketID,aUserData) then
  313. Instance.HandleClose(TWasmWebSocket(aUserData),aCode,aReason,aClean);
  314. end;
  315. class procedure TWasmWebSocketManager.HandleOpenCallBack(aWebSocketID: TWasmWebSocketID; aUserData: Pointer);
  316. begin
  317. If Instance.IsValidWebSocket(aWebSocketID,aUserData) then
  318. Instance.HandleOpen(TWasmWebSocket(aUserData));
  319. end;
  320. function TWasmWebSocketManager.HandleReleasePacket(aSocket: TWasmWebSocket; aPacket: Pointer): Boolean;
  321. begin
  322. aSocket.ReleaseData(TBytes(aPacket));
  323. end;
  324. procedure TWasmWebSocketManager.HandleClose(aSocket: TWasmWebSocket; aCode: Longint; const aReason: String; aClean: Boolean);
  325. begin
  326. aSocket.HandleClose(aCode,aReason,aClean);
  327. end;
  328. procedure TWasmWebSocketManager.HandleError(aSocket: TWasmWebSocket);
  329. begin
  330. aSocket.HandleError;
  331. end;
  332. procedure TWasmWebSocketManager.HandleMessage(aSocket: TWasmWebSocket; aMessageType: TWasmWebSocketMessageType; aMessage: TBytes);
  333. begin
  334. aSocket.HandleMessage(aMessageType,aMessage);
  335. end;
  336. procedure TWasmWebSocketManager.HandleOpen(aSocket: TWasmWebSocket);
  337. begin
  338. aSocket.HandleOpen;
  339. end;
  340. class constructor TWasmWebSocketManager.init;
  341. begin
  342. WebSocketErrorCallback:=@HandleErrorCallBack;
  343. WebSocketMessageCallback:=@HandleMessageCallBack;
  344. WebSocketCloseCallback:=@HandleCloseCallBack;
  345. WebSocketOpenCallback:=@HandleOpenCallBack;
  346. WebSocketReleasePackageCallBack:=@HandleReleasePackageCallBack;
  347. end;
  348. constructor TWasmWebSocketManager.create;
  349. begin
  350. Flist:=TFPList.Create;
  351. end;
  352. destructor TWasmWebSocketManager.destroy;
  353. begin
  354. FreeAndNil(Flist);
  355. inherited destroy;
  356. end;
  357. end.