Documentation for irc-hybrid-client
API Examples
Network Diagram

Generally, the browser communication is split. Messages from the web server to browser are passed over the websocket as RFC 2812 IRC server messages. In the reverse direction, no messages are passed from the browser to web server over the websocket. All messages from the browser to the web server are passed over a web API using POST and GET requests. More specifically, IRC server messages (such as PRIVMSG) are passed over the /irc/message route as UTF-8 strings using a POST web API request. Information related to the state of the IRC connection and channel membership are retrieved as needed by issuing a GET request to the web API.

The web browser IRC client operates on IRC messages received from the backend NodeJs web server. These are RFC 2812 standard IRC messages. Some non-standard (outside RFC 2812) messages are passed over the websocket for control purposes as described below.

In most cases, the API requests return a web response showing the asynchronous request was either accepted or caused an error. The API response body contains an error boolean flag within a JSON object.

General state errors, such as "IRC Server not connected" are returned within API response body with status 200 and description of the error as a JSON object.

Data validation or parsing errors are returned as status 400 Bad Request.

Network connection errors occurring with the IRC server connection between NodeJs backend and IRC server are returned in the websocket stream prefixed with the string value "webError: ".

Errors occurring over IRC network are returned within the websocket stream as standard IRC RFC 2812 messages containing 3 digit numeric code and descriptive string.

Example (generic) API Responses for successful API request:

{
  "error": false
}

Example (generic) API Responses for a miscellaneous state error:

{
  "error": true,
  "message": "IRC server not connected"
}

Websocket IRC server messages are formed by prefixing a timestamp in front of of verbatim RFC 2812 IRC server message delimited by end-of-line characters. Due to the nature of a text stream buffered I/O, it is possible that the one buffer of stream data could contain multiple IRC messages or that a single IRC message could be split, half arriving in one buffer and half in the next buffer. For reference, RFC 2812 can be viewed at https://datatracker.ietf.org/doc/html/rfc2812.

@time=2021-06-18T21:13:06.949Z :myNickName!*@* PRIVMSG #mychannel :Hi, i'm typing a IRC channel message

Example Websocket Message (generic) for a network connection error being returned asynchronously over websocket stream:

"webError: IRC server timeout while connecting"

Example Websocket Message (generic) for general info messages over the websocket:

"webServer: Opening socket to test-server 192.168.1.127:6667"

A more detail description of web socket usage is included near the bottom of this page.

API Authorization

Cookies

HTTP requests to the web server API require a session cookie with a valid digital signature. API requests made to any protected API without a valid cookie will return status 403 Forbidden.

The user should have previously obtained a cookie by loading the page at "/irc/webclient.html". The web server will check for a valid cookie. In the case of a missing, invalid or expired cookie, the users web browser is redirected from /irc/webclient.html to the proper form for entry of username and password. After successful password entry, the browser is redirected back to /irc/webclient.html with a new valid cookie.

CSRF Tokens

Cross site request forgery is a security concern where html <form> elements are used to submit data using the form's submit button with method POST. A malicious page would attempt to trick a user to clicking an embedded form causing data to be submitted to a different web site where the user previously obtained a valid cookie. A CSRF token is a random nonce that is embedded into the HTML of a web page. It changes each time the page is reloaded to a new random value.

The hrc-hybrid-client web server requires CSRF tokens for HTTP methods that change state, such as the POST methods. The CSRF token is obtained by using JavaScript to parse the token data from the HTML code in /irc/webclient.html as shown in this example.

  <head>
    <meta name="csrf-token" content="ob1P2QF3-bBXabc8At9xxiJyC2I44sAbjgsA">
  </head>

When performing a HTTP request using the POST method, the CSRF token can be added to the request headers as follows:

  "accept": "application/json",
  "csrf-token": "ob1P2QF3-bBXabc8At9xxiJyC2I44sAbjgsA",
  "content-type": "application/json",

Requests with missing or invalid CSRF tokens will return 403 Forbidden "invalid csrf token"

IRC Client API Routes

POST /irc/server

The backend NodeJs web server maintains a list of available IRC servers. This list is loaded from a configuration file "servers.json". Calls to /irc/server shall include an "index" property of type integer that is used to select a specific server.

In the case where the change in selected IRC server causes a change in IRC server group number, or if the requested IRC server number is equal to zero, the IRC message cache on the web server will be cleared. The web server will then send a "CACHERESET" command to all connected web browsers over the websocket connection. The web browser will interpret this command as a change in IRC networks. The browser will clear all relevant textarea elements in order to prevent previously received messages on one IRC network from being displayed as if they were received from a different IRC network. In the case where more than one web browser is concurrently connected to the web server this will force all connected web browsers will remain synchronized with the empty message buffer on the web server.

When the web server configuration changes to index a different IRC server, an "UPDATE" command is sent to the browser over the websocket stream. Upon receipt of the UPDATE request, the browser should perform a GET request to route /irc/getircstate. The getircstate response contains updated server configuration in JSON format. (see getircstate below)

POST Request Body:

{
  "index": -1
}

Success Response:

{
  "error": false,
  "index": 2,
  "name": "DALnet"
}

Error Response:

{
  "error": true,
  "message": "Requested server index number out of range."
}

POST /irc/connect

Calls to the connect API are used to request the backend NodeJs web server to initiate an IRC client connection to the IRC server. The nickName and realName are required properties of type UTF-8 string. The IRC user name (identd alternate user) is not editable from the browser and therefore excluded from this schema. The initial userMode property is optional.

Connection to an IRC server involves several steps, such as opening a TCP socket, sending commands to set nickname and other IRC formation. As the connection is established, the irc state object located in the web server is updated. Each time the state changes, an "UPDATE" command is sent to the browser over the web socket stream. Upon receipt of the UPDATE request, the browser should perform a GET request to route /irc/getircstate and update the user interface from information returned in the state object. (see getircstate below)

POST Request Body

{
  "nickName": "myNickName",
  "realName": "John Doe",
  "userMode": ""
}

Success Response:

{
    "error": false
}

Error Response:

{
    "error": true,
    "message": "Error: already connected to IRC server."
}

POST /irc/message

Calls to this API are used to send RFC 2812 IRC messages from web browser to the NodeJs backend server for re-transmission to the IRC server.

The request body should contain a "message" property of type UTF-8 string containing the RFC 2812 IRC message. Multiple messages are not allowed. The end-of-line characters should not be included.

Assuming the IRC command is accepted by the IRC server, the IRC response is sent to the browser via the websocket stream as a standard RFC 2812 IRC message. These messages are UTF-8 text and delimited by return and end-of-line characters 0x10 and 0x13 ("\r\n"). See RFC 2812 for specification of these messages.

Issuing some commands to the IRC server may result in a state change of the IRC client. One example would be a command to JOIN an IRC channel. Some commands, such as PRIVMSG may not cause a state change. Each time the state changes, an "UPDATE" command is sent to the browser over the web socket stream. Upon receipt of the UPDATE request, the browser should perform a GET request to route /irc/getircstate and update the user interface from information returned in the state object. (see getircstate below)

POST Request Body:

{
  "message": "PRIVMSG #mychannel :Hello World"
}

Success Response:

{
    "error": false
}

Error Response:

{
    "error": true,
    "message": "Can not send server message when IRC server not connected"
}

Websocket Response (IRC server PRIVMSG message in stream)

@time=2021-06-18T20:09:38.944Z :myNickName!*@* PRIVMSG #test :hello, how are you

Example Request causing IRC server to issue an error as IRC message by sending invalid server name with TIME request.

{
  "message": "TIME invalid.server.name"
}

Websocket Error Response (IRC server 402 error message in stream)

@time=2021-06-18T20:05:26.538Z :irc.example.com 402 myNickName invalid.server.name :No such server

GET /irc/getircstate

The actual IRC client is located within the NodeJs backend web server. API calls to route /irc/getircstate will retrieve a JSON object containing all relevant state information for the IRC client connection and IRC channel membership.

The web browser should listen for a websocket stream message containing a string matching "UPDATE". When an UPDATE is received as a single line message delimited by 0x10,0x13, (...\r\nUPDATE\r\n...) the browser javascript should fetch a GET request to the /irc/getircstate route. The browser should then parse the state object for changes and update the user interface as needed.

This example shows the websocket stream during a conversation between two people, while a third new person joins the channel. The IRC server sends a JOIN message which is passed through to the browser. In the backend NodeJs web server, the new user nickname is added to the list of channel members. The UPDATE command is sent to the browser in the websocket stream.

Websocket stream showing JOIN to channel and UPDATE command.

@time=2021-06-18T21:13:06.949Z :myNickName!*@* PRIVMSG #test :Hello, how are you doing
@time=2021-06-18T21:14:16.322Z :otherName!*@* PRIVMSG #test :I am doing fine, how are you
@time=2021-06-18T21:15:26.412Z :myNickName!*@* PRIVMSG #test :I am fine also, I have to run
@time=2021-06-18T21:16:03.212Z :otherName!*@* PRIVMSG #test :OK, bye for now
@time=2021-06-18T21:21:11.393Z :Mary!newuser@1.2.3.4.example.net JOIN :#MyChannel
UPDATE
@time=2021-06-18T21:22:20.433Z :myNickName!*@* PRIVMSG #test :Hello Mary, how are you doing?

The browser should act on the UPDATE command by obtaining a new state object which contains an updated array containing channel member nicknames.

API response to GET /irc/getircstate

{
    "ircConnectOn": true,
    "ircConnecting": false,
    "ircConnected": true,
    "ircRegistered": true,
    "ircIsAway": false,
    "nickRecoveryActive": false,
    "userHost": "~someuser@somehose.example.net",
    "connectHost": "~someuser@somehose.example.net",
    "lastPing": "0.022",
    "ircSockInfo": {
      "encrypted": true,
      "verified": true,
      "protocol": "TLSv1.3"
    }
    "ircServerIndex": 0,
    "ircServerGroup": 0,
    "ircServerName": "freenode",
    "ircServerHost": "chat.freenode.net",
    "ircServerPort": 7000,
    "ircTLSEnabled": true,
    "ircTLSVerify": true,
    "ircProxy": false,
    "ircAutoReconnect": true,
    "ircServerRotation": true,
    "nickName": "myNickName",
    "userName": "myNickName",
    "realName": "myNickName",
    "userMode": "+i",
    "channelList": [],
    "ircServerIndex": 3,
    "ircServerPrefix": "tildes.freenode.net",
    "channels": [
      ""#mychannel"
    ],
    "channelStates": [
      {
        "name": "#mychannel",
        "csName": "#MyChannel"
        "topic": "Welcome to #MyChannel cool topic message",
        "names": [
          "@myNickName",
          "@otherName",
          "Mary"
        ],
        "joined": true,
        "kicked": false
      }
    ],
    "enableSocks5Proxy": true,
    "socks5Host": "socks5.example.com",
    "socks5Port": 10080,
    "progVersion": "0.2.8",
    "progName": "irc-hybrid-client",
    "times": {
        "programRun": "1624047717",
        "ircConnect": "1624047739"
    },
    "count": {
        "ircConnect": 1,
        "ircConnectError": 0,
        "ircStateCalls": 20
    },
    "websocketCount": 1,
    "disableServerListEditor": false,
    "customBeepSounds": false
}

POST /irc/prune

This is used to remove a non-joined IRC channel from the active channel list. Trying to prune a channel while present in the channel will cause an error. Pruning a channel will delete the IRC message cache buffer associated with the pruned channel. In the case where the maximum count of IRC channel message buffers was exceeded, the prune function will erase channel messages from the default IRC message cache.

The request body shall contain the property "channel" of type string containing the channel name.

POST Request Body

{
  "channel": "#test"
}

GET /irc/cache

The web server maintains a cache of IRC server messages messages (lines of text) in RFC 2812 format. Performing a GET request to /irc/cache will return a response containing and array of elements of type string. Each array string element represents a previous cached IRC server message.

When using this IRC client on an iPhone, there are issues where the websocket is disconnected when the screen lock is enabled. Before using this API, the browser will clear all previous channel messages, private messages and server messages. From the API response, each string in the cache array can be parsed one by one as if they had just arrived from the IRC server. This will restore the user interface to show messages that may have arrived while the websocket was disconnected.

Starting with version v0.2.14, the message cache consists of one cache buffer for IRC server messages, those being messages which are not related to a specific IRC channel. Additional cache buffers are created for each IRC channel up to a maximum limit. When the limit is exceeded, IRC messages from extra IRC channels are cached in the default server message cache. In response to the API call, all cache buffers are concatenated before sending. A section of messages from a single IRC channel cache would be in chronological order within that IRC channel. The web browser will restore the display independently for each IRC channel, so a channel window will be properly ordered. Combined, however, the overall set of messages may be out of chronological order between different cache buffers.

The size of the cache is 100 messages per buffer. There is 1 default server buffer, 1 private message (PM) buffer, and 5 additional IRC channel buffers. Therefore up to 600 IRC messages could be returned. This is intended to ride out a screen lock. It is not meant to be an offline client. Over time messages will cycle out of the cache and may be lost without viewing.

Example API response:

[
  "@time=2021-06-18T21:13:06.949Z :myNickName!*@* PRIVMSG #test :Hello, how are you doing",
  "@time=2021-06-18T21:14:16.949Z :otherName!*@* PRIVMSG #test :I am doing fine, how are you",
  "@time=2021-06-18T21:15:26.949Z :myNickName!*@* PRIVMSG #test :I am fine also, I have to run",
  "@time=2021-06-18T21:16:03.949Z :otherName!*@* PRIVMSG #test :OK, bye for now"
]

In the IRC protocol, QUIT messages do not include the IRC channel name. It is up to the IRC client to maintain a list of channel members and apply the QUIT message to the proper channel(s). With a chronological linear message cache, orphan QUIT messages in the cache belonging to an IRC user who has left before the cache was restored are not able to be matched to the any active channel windows. To address this, in v0.1.14 a new IRC message type was created as `cachedQUIT`. The format is similar to the standard QUIT message except an additional field has been added for the IRC channel name. In the cache, the cachedQUIT message is duplicated for each IRC channel. As real time messages are received from the IRC server, standard QUIT messages are parsed directly. When the browser content is deleted and restored from the IRC message cache, the alternate cachedQUIT messages are used.

In v0.2.16, similar code was added for the NICK command. A new IRC message cachedNICK was created. The NICK message is duplicated for each IRC channel where the NICK is applicable.

Example showing QUIT, cachedQUIT, NICK, and cachedNick messages


Live message:
@time=2022-09-04T22:24:33.083Z :nickname!~user@192.168.1.1 QUIT :test QUIT message
@time=2022-09-04T22:25:22.144Z :oldNickname!~user@192.168.1.1 NICK :newNickname

Cached message with 2 IRC channels.
@time=2022-09-04T22:24:33.082Z :nickname!~user@192.168.1.1 cachedQUIT #channelName :test QUIT message
@time=2022-09-04T22:24:33.082Z :nickname!~user@192.168.1.1 cachedQUIT #otherChannel :test QUIT message
@time=2022-09-04T22:25:22.144Z :oldNickname!~user@192.168.1.1 cachedNICK #channelName :newNickname

POST /irc/erase

This API is used to delete the contents of the IRC message cache. The entire message cache may be erased and reinitialized, or specific message types may be erased from the cache.

The primary purpose for cache erase is to remove previous content when changing from one IRC network to a different IRC network.

The erase function may also be used to manage the number of panels in the web browser. When the web browser connects to the web server, the entire message cache is sent to the browser. In the browser, different panels will open automatically to display different types of data, such as IRC channel PRIMVSG messages, PM messages, NOTICES and WALLOPS messages. There was a tendency to open extraneous panels to display past IRC messages that were no longer relevant, making the browser interface cluttered with extra panels. This API allows cached data for specific panels to be deleted independently. This in turn eliminates unnecessary browser panels from being opened automatically.

A target specifier is required to determine the scope of the erase function. The target specifier in an "erase" property in the body of the POST request. Valid values are:

Example POST Request Body:

{
  "erase": "CACHE"
}

POST /irc/disconnect

This is an emergency function used to forcibly close the socket to the IRC server. In routine operation, IRC connections should be closed by sending an RFC 2812 "QUIT" command to the web server for re-transmission to the IRC server as a normal IRC command. The body of the request should contain an empty JSON object "{}".

POST Example body:

{
}

POST /terminate

This is essentially a "die" function to forcibly shutdown the NodeJs web server. A confirmation flag of type boolean is required in a "terminate" property in the POST body.

POST Example body:

{
  "terminate": "YES"
}
Server List Editor API

The server list editor web page located at "/irc/serverlist.html" is used to view the list of available IRC server definitions. Individual IRC servers can be modified using a simple HTML form editor. The purpose of the API located at "/irc/serverlist" is to service the associated HTML page.

Modification of the list of IRC servers operates independently of the IRC web client. The IRC client must be disconnected from all IRC servers in order to use these API calls to modify the list of IRC servers.

The list of IRC servers is stored in the base folder of the IRC web server in the file "servers.json" The file may be edited manually instead of using this interface.

The IRC server list editor can be disabled by setting "disableServerListEditor: true" in the credentials.json file or by setting the environment variable "IRC_DISABLE_LIST_EDITOR=true" When disabled, all attempts to access the /irc/serverlist API will return Status 405 Method not allowed "Server List Editor Disabled"

The API supports several methods, such as GET, POST, PATCH, COPY, and DELETE which are used to perform various tasks.

Errors that occur during input validation checking will return Status 422 Unprocessable entity.

The fields used for IRC server password, SASL password, and user's NickServ command are write only fields. New values may be submitted, but existing values are not returned by the API. However, these values are stored un-encrypted in plain text in the servers.json file, and may be viewed there.

GET /irc/serverlist (without params)

Submitting a GET request without any URL query parameters will return the complete list of IRC server definitions from the servers.json file. The response is a JSON encoded array of javascript objects. The list of IRC channels is a comma separated list.

API response to GET /irc/serverlist

[
    {
      "index": 0,
      "disabled": false,
      "group": 0,
      "name": "DALnet-1",
      "host": "lion.dal.net",
      "port": 6697,
      "tls": true,
      "verify": true,
      "proxy": false,
      "reconnect": false,
      "logging": true,
      "identifyNick": "",
      "nick": "MyNickName",
      "altNick": "",
      "recoverNick": false,
      "user": "user",
      "real": "John Doe",
      "modes": "+i",
      "channelList": "#myChannel, #otherChannel"
  },
  {
    "index": 1,
    "disabled": false,
    "group": 0,
    "name": "DALnet-2",
    "host": "irc.dal.net",
    "port": 6667,
    "tls": false,
    "verify": false,
    "proxy": false,
    "reconnect": false,
    "logging": true,
    "identifyNick": "",
    "nick": "MyNickName",
    "altNick": "",
    "recoverNick": false,
    "user": "user",
    "real": "John Doe",
    "modes": "+i",
    "channelList": "#myChannel, #otherChannel"
  }
]

GET /irc/serverlist?index=0&lock=1

Submitting a GET request with a query parameter "index" will return the individual server definition with the specified index number. Specification of the query parameter "lock=1" will attempt to check out the record for editing by placing a lock flag on the database. Repeating the same request with "lock=0" will clear the lock flag .

The purpose of this request is to checkout and open an existing IRC server definition for editing. When editing complete, the modified data would be returned using the PATCH method shown further below.

API response to GET /irc/getircstate?index=0&lock=1

{
  "index": 0,
  "disabled": false,
  "group": 0,
  "name": "DALnet",
  "host": "lion.dal.net",
  "port": 6697,
  "tls": true,
  "verify": true,
  "proxy": false,
  "reconnect": false,
  "logging": true,
  "identifyNick": "",
  "nick": "MyNickName",
  "altNick": "",
  "recoverNick": false,
  "user": "user",
  "real": "John Doe",
  "modes": "+i",
  "channelList": "#myChannel, #otherChannel"
}

Error responses:

POST /irc/serverlist

The POST method is used to create a new IRC server definition. The database should be unlocked for this request. A valid CSRF token is required in the request headers as described above.

The request must not include an "index" property because a new index will be automatically generated when the record is created.

The properties "password", "saslPassword" and "identifyCommand" are optional. If the POST request includes these properties, they will be part of the server definition. An empty string may be submitted, such as: password:""

POST Request Body:

{
  "disabled": false,
  "group": 0,
  "name": "DALnet",
  "host": "lion.dal.net",
  "port": 6697,
  "tls": true,
  "verify": true,
  "proxy": false,
  "reconnect": false,
  "logging": true,
  "password": "",
  "saslUsername": "",
  "saslPassword": "",
  "identifyNick": "",
  "identifyCommand": "",
  "nick": "MyNickName",
  "altNick": "",
  "recoverNick": false,
  "user": "user",
  "real": "John Doe",
  "modes": "+i",
  "channelList": "#myChannel, #otherChannel"
}  

The response body will include the index number of the new record.

Success Response:

{
  "status": "success",
  "method": "POST",
  "index": 4
}

Error Response:

Input Validation Error Response: (Missing port number)

{
  "status": 422,
  "message": "Unprocessable Entity",
  "errors": [
      {
          "msg": "Required values",
          "param": "port",
          "location": "body"
      },
      {
          "msg": "Invalid socket port number",
          "param": "port",
          "location": "body"
      }
  ]
}

PATCH /irc/serverlist?index=xxx

The PATCH method is used to modify an existing IRC server definition. Prior to calling the PATCH method, a current copy of the data should be retrieved using GET /irc/serverlist?index=xxx?lock=1. The GET request query parameter "lock=1" will set a lock flag with the index value for this record. Successful completion of this request will remove the lock flag automatically. A valid CSRF token is required in the request headers as described above.

The index parameter is required as a URL query parameter. The index value specified in the body of the request must match the index value in the URL query parameters.

The properties "password", "saslPassword" and "identifyCommand" are optional. In the example below, these propertires are omitted. If the PATCH request includes these properties, any existing value will be replaced. If the properties are omitted from the object, no changes will be made to hose properties in the original record. The GET request will not include these existing values for security concerns. An empty string can be sent to remove a previous value (i.e. no server password). In the example below, they are omitted, retaining existing values.

It is also possible to edit the existing servers manually in the servers.json file using a text editor.

PATCH Request Body:

{
  "index": 0,
  "disabled": false,
  "group": 0,
  "name": "DALnet",
  "host": "lion.dal.net",
  "port": 6697,
  "tls": true,
  "verify": true,
  "proxy": false,
  "reconnect": false,
  "logging": true,
  "saslUsername": "",
  "identifyNick": "",
  "nick": "MyNickName",
  "altNick": "",
  "recoverNick": false,
  "user": "user",
  "real": "John Doe",
  "modes": "+i",
  "channelList": "#myChannel, #otherChannel"
}  

Success Response:

{
"status": "success",
"method": "PATCH",
"index": 0
}

Error Response:

Input Validation Error Response: (Missing port number)

{
  "status": 422,
  "message": "Unprocessable Entity",
  "errors": [
    {
      "msg": "Required values",
      "param": "port",
      "location": "body"
    },
    {
      "msg": "Invalid socket port number",
      "param": "port",
      "location": "body"
    }
  ]
}

COPY /irc/serverlist?index=xxx

The COPY method is used to duplicate one of the existing records and append the duplicated copy to the end of the server list arrray.

The index parameter is required as a URL query parameter. The index value specified in the body of the request must match the index value in the URL query parameters. The index property in the response body will point to the index of the new record created by the request.

COPY Request Body:

{
  "index": 0
} 

Success Response:

{
  "status": "success",
  "method": "COPY",
  "index": 3
}

Error Response:

DELETE /irc/serverlist?index=xxx

The DELETE method will remove and delete the record at the specified index. The remaining records will be renumbered. The response body will include the index number of the deleted record. The database must be unlocked to the DELETE method.

The index parameter is required as a URL query parameter. The index value specified in the body of the request must match the index value in the URL query parameters.

The body of the request may include other valid properties. Only the "index" property is used by the DELETE method. Other valid properties are ignored. Extraneous unrecognized properties will generate an error.

DELETE Request Body:

 {
  "index": 2,
}

Success Response:

{
  "status": "success",
  "method": "DELETE",
  "index": 3
}

Error Response:

POST /irc/serverlist/tools?index=0

A "tools" API can be used to perform various toolbox functions to the server list. The selection is based on the "action" property in the body of the request. The following actions are available.

POST /irc/serverlist/tools?index=xxx with action: 'move-up'

When action is set to "move-up" the record at the specified index is moved up to the next decremented index number. The up direction will lower the index number by 1 moving records towards index 0. The record at index 0 can not be moved, and the request will be ignored without error. The index property in the response body will point to the index of the relocated record.

POST Request Body:

 {
  "index": 2,
  "action": "move-up"
}

Success Response:

{
  "status": "success",
  "method": "POST",
  "index": 1
}

POST /irc/serverlist/tools?index=xxx with action: 'toggle-disabled'

When action is set to "toggle-disabled" the record at the specified index will toggle the "disabled" property between true and false. The index property in the response body will point to the index of the modified record. The new value of the disabled property will be returned in the body of the response as a boolean "value" property.

POST Request Body:

{
"index": 2,
"action": "toggle-disabled"
}

Success Response:

{
"status": "success",
"method": "POST",
"index": 2,
"value": false
}

Error Response:

Websocket Interface

In a typical IRC client program, such as mirc or hexchat, the client IRC program establishes a bi-directional TCP network socket connection between the IRC client and the IRC server. The client software sends IRC commands and channel text messages over the TCP socket. The IRC server responds asynchronously with RFC 2812 messages over the TCP network socket.

In the irc-hybrid-client program, the backend web server serves as the IRC client. The backend maintains the IRC server TCP socket connection. The backend is capable to maintain a valid IRC user connection independent of the web browser. Therefore, IRC server messages related to connection status, channel membership, and other state is parsed and maintained within the backend server.

Although a summary of IRC connection state can be retrieved over the /irc/getircstate API, as described above, the ircState json object does not contain text messages from other users.

Independent of the TCP socket connection between the backend and the IRC server, the web browser opens an independent websocket connection to the backend webserver, using ws:// or wss:// protocol standard websocket connections. The websocket passes a stream of utf-8 characters. Each message is delimited by CR,LF end of line characters ("\r\n").

The TCP socket for the websocket connection between web browser and the irc-hybrid-client web server is managed on the server end by the NPM package "ws" using RFC 6455 Websocket Protocol. Chrome supports this type of websocket connection and no special library is required for the web browser. The general connection process involves making a HTTP request with special headers to designate the request as an 'upgrade' request. The ws library then opens the websocket connection and manages messages, errors, and closure of the connection.

In order to initiate a websocket connection, the browser must obtain a valid cookie using the normal user password login process. The browser must extract a valid CSRF token from the HTML on the main page. Using the website's cookie and the valid CSRF token, the browser performs a POST request to the /irc/wsauth route. The cookie is validated. The CSRF token is validated. The cookie will be remembered. A 10 second timer is started. Within the 10 second time window, the browser submits a connection upgrade request to the /irc/ws route using ws:// or wss:// protocol including the same cookie in the request headers. Upon successful validation of the cookie the server passes the request to the ws library to manage the websocket. Unauthorized connection upgrade requests will return a status 401 Unauthorized error.

All RFC 2812 messages from the web server are passed through the backend. A copy of each message is echoed to all connected web browsers in utf-8 format over the websocket. JavaScript in the web browser parses the messages to identify text messages from other IRC users. User text messages are then displayed as text content in the appropriate windows.

The websocket stream also contain command messages intended to initiate browser actions. There are 6 types of command defined in the following table. Each message type begins with a specific prefix string as shown in the first table column.

Any message not beginning with a command prefix string is considered to be a valid RFC 2812 IRC server message. Valid RFC 2812 messages are prefixed with a timestamp in IRC_V3 format.

Prefix Description
HEARTBEAT The web server will send a HEARTBEAT message over the websocket each 10 seconds. It is the responsibility of the web browser javascript to monitor these recurring messages. Absence of the HEARTBEAT messages shows the websocket is no longer actively connected to the web server.
UPDATE UPDATE is a command to instruct the browser to call route /irc/getircstate in response to a change in the ircState object in the backend. For example, when a person departs an IRC channel, the departing user's nick name is removed from the array of channel membership within the backend ircState object. An UPDATE request is sent to the web browser. In response the UPDATE message, the browser performs an API call to the /irc/getircstate route to obtain a new copy of the ircState object. The ircState object is parsed by the browser and the channel membership name list is updated in the appropriate channel window.
CACHERESET CACHERESET is a command that will instruct all connected web browsers to clear the content of all related textarea elements. The primary purpose is to clear content from one IRC network from being displayed after a new connection is made to a different IRC network. The CACHERESET command is sent by the server when it detects a change in server group number. It is also sent to all connected web browsers in response to an API call to the /irc/erase route.
CACHEPULL CACHEPULL is a command will instruct all connected web browsers to reload the entire message cache from the web server and update all textarea elements accordingly. The CACHEPULL command is sent by the server when one specific part of the IRC message cache has been erased using the POST /irc/erase route. This will allow extraneous windows to be removed after the content is no longer relevant and has been deleted from the message cache.
DEBUGPONG DEBUGPONG is a diagnostic command used to measure network latency between the web browser and the web server. The test is triggered when the browser performs a GET request to the "/irc/test3" route. The HTTP connection will return a Status 201 response which may be used to measure the API delay time of the HTTP response. Concurrently, the web server will return the text command "DEBUGPONG" through the web socket stream. The browser can then measure the latency of commands sent to the API and returned by websocket stream. There is a button in the Debug panel to perform this.
LAG=0.001 Each time the backend IRC client sends a PING command to the IRC server, the delay time for the server PONG response is measured. The time value in seconds is sent to the browser over the websocket using a message as a string in the following format: "LAG=x.xxx\r\n".
webServer: Prefix to indicate an informational message from the backend. The webServer messages are primarily intended to show IRC client events that are intended for debugging. The webServer messages are not intended for display to the IRC user.
webError: Prefix to indicate an error has been detected by the backend while acting as an IRC client program. The expectation is that contents of the error are displayed to the IRC user as a valid error message within the web page.
--> The prefix "-->" is an indicator to specify copies of outgoing messages that are sent from the backend web server to the IRC server. Outgoing messages are echoed verbatim to all connected web browsers. Some IRC commands containing possible passwords may be filtered. Visibility of the exact messages sent to the IRC server can be useful when troubleshooting software errors. These outgoing messages can be viewed in the IRC client Server window by clicking checkboxes for [x]View-Raw and [x]Add-Comms. These are outgoing messages. There is no assurance the IRC server accepted them without error. Therefore, these messages should be considered as informational only for debug purposes. Messages starting with "-->" should not be parsed by the browser for content. As shown in the following example, the user +i user mode change should be parsed from the (incoming) "MODE" IRC message, not the (outgoing) "-->" debug message.

The following example shows a sample of websocket messages.

HEARTBEAT
HEARTBEAT
UPDATE
webServer: Opening socket to irc.example.com:6667
webServer: Connected
webServer: Ready
--> NICK myNick
--> USER myUsername 0 * :John Doe
UPDATE
@time=2021-07-19T11:56:30.532Z :irc.example.com 001 myNick :Welcome to the Internet Relay Network myNick!~myUsername@192.168.1.182
@time=2021-07-19T11:56:31.532Z :irc.example.com 002 myNick :Your host is irc.example.com, running version ngircd-25 (x86_64/pc/linux-gnu)
@time=2021-07-19T11:56:31.532Z :irc.example.com 003 myNick :This server has been started Thu Jul 15 2021 at 18:12:20 (UTC)
@time=2021-07-19T11:56:31.532Z :irc.example.com 004 myNick irc.example.com ngircd-25 abBcCFiIoqrRswx abehiIklmMnoOPqQrRstvVz
--> MODE myNick +i
@time=2021-07-19T11:56:32.533Z :myNick!~myUsername@192.168.1.182 MODE myNick :+i
UPDATE
HEARTBEAT
HEARTBEAT
LAG=0.023
HEARTBEAT
API Manual Testing

Manual testing of the backend web server requires two tools. One to issue http GET and POST methods to the web server. These API requests initiate subsequent RFC 2812 IRC commands which are sent asynchronously to the IRC server. A second tool is required to observe the asynchronous RFC 2812 IRC messages which originate in the IRC server and are passed through the web server and onward to the web browser using the websocket stream connection.

One way to send http requests to the backend is to use the VSCode extension Thunder Client. This is an API test application similar to Postman. The irc-hybrid-client github repository includes a Thunder Client collection containing example API calls described above. This program's API debug collection can be imported from the cloned repository. The following documentation will explain using Thunder Client to issue API requests. Thunder Client should be used in combination with an independent method to monitor IRC messages.

Warning: Running the tests individually can cause the web server to initiate IRC connections and issue IRC commands WITHOUT display of the IRC server responses. This is because IRC server responses are returned asynchronously in the websocket stream which is not visible in Thunder Client. In most cases, the API response is limited to a true/false error flag showing initialization of an asynchronous request, not subsequent completion or data. The collections include tests defined in a specific order such that interaction between the web server and IRC server are blocked as authorization failures. Should you choose to run the API requests individually, it is recommended to use a dedicated IRC server.

IRC server setup for testing

It is recommended to install a temporary IRC server to perform these tests. The irc-hybrid-client was developed using the Debian apt package "ngircd". Performing these tests can submit multiple IRC messages in rapid succession. Running this on a public IRC server may get the IP addressed banned.

It is recommended to run the entire test on an isolated network or inside a virtual machine. In an isolated environment TLS and TLS certificates are not required. Development testing was performed using a ngirc IRC server on a raspberry pi on segregated network behind a NAT router.

The temporary IRC server that we just created must be added to the irc-hybrid-client server list. If you prefer to use the internal server list editor web page, you can skip the next part and use the server list editor web page after the app is running.

In the base folder of the repository, copy the example IRC server file to servers.json

   cp -v example-servers.json servers.json

The File will look like this. Setup the servers.json to match the test IRC server you just installed. The file is in JSON format. Use caution to use quotes and commas in the proper syntax. The important values are marked with arrows.

{
  "configVersion": 2,
  "ctcpTimeLocale": ["en-US", "UTC"],
  "serverArray": [
    {
      "disabled": false,
      "name": "Test IRC Server",    <------
      "host": "127.0.0.1",          <------
      "port": 6667,                 <------
      "tls": false,
      "verify": false,
      "proxy": false,
      "reconnect": false,
      "logging": false,
      "password": "",
      "saslUsername": "",
      "saslPassword": "",
      "identifyNick": "",
      "identifyCommand": "",
      "nick": "Bob",                <------
      "altNick": "",
      "recoverNick": false,
      "user": "user",               <------
      "real": "Bob Smith",          <------
      "modes": "+i",
      "channelList": ["#test"]
    }
  ]
}  

Configuration of irc-hybrid-client for testing

Previous sections of this documentation contained detail instructions for installation and setup of the irc-hybrid-client web server. The following are limited instructions for creating a test instance of the web server in an isolated environment that is protected from the internet.

The irc-hybrid-client is configured using a .env file. The following minimum configuration may be used for testing.

Example .env file

ENV_VAR_CONFIG_VERSION=2
LOGIN_USER_USERID=1
LOGIN_USER_USER="user1"
LOGIN_USER_NAME="Bob Smith"
LOGIN_USER_HASH="---BCRYPT-HASH-GOES-HERE---"
SERVER_TLS=false
SERVER_PORT=3003
SERVER_INSTANCE_NUMBER=0
SESSION_SECRET="---COOKIE-SECRET-GOES-HERE---"  

Thunder Client configuration

This must be done **BEFORE** importing the collection. Otherwise, tests using the COPY method will be imported as GET requests.

In Thunder Client: Disable the functionality to follow redirects (302)

  Settings > User > Extensions > Thunder Client > Follow Redirects (Uncheck)

In Thunder Client: Add "COPY" as a custom HTTP method.

  Settings > User > Extensions > Thunder Client > Custom Methods: COPY

Thunder Client Environment Variables

A local environment is required to save authorization codes and access_tokens. In the Thunder Client "Env" tab, if "(Local Env)" does not show in the list of environments. it must be created by selecting "Local Environment" in dropdown.

The following environment variables may be imported into Thunder Client from: "thunderclient/thunder-environment_irc-hybrid-client.json". The first 6 environment variables listed below are used for the main IRC test collection. The remaining 7 environment variables that begin with "auth_" are only required if remote authentication is used. The variables beginning with auth_ may be omitted for this part of the test.

server_URL:          "http://localhost:3003"
server_user1:        "user1"
server_password1:    "mysecret"
irc_nickname:        "myNick"
irc_channel":        "#test"
server_die:          "NO"

auth_host:           "http://127.0.0.1:3500"
auth_redirect_uri:   "http://localhost:3003/login/callback"
auth_username:       "bob"
auth_password:       "bobssecret"
auth_client_id:      "irc_client_1"
auth_client_secret:  "ssh-secret"
auth_scopes:         "irc.all"

Thunder Client Collection

In the Thunder Client extension in VSCode, import the irc-hybrid-client collection from "thunderclient/thunder-collection_irc-hybrid-client.json". This collection is divided into 5 folders that can be run as individual sequences or collectively the entire collection can be run as a single sequence.

The test described below are in the collection folder "Message Debug"

Obtaining a Thunder Client cookie

To get a valid cookie, run selections 7.1, 7.2, and 7.3 from the Thunder Client collection. 7.1 Will clear previous cookie. 7.2 Will request the login form, then extract a login random nonce and CSRF token from the form. 7.3 Will issue a POST request containing username and password to authenticate the user. Steps 7.2 and 7.3 are time critical and must be executed together to avoid time expiration of the random nonce.

List of tests

Collection: "irc-hybrid-client message debug "

1F Additional documentation

Additional documentation is available in a README.md file located in the github directory where the Thunder Client collections are located.

1G - IRC command issuance

The following screen capture shows an API POST request used to emulate transmission of a user text message to an active IRC channel. The body of the POST request contains a "message" property containing an RFC 2812 formatted message. The IRC message includes the command "PRIVMSG", followed by the first IRC argument (channel name), followed by the second IRC argument (text message content) which begins with a colon (:) character, followed by a UTF-8 string terminating in an end-of-line character.

The Thunder Client response shown below has the response body tab selected. The contents of the response is limited to a boolean error flag. The error condition refers to acceptance of the API request by the web server. As the IRC command is passed to the IRC server, should an IRC server error occur, the IRC server error will appear in the websocket stream, not on the API response.

API test Example

Thunder Client is not capable to view the websocket response. The expected response should be returned to the web browser as stream data containing IRC messages received over the websocket. The example below shows the sample websocket message expected from the above Thunder Client request. The first line is a RFC 2812 formatted IRC message intended to be parsed by the browser in order to add content to the displayed web page. The second line starting with "-->" is a verbatim copy of the outgoing IRC message sent to the IRC server which is intended to be used for debugging purposes only.

@time=2021-07-19T14:35:37.000Z :myNick!*@* PRIVMSG #test :This is a test message (irc-hybrid-client)
--> PRIVMSG #test :This is a test message (irc-hybrid-client)

As a side note, IRC servers do not return outgoing IRC channel text messages back to the IRC client. The irc-hybrid-client supports multiple concurrent browsers connected at the same time. In order to display outgoing messages on all connected browsers, the previous websocket PRIVMSG response was generated by the backend in valid RFC 2812 format, so it could be parsed in the browser, equivalent to incoming PRIVSG messages from other users.

The following sections include instruction on methods to view these web socket messages.

2 - Browser Websocket Messages (using IRC client)

There are two ways to view websocket stream messages. Websocket messages can be viewed in raw format within the irc-hybrid-client web page. Alternately, a simple html page and javascript file was included as a debug tool to monitor the websocket independent of the IRC client web page.

To view websocket messages in the IRC client, first open the irc-hybrid-client web page. Enter the username and password that were setup for Thunder Client tests as described above. In the main navigation dropdown menu, select More..., then select Debug Tools. The Debug Panel will open. An screen shot of the debug panel is included at the bottom of this page. In the Debug Panel, select Button 3-3 Open: IRC raw message log. Press the Start button to enable real time display of websocket messages.

Debug Panel

3 - Browser Websocket Messages (custom debug tool)

The irc-hybrid-client github repository contains a custom web page used to test the websocket connection. In order for the session cookies to work properly with the websocket, the websocket must exist on the same web server containing the browser html page and browser javascript. The custom web page is located in the "server/testws" folder. It contains 3 files, one each html, js and css. To enable this route and serve these files from the main web server, TEMPORARILY remove comment characters from the following code located in the "server/web-server.mjs" file. Restart the server after the edit.

// console.log('******** (debug) /testws route enabled without authorization **********');
// if (nodeEnv === 'development') {
//   const testWs = require('./testws/testws');
//   app.use('/testws', testWs);
// }

Caution: The /testws route does not require authentication and would be publicly visible. Remember to disable the route by replacing the comment characters after testing.

It is necessary to obtain a session cookie. Both Thunder Client and the web browser each requires it's own cookie. The easiest way to do this is to open the normal login form at the "/login" route. The login route can be appended to the testing web URL, such as "http://localhost:3000/login". The following window should open.

Web login form

If your login is successful, the normal IRC client page will open. This page will not be used. Instead, edit the URL in the browser bar and go to the following route: "/testws/test-websocket.html". If you are using localhost, it will look something like this, but possibly a different port number, depending on your configuration file.

http://localhost:3000/testws/test-websocket.html

The custom web socket debug page should open. It will look similar to the following example.

In order to connect the web socket, first a special http POST request must be issued to schedule the websocket authentication. The current expiration time limit is 10 seconds. Immediately, the browser must open the websocket, either normal (ws://) or with TLS (wss://).

To do this, first select the [Schedule Auth] button, then immediately select the [Connect Websocket] button. If successful, you will see the message: "websocket: open event fired".

Websocket messages appear in the textarea labeled "Messages"

Please remember to disable the "/testws" route after testing.

For additional information about use of the test websocket page to verifiy access restrictions to the web socket, view the README.md file in the " server/testws" folder.

Websocket Example
Web page Debug Panel

The Debug Panel is a general toolbox of diagnostic and software development functions. These are intended for use in writing software changes and tracing software errors. This panel is NOT intended for general use in operating the IRC client.

When using irc-hybrid-client on an iPhone, security features on mobile devices make it difficult to debug software. The debug panel allows in-app information that would typically be available in the browser development tools console to be viewed directly in the application.

For users interested in learning how the RFC-2812 IRC protocol works, debug tools allow display of RAW IRC messages as they pass to and from the IRC network from your IRC client.

The Debug panel can be invoked from the dropdown navigation menu.

Appending "#DEBUG" to web page URL will show the debug panel when the page loads.

Admin Functions

Debug Functions

Debug Panel Examples

In the example below, Button 1-3 was selected. The memory usage was displayed at the bottom of the Debug Panel.

Debug Panel

Example showing the Button 3-3 from the Debug Panel.

Debug Panel