Selaa lähdekoodia

* Add websocket example

Michaël Van Canneyt 3 vuotta sitten
vanhempi
commit
1adc178690

+ 35 - 0
demo/websockets/README.md

@@ -0,0 +1,35 @@
+# Websocket demo 
+
+This demo is intended to run with the wsserver demo of the FPC websocket
+support example wsserver. It is a small chat server client.
+
+# Running the example
+
+To run it, run the FPC wsserver example application  (see fcl-web/examples/websocket/server).
+```sh
+wsserver -p 8080
+```
+
+Edit the serverconfig.js and set the correct server URL:
+```json
+var 
+  serverConfig = {
+    "url": "ws://localhost:8080/"
+  };
+```
+(change port etc. to match your setup)
+
+Compile the demowebsocket example using lazarus or pa2js.
+
+run simpleserver (or any other webserver) in this directory:
+
+```sh
+simpleserver -p 3000
+```
+
+and point your browser at port 3000:
+```text
+http://localhost:3000/
+```
+
+

+ 90 - 0
demo/websockets/demowebsocket.lpi

@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+        <Runnable Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="demowebsocket"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <CustomData Count="4">
+      <Item0 Name="MaintainHTML" Value="1"/>
+      <Item1 Name="PasJSHTMLFile" Value="project1.html"/>
+      <Item2 Name="PasJSPort" Value="0"/>
+      <Item3 Name="PasJSWebBrowserProject" Value="1"/>
+    </CustomData>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="demowebsocket.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="index.html"/>
+        <IsPartOfProject Value="True"/>
+        <CustomData Count="1">
+          <Item0 Name="PasJSIsProjectHTMLFile" Value="1"/>
+        </CustomData>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target FileExt=".js">
+      <Filename Value="demowebsocket"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="js"/>
+    </SearchPaths>
+    <Parsing>
+      <SyntaxOptions>
+        <AllowLabel Value="False"/>
+        <CPPInline Value="False"/>
+        <UseAnsiStrings Value="False"/>
+      </SyntaxOptions>
+    </Parsing>
+    <CodeGeneration>
+      <TargetOS Value="browser"/>
+    </CodeGeneration>
+    <Linking>
+      <Debugging>
+        <GenerateDebugInfo Value="False"/>
+        <UseLineInfoUnit Value="False"/>
+      </Debugging>
+    </Linking>
+    <Other>
+      <CustomOptions Value="-Jeutf-8 -Jirtl.js -Jc -Jminclude"/>
+      <CompilerPath Value="$(pas2js)"/>
+    </Other>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 252 - 0
demo/websockets/demowebsocket.lpr

@@ -0,0 +1,252 @@
+program demowebsocket;
+
+{$mode objfpc}
+{$modeswitch externalclass}
+
+uses
+  browserconsole, browserapp, JS, Classes, SysUtils, Web, strutils;
+
+
+
+type
+  TServerConfig = Class External name 'Object' (TJSObject)
+    url : string;
+  end;
+
+Var
+  ServerConfig : TServerConfig; external name 'serverConfig';
+
+
+Type
+  { TMyApplication }
+  TMsgKind = (mkIncoming,mkOutgoing,mkSystem);
+
+  TMyApplication = class(TBrowserApplication)
+    btnSend: TJSHTMLButtonElement;
+    EdtSender : TJSHTMLInputElement;
+    EdtMessage : TJSHTMLInputElement;
+    EdtRecipient : TJSHTMLInputElement;
+    divMessages : TJSHTMLDivElement;
+    btnConnect : TJSHTMLButtonElement;
+    WS : TJSWebSocket;
+    procedure doRun; override;
+  private
+    function AppendIcon(aParent: TJSHTMLElement; aName: String): TJSHTMLElement;
+    function CreateMessageEl(aKind : TMsgKind): TJSHTMLElement;
+    procedure DisplayClose;
+    procedure DisplayOpen;
+    function DoClosed(Event: TEventListenerEvent): boolean;
+    procedure doConnect;
+    function DoIncomingMessage(Event: TEventListenerEvent): boolean;
+    procedure DisplayMessage(Sender, Msg: String; Incoming: Boolean=True);
+    function DoOpen(Event: TEventListenerEvent): boolean;
+    function DoReconnect(aEvent: TJSMouseEvent): boolean;
+    function DoSendClick(aEvent: TJSMouseEvent): boolean;
+    function DoToggleConnectClick(aEvent: TJSMouseEvent): boolean;
+    procedure SendMessage(const aRecipient, aMessage: String);
+  end;
+
+
+
+procedure TMyApplication.doRun;
+
+begin
+  edtRecipient:=TJSHTMLInputElement(GetHTMLElement('edtRecipient'));
+  edtSender:=TJSHTMLInputElement(GetHTMLElement('edtSender'));
+  edtMessage:=TJSHTMLInputElement(GetHTMLElement('edtMessage'));
+  btnConnect:=TJSHTMLButtonElement(GetHTMLElement('btnConnect'));
+  btnConnect.onclick:=@DoToggleConnectClick;
+  btnSend:=TJSHTMLButtonElement(GetHTMLElement('btnSend'));
+  btnSend.onclick:=@DoSendClick;
+  divMessages:=TJSHTMLDivElement(GetHTMLElement('messages'));
+  DoConnect;
+  Terminate;
+end;
+
+procedure TMyApplication.doConnect;
+
+Var
+  URL,aHost : string;
+
+begin
+  URL:='';
+  if Assigned(ServerConfig) and isString(serverConfig.URL) then
+    URL:=serverConfig.URL;
+  if URL='' then
+    begin
+    aHost:=window.location.host;
+    aHost:=ExtractWord(1,aHost,[':']);
+    URL:='ws://'+aHost+':8080/';
+    end;
+  try
+    WS:=TJSWebsocket.New(url);
+    WS.onmessage:=@DoIncomingMessage;
+    WS.onclose:=@DoClosed;
+    WS.onopen:=@DoOpen;
+  except
+    on TJSError do
+      Window.Alert('Could not connect to websocket server at '+URL);
+  end;
+end;
+
+function TMyApplication.DoOpen(Event: TEventListenerEvent): boolean;
+begin
+  btnSend.disabled:=False;
+  DisplayOpen;
+  btnConnect.InnerText:='Disconnect';
+end;
+
+function TMyApplication.DoClosed(Event: TEventListenerEvent): boolean;
+begin
+  btnSend.disabled:=true;
+  btnConnect.InnerText:='Connect';
+  DisplayClose;
+end;
+
+function TMyApplication.DoIncomingMessage(Event: TEventListenerEvent): boolean;
+
+Var
+  Msg: TJSMessageEvent absolute Event;
+  JS : TJSObject;
+
+begin
+   if isString(Msg.Data) then
+     begin
+     JS:=TJSJSON.parseObject(String(Msg.Data));
+     DisplayMessage(String(JS['from']),String(JS['msg']),True)
+     end
+   else
+     DisplayMessage('','<<unknown data arrived>>',True);
+end;
+
+function TMyApplication.CreateMessageEl(aKind : TMsgKind): TJSHTMLElement;
+
+Var
+  ImgDiv,ImgEl,msgEl : TJSHTMLElement;
+
+begin
+  MsgEl:=TJSHTMLElement(Document.createElement('DIV'));
+  if aKind=mkSystem then
+    begin
+    MsgEl.className:='text-center my-2';
+    end
+  else
+    begin
+    msgEl.className:='d-flex align-items-center'+Ifthen(aKind=mkIncoming,'',' text-right justify-content-end');
+    ImgDiv:=TJSHTMLElement(Document.createElement('DIV'));
+    ImgDiv.ClassName:='text-left pr-1';
+    ImgEl:=TJSHTMLElement(Document.createElement('IMG'));
+    ImgEl['Src']:=IfThen(aKind=mkIncoming,'guest','you')+'.png';
+    ImgDiv.AppendChild(ImgEl);
+    if aKind=mkIncoming then
+      msgEl.AppendChild(ImgDiv);
+    end;
+  Result:=TJSHTMLElement(Document.createElement(IfThen(aKind=mkSystem,'span','div')));
+  if aKind=mkSystem then
+    Result.className:='between'
+  else
+    Result.className:='pr-2'+IfThen(aKind=mkIncoming,' pl-1','');
+  msgEl.AppendChild(Result);
+  if aKind=mkOutgoing then
+    msgEl.AppendChild(ImgDiv);
+  divMessages.appendChild(msgEl);
+  msgEl.scrollIntoView;
+end;
+
+function TMyApplication.AppendIcon(aParent: TJSHTMLElement; aName: String
+  ): TJSHTMLElement;
+
+begin
+  Result:=TJSHTMLElement(Document.createElement('i'));
+  Result.className:='fas fa-'+aName+' mr-3';
+  aParent.AppendChild(Result);
+end;
+
+procedure TMyApplication.DisplayClose;
+
+Var
+  iEl,pEl : TJSHTMLElement;
+
+begin
+  Pel:=CreateMessageEl(mkSystem);
+  iEl:=AppendIcon(Pel,'plug');
+  iEl.onclick:=@DoReconnect;
+  pEl.AppendChild(Document.createTextNode('Connection closed, click icon to reconnect'));
+end;
+
+procedure TMyApplication.DisplayOpen;
+
+Var
+  pEl : TJSHTMLElement;
+
+begin
+  Pel:=CreateMessageEl(mkSystem);
+  AppendIcon(Pel,'link');
+  pEl.AppendChild(Document.createTextNode('Connection open, you can start messaging'));
+end;
+
+
+
+procedure TMyApplication.DisplayMessage(Sender,Msg: String; Incoming: Boolean = True);
+
+Const
+   kinds : Array[Boolean] of TMsgKind = (mkOutgoing,mkIncoming);
+
+Var
+  pEl,pEl2 : TJSHTMLElement;
+
+begin
+  pEl:=CreateMessageEl(Kinds[Incoming]);
+  if (Sender<>'') then
+    begin
+    pEl2:=TJSHTMLElement(Document.createElement('span'));
+    pEl2.className:='name';
+    pEl2.innerText:=Sender;
+    pEl.appendChild(pEl2);
+    end;
+  pEl2:=TJSHTMLElement(Document.createElement('p'));
+  PEL2.className:='msg';
+  PEL2.AppendChild(Document.createTextNode(Msg));
+  pEl.AppendChild(pEL2);
+end;
+
+function TMyApplication.DoReconnect(aEvent: TJSMouseEvent): boolean;
+begin
+  DoConnect;
+end;
+
+procedure TMyApplication.SendMessage(const aRecipient,aMessage: String);
+
+Var
+  JS : TJSObject;
+
+begin
+  JS:=New(['from',EdtSender.Value,'msg',aMessage,'to',aRecipient]);
+  WS.send(TJSJSON.Stringify(JS));
+  DisplayMessage('you',aMessage,False);
+end;
+
+function TMyApplication.DoSendClick(aEvent: TJSMouseEvent): boolean;
+begin
+  SendMessage(EDTRecipient.Value, EdtMessage.Value);
+end;
+
+function TMyApplication.DoToggleConnectClick(aEvent: TJSMouseEvent): boolean;
+begin
+  if btnConnect.InnerText='Connect' then
+    doConnect
+  else
+    begin
+    WS.close;
+    WS:=nil;
+    end;
+end;
+
+var
+  Application : TMyApplication;
+
+begin
+  Application:=TMyApplication.Create(nil);
+  Application.Initialize;
+  Application.Run;
+end.

BIN
demo/websockets/guest.png


+ 74 - 0
demo/websockets/index.html

@@ -0,0 +1,74 @@
+<!doctype html>
+<html lang="en">
+<head>
+  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Websockets Message demo</title>
+  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" >
+  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" >
+  <script src="https://code.jquery.com/jquery-3.4.1.js" ></script>
+  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" ></script>
+  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.js"></script>
+  <script src="serverconfig.js"></script>
+  <script src="demowebsocket.js"></script>
+  <link rel="stylesheet" href="style.css">
+</head>
+<body>
+  <div class="container-fluid">
+
+<!-- Your Alias dialog -->
+    <div class="row justify-content-center">
+      <div class="col-12 col-md-6 col-sm-12 col-xs-12">
+          <div class="card p-4 p-md-4 my-4 mx-3 mx-md-0 ">
+            <form onsubmit="event.preventDefault()">
+              <label for="edtSender"> Your Alias </label>
+              <div class="input-group">
+                <input id="edtSender" type="text" class="form-control">
+                <span class="input-group-btn">
+                  <button id="btnConnect" type="submit"  class="btn btn-primary btn-flat">Connect</button>
+                </span>
+              </div>
+            </form>
+          </div>
+      </div>
+    </div>
+    <!-- end of Your Alias dialog -->
+    <div class="row justify-content-center">
+      <div class="col-12 col-md-6 col-sm-12 col-xs-12">
+        <div class="card main">
+          <!-- chat messages -->
+          <div class="px-2 scroll" id="messages">
+          <!-- messages will be appended here -->
+          </div>
+
+          <!-- end of chat messages -->
+
+          <!-- To and Message Inputs -->
+          <nav class="navbar navbar-expand-sm p-0">
+            <!-- To Input -->
+            <div class="input-group" style="width: 40%;">
+              <div class="input-group-prepend">
+                <span class="input-group-text">To:</span>
+              </div>
+              <input id="edtRecipient" type="text" class="form-control">
+            </div>
+            <!-- Message Input and Send button -->
+            <div class="input-group">
+              <input id="edtMessage" type="text" name="message" placeholder="Type Message ..." class="form-control">
+              <span class="input-group-btn">
+                <button id="btnSend" type="button" class="btn btn-primary btn-flat"><i class="fas fa-paper-plane pr-2"></i>Send</button>
+              </span>
+            </div>
+          </nav>
+       </div>
+      </div>
+    </div>
+    <!-- end of chat dialog -->
+  </div>
+
+  <script>
+  rtl.run();
+  </script>
+  <div id="pasjsconsole"></div>
+</body>
+</html>

+ 5 - 0
demo/websockets/serverconfig.js

@@ -0,0 +1,5 @@
+var 
+  serverConfig = {
+    "url": "ws://localhost:8080/"
+  };
+  

+ 80 - 0
demo/websockets/style.css

@@ -0,0 +1,80 @@
+@import url('https://fonts.googleapis.com/css2?family=Manrope&display=swap');
+
+html,
+body {
+    height: 100%;
+}
+
+* {
+    padding: 0px;
+    margin: 0px;
+}
+
+body {
+  background-color: #fff;
+  font-family: 'Manrope', sans-serif;
+}
+
+::-webkit-scrollbar {
+  width: 10px;
+}
+
+::-webkit-scrollbar-track {
+  background: #eee;
+}
+
+::-webkit-scrollbar-thumb {
+  background: #888;
+}
+
+::-webkit-scrollbar-thumb:hover {
+  background: #555;
+}
+
+
+.scroll {
+  overflow-y: scroll;
+  scroll-behavior: smooth;
+  /* height: 325px */
+  height: 425px;
+}
+
+.card {
+  background-color: #eee;
+  border-radius: 10px;
+}
+
+.main {
+  position: relative;
+  padding: 6px 0px 0px 0px;
+}
+
+.name {
+  font-size: 14px;
+}
+
+.msg {
+  background-color: #fff;
+  font-size: 16px;
+  padding: 5px 10px;
+  border-radius: 5px;
+  font-weight: 500;
+  color: #3e3c3c;
+}
+
+.between {
+  font-size: 14px;
+  font-weight: 500;
+  color: #a09e9e;
+  padding: 10px 0;
+}
+
+.navbar {
+  border-bottom-left-radius: 8px;
+  border-bottom-right-radius: 8px;
+  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)
+}
+
+.form-control:focus {
+  box-shadow: none;
+}

BIN
demo/websockets/you.png