├── .babelrc
├── .gitignore
├── .meta
├── logui.svg
└── tudelft.svg
├── CHANGELOG.md
├── PROTOCOL.md
├── README.md
├── build
├── env
│ ├── controller.js
│ ├── index.html
│ ├── normalize.css
│ └── styles.css
└── reactapp
│ ├── .babelrc
│ ├── app
│ ├── app.js
│ └── root.js
│ ├── package-lock.json
│ └── package.json
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── main.js
└── modules
│ ├── DOMHandler
│ ├── DOMPropertiesObject.js
│ ├── binder.js
│ ├── browserEventsController.js
│ ├── handler.js
│ ├── helpers.js
│ └── mutationObserverController.js
│ ├── browserEvents
│ ├── contextMenu.js
│ ├── cursorPosition.js
│ ├── pageFocus.js
│ ├── scroll.js
│ ├── urlChange.js
│ └── viewportResize.js
│ ├── config.js
│ ├── defaults.js
│ ├── dispatchers
│ ├── consoleDispatcher.js
│ └── websocketDispatcher.js
│ ├── eventCallbackHandler.js
│ ├── eventHandlerController.js
│ ├── eventHandlers
│ ├── formSubmission.js
│ ├── mouseClick.js
│ ├── mouseHover.js
│ ├── sampleEventHandler.js
│ └── scrollable.js
│ ├── eventPackager.js
│ ├── helpers.js
│ ├── metadataHandler.js
│ ├── metadataSourcers
│ ├── elementAttribute.js
│ ├── elementProperty.js
│ ├── localStorage.js
│ ├── reactComponentProp.js
│ ├── reactComponentState.js
│ └── sessionStorage.js
│ ├── required.js
│ ├── specificFrameworkEvents.js
│ └── validationSchemas.js
└── tests
├── env
└── landing.html
└── modules
└── sample.spec.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/env"
4 | ],
5 | "plugins": [
6 | ["import-directory"],
7 | ["@babel/plugin-transform-runtime", {
8 | "regenerator": true
9 | }]
10 | ]
11 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # System databases
2 | Thumbs.db
3 | .DS_Store
4 |
5 | # Node/npm Specifics
6 | node_modules/
7 | .npm
8 | .env
9 |
10 | # VSCode
11 | .vscode/*
12 | !.vscode/settings.json
13 | !.vscode/tasks.json
14 | !.vscode/launch.json
15 | !.vscode/extensions.json
16 |
17 | # Repository-specific rules
18 | build/*.bundle.js
19 | build/
20 | tests/screenshots
21 | tests/logui.test.bundle.js
--------------------------------------------------------------------------------
/.meta/logui.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
73 |
--------------------------------------------------------------------------------
/.meta/tudelft.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
63 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # LogUI Changelog
2 |
3 | This Markdown file contains the `CHANGELOG` for the LogUI client library. Changes are made as per the [GNU CHANGELOG Style Guide](https://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html).
4 |
5 | ```
6 |
7 | 2020-09-14 Version 0.4.0
8 |
9 | Implemented basic repository structure, including working build and test environment. Repository ready for the addition of existing library code.
10 |
11 | * NPM is the selected package manager.
12 | * package.json contains the necessary infrastructure to build and test the project.
13 | * Sample test sample.spec.js included.
14 | * .gitignore is included.
15 |
16 | 2021-03-26 Version 0.5.0
17 |
18 | Basic implementation now complete. All basic functionality has been included, along with communicative ability to the LogUI server component.
19 |
20 | 2021-03-26 Version 0.5.1
21 |
22 | Works with LogUI server version 0.5.1 and above.
23 |
24 | Altered the configuration object to include an authorisation token, not an authentication token. Tidying up terminology.
25 |
26 | 2021-03-31 Version 0.5.2a
27 |
28 | Works with LogUI server version 0.5.1 and above.
29 |
30 | Altered the behaviour of the eventCallbackHandler to prevent event bubbling.
31 | Updated the websocketDispatcher to suppress logging when verbose is set to false.
32 | ```
--------------------------------------------------------------------------------
/PROTOCOL.md:
--------------------------------------------------------------------------------
1 | # LogUI Communication Protocol
2 |
3 | This document outlines the protocol that exists between the LogUI Client and LogUI Server. **Consider this page to be the definitive guide for the LogUI communication protocol.**
4 |
5 | Last Updated | Changed By
6 | -------------|--------------
7 | 2020-09-15 | David Maxwell
8 |
9 | ## TODOs
10 |
11 | - [x] Outline protocol
12 | - [x] Provide error codes
13 | - [ ] Include description of minimum viable event description
14 | - [ ] Diagram of process
15 | - [x] WebSocket Connection
16 | - [x] LogUI Handshake
17 | - [x] Event Listening
18 | - [x] Application-Specific Changing
19 | - [x] WebSocket Disconnection
20 |
21 | ## Protocol Stages
22 |
23 | The protocol establishes a sequence of stages that take place over the WebSocket connection between the client and server. All exchanges take place via standard WebSocket calls after the HTTP layer establishes a connection.
24 |
25 | 1. **WebSocket Connection**
26 | The LogUI client creates a connection to the LogUI logging endpoint.
27 |
28 | 2. **LogUI Handshake**
29 | The handshake that establishes identity.
30 |
31 | 3. **Event Listening**
32 | The server then waits for events and logs them as they are sent by the client. The client may also request to update the [application-specific information](#logui-handshake) stored for each logged event.
33 |
34 | 4. **WebSocket Disconnection**
35 | Disconnect. The disconnect can be triggered by either the client or the server.
36 |
37 | Of course, there are several possible responses to each of the above stages. Things can go wrong! Hopefully, this protocol allows each party to be aware of each other's state, and act accordingly. We now take each stage in turn, providing detailed explanations of what exactly happens during these stages. We also include example messages to demonstrate the functionality of the procotol.
38 |
39 | ### WebSocket Connection
40 | This stage is handled by the [WebSockets Web API](https://developer.mozilla.org/en-US/docs/Web/API/Websockets_API). A connection to the LogUI logging endpoint is established by the aforementioned APIs. Once a connection has been established, we move to the LogUI handshake stage.
41 |
42 | ### LogUI Handshake
43 | The LogUI handshake stage provides the server with basic authentication details that allow the server to determine whether or not it should be listening to the client attempting to establish a connection. Information sent to the server includes the `appIdentifier` string, an encrypted string that when decrypted reveals what application is being logged, and the version of the LogUI client library being used. Information can be checked by the server against a database of known applications. If anything doesn't match, the server will refuse to serve the client and disconnect.
44 |
45 | The LogUI handshake **must be the first message sent by the client.** If anything else is sent down the WebSocket to the server, the server will disconnect immediately. If the server does not receive the handshake within the first three seconds, the server will also disconnect.
46 |
47 | #### Complete LogUI Handshake Example
48 | A complete LogUI handshake request sent by the client, complete with sample data, is shown below. **Note that `appIdentifier` is expanded to its full representation** (it would nominally be represented by a single, encrypted string -- more below).
49 |
50 | ```json
51 | {
52 | "messageType": "logui-handshake-request",
53 | "sessionUUID": "ce2a6120-a78e-45e9-86c7-29df8225494d",
54 | "clientTimestamp": "641143800",
55 | "clientVersion": "0.4.0",
56 | "applicationIdentifier": { // Nominally represented as a single string
57 | "applicationID": "9587489a-2bc3-4f49-a795-b2298408fc49",
58 | "flightID": "fc7af2c8-4d39-4ad0-b287-7a2c1e3a60b1",
59 | "expectedClientVersion": "0.4.0"
60 | },
61 | "applicationSpecificData": {
62 | "userID": "exp-user-26",
63 | "condition": "c2",
64 | "askedForHelp": true
65 | }
66 | }
67 | ```
68 |
69 | Let's now walk through each of the individual components of this payload sent to the server. We'll discuss what each key/value pairing represents, and what other values can be provided (for example, if no `sessionUUID` is available).
70 |
71 | ##### `messageType`
72 | This field is a requirement for all messages sent from the LogUI to the server. A `messageType` of `logui-handshake-request` indicates to the server that the remainder of the message will contain information pertaining to a handshake. As such, the server then knows what other fields to expect.
73 |
74 | ##### `sessionUUID`
75 | The `sessionUUID` is a unique session identifier. It identifies the browser tab that has been open, if previous pages with LogUI tracking on the same origin were present. The `sessionUUID` is handled entirely by the client-side library; refer to the documentation of that library for more information. If *no previous page has been loaded* in the same tab or browser session, the value for `sessionUUID` will be set to `null`.
76 |
77 | ##### `clientTimestamp`
78 | The field `clientTimestamp` must provide the UNIX timestamp for the browser at the point the client sends the handshake request. This timestamp is used by the LogUI server to synchronise the client's time against the server's time, and provides a point in time that logged events can be measured from in absolute terms.
79 |
80 | ##### `clientVersion`
81 | `clientVersion` reports the version of the LogUI client library that is being used to issue the handshake request. In the example above, version `0.4.0` is shown. If the version of the LogUI client library is not compatible with the LogUI server being connected to, the handshake is considered invalid.
82 |
83 | ##### `applicationIdentifier`
84 | The `applicationIdentifier` field contains a number of subfields that are used by the LogUI server to verify who is attempting to authenticate. The following fields are what the complete data structure should look like when decrypted by the LogUI server.
85 |
86 | ###### `applicationID`
87 | The `applicationID` is the identifier for the specific application entry in the LogUI server database. If the supplied identifier does not match, the handshake is assumed to be invalid.
88 |
89 | It should be also noted that a given `applicationID` is *tied against a specific domain.* If the domain the server that is hosting the LogUI client library does not match the recorded domain, the handshake is also assumed to be invalid. This is to prevent an `applicationID` code being hijacked and used elsewhere.
90 |
91 | ###### `flightID`
92 | The `flightID` is a sub-identifier for the application, allowing for different configurations of application within the same `applicationID`. Like the `applicationID` above, a comparison is made against the LogUI server's database. If no match is found, the handshake is considered to be invalid.
93 |
94 | ###### `expectedClientVersion`
95 | The final field `expectedClientVersion` dictates what version of the LogUI client has been paired with the `applicationID`. This is to allow for the tying of one specific version of LogUI to an application. If the expected version is not what is reported by the LogUI client itself (in `clientVersion` above), the handshake is rejected.
96 |
97 | ###### (Encrypted) String Representation
98 | Given the sensitive nature of the above three fields, information in `applicationIdentifier` is not sent to the LogUI server in its expanded state. Rather, the `applicationIdentifier` object is converted to a string and encrypted, with this string being sent to the LogUI server on a handshake request instead. This string will be acquired by the developer wishing to integrate LogUI into their web application.
99 |
100 | To give a complete example of `applicationIdentifier`, the above example is encrypted to yield this portion of the handshake request.
101 |
102 | ```json
103 | {
104 | ...
105 | "applicationIdentifier": "ZXlKaGNIQnNhV05oZEdsdmJrbEVJam9pT1RVNE56UTRPV0V0TW1Kak15MDBaalE1TFdFM09UVXRZakl5T1RnME1EaG1ZelE1SWl3aVpteHBaMmgwU1VRaU9pSm1ZemRoWmpKak9DMDBaRE01TFRSaFpEQXRZakk0TnkwM1lUSmpNV1V6WVRZd1lqRWlMQ0psZUhCbFkzUmxaRU5zYVdWdWRGWmxjbk5wYjI0aU9pSXdMalF1TUNKOToxa0lDS206Ym9rWkF3d3lLeU10ZTcwUGZ5N3JkZTBValgwVk9RN2JyTHgwUDY2X1IzNA==",
106 | ...
107 | }
108 | ```
109 |
110 | ##### `applicationSpecificData`
111 | This final field of the handshake again consists of subfields. Here, the developer who is using LogUI can include additional information to be included as part of log events that are specific to the developer's application. For example, the developer may have an application-specific `userID` that they wish to include, or a `condition` or `askedForHelp` field. These values will be stored with each logged event. Nested fields can also be included if this is desired -- the entire set of properties are transferred to each logged event.
112 |
113 | If no application-specific fields are required, this field **must** be present; simply present an empty pair of JSON curly braces, like so.
114 |
115 | ```json
116 | {
117 | ...
118 | "applicationSpecificData": {}
119 | ...
120 | }
121 | ```
122 |
123 | #### Valid Handshake Request
124 | If the `logui-handshake-request` sent to the LogUI server is considered to be valid, the server will then send a response back down the connected WebSocket like the one below.
125 |
126 | ```json
127 | {
128 | "messageType": "logui-handshake-success",
129 | "sessionIdentifier": "ce2a6120-a78e-45e9-86c7-29df8225494d"
130 | }
131 | ```
132 |
133 | This simple response denotes that the handshake request was a success.
134 |
135 | In addition to the successful handshake indication, the response also provides an additional field, `sessionIdentifier`. If this was provided as part of the original handshake request, the same UUID will be provided here. **However, if no UUID was given in the original handshake request, the UUID provided in `sessionIdentifier` denotes a new session identifier.** This UUID should be stored and sent as part of handshake requests for future initialisations of LogUI (of course, only within the same browser session).
136 |
137 | Once this message has been sent from the LogUI server, one can assume that the server then transitions to the next stage, [Event Listening](#event-listening).
138 |
139 | #### Bad Handshake Request
140 | If the handshake request fails for whatever reason, a response is returned that outlines the cause of the failure.
141 |
142 | ```json
143 | {
144 | "messageType": "logui-handshake-failure",
145 | "failureDetails": {
146 | "failureCode": 10,
147 | "terminateConnection": true
148 | }
149 | }
150 | ```
151 |
152 | This `messageType` indicates that the handshake failed (`logui-handshake-failure`). A more specific `failureDetails` field is provided with two subfields. Refer to [later in this guide](#logui-handshake-failure) for more information on failure responses.
153 |
154 | After this response has been sent to the client, the server will close the WebSocket.
155 |
156 | ### Event Listening
157 |
158 | Event listening is the main stage in LogUI. Interactions that are requested to be logged are gathered by the LogUI client and then sent down the WebSocket connection to the LogUI server in batches. When the LogUI client is ready to send a batch of logged events, it does so with a `logui-event-payload` message.
159 |
160 | An example of this message is shown below.
161 |
162 | ```json
163 | {
164 | "messageType": "logui-event-payload",
165 | "events": [
166 | {
167 | "timestamp": "123456789",
168 | "eventName": "click"
169 | },
170 | {
171 | ...
172 | },
173 | {
174 | ...
175 | },
176 | ...
177 | ]
178 | }
179 | ```
180 |
181 | For this message, the type of the aforementioned `logui-event-payload`. The field `events` is an array of events. This array can be of variable length; it can be zero length, which denotes that no events are to be saved from this payload. Events are assumed by the LogUI server to be placed in chronological order (where the earliest event is placed first in the array).
182 |
183 | As each `event` that is logged can vary wildly, the fields that are included in each `event` entry are very much open to whatever is required. However, the LogUI server will expect at the very least the following fields to be present for each event logged.
184 |
185 | * `timestamp`, representing the UNIX timestamp (using the client's time) for when the event in question occurred.
186 | * `eventName`, a string representing the name of the event.
187 | * **TODO** - complete this list as required.
188 |
189 | Application-specific data (as provided by `applicationSpecificData`) is bound to each event on the LogUI server before it is committed to data storage.
190 |
191 | If any of the required fields listed above are missing, or some other formatting issue is present within the request, the request is counted as a [*bad request*. See a later section on this](#dealing-with-bad-requests).
192 |
193 | If the request is successful, the server will respond with a simplistic message, as shown below.
194 |
195 | ```json
196 | {
197 | "messageType": "logui-events-saved"
198 | }
199 | ```
200 |
201 | This `logui-events-saved` message is an indication that the request has been accepted, and that the events have been successfully stored. As such, the client no longer needs to retain these events in its memory.
202 |
203 | ### Updating Application-Specific Data
204 |
205 | At any stage after a successful handshake, but before the WebSocket connection is closed, the LogUI client may request to change the `applicationSpecificData` that is held for the current session. When a change is requested, all events that are logged will from that point onwards will have the updated set of `applicationSpecificData` applied.
206 |
207 | Why would one want to do this? One possible reason could be to capture a change in state within a particular session. If a user is undertaking an experiment for example, a change in `applicationSpecificData` could reflect the fact that they leave a phase providing instructions to a phase that requires them to perform some kind of activity. Providing a field in `applicationSpecificData` could make this easier to track.
208 |
209 | As the LogUI client works by sending events to the LogUI server in batch, there may be events present that the client wishes to save with the old `applicationSpecificData` scheme, *before* applying an update. To counter this, this request considers two main payloads: the updated `applicationSpecificData` fields, and a `logui-event-payload`.
210 |
211 | An example of this request is shown below.
212 |
213 | ```json
214 | {
215 | "messageType": "logui-application-specific-data-change",
216 | "applicationSpecificDataChanges": {
217 | ...,
218 | "condition": "c3",
219 | "bonus": true,
220 | "askedForHelp": null
221 | ...
222 | },
223 | "saveEventsBefore": {
224 | "messageType": "logui-event-payload",
225 | "events": [
226 | {
227 | "timestamp": "123456789",
228 | "eventName": "click"
229 | },
230 | ...
231 | ]
232 | }
233 | }
234 | ```
235 |
236 | This request outlines that the application-specific fields `condition` must be set to `c3`, and `bonus` must be set to `true`. Using the `applicationSpecificData` definition [provided earlier in this guide](#complete-logui-handshake-example), we note that `condition` was already provided (with a value of `c2`). The effect of this latter `logui-application-specific-data-change` is that the value of `condition` is **changed** from `c2` to `c3`. As `bonus` did not exist, it is **created**. Where a field existed but should now be **deleted**, the value should be set to `null`, as shown in the example above for `askedForHelp`.
237 |
238 | Setting a field's value to `null` that was not present in the application-specific data beforehand has no effect. If `applicationSpecificDataChanges` is empty, no changes to the application-specific data are made.
239 |
240 | The LogUI client should also provide a `saveEventsBefore` field as part of the `logui-application-specific-data-change` request. The expected value for this field is an encapsulated `logui-event-payload` request, complete with `messageType`. [Refer to the appropriate section for more information on what is expected here](#event-listening). To clarify, the `events` array can be empty (i.e. zero-sized), but *it must always be present in the request.* **All events presented here are saved *before* the `applicationSpecificDataChanges` are applied.**
241 |
242 | If a valid `logui-application-specific-data-change` request is made, the server will respond with a simplistic acknowledgement. This is to primarily serve notice to the LogUI client that any `events` have been successfully saved, and can be disposed of by the LogUI client.
243 |
244 | ```json
245 | {
246 | "messageType": "logui-application-specific-data-saved"
247 | }
248 | ```
249 |
250 | If for any reason the request failed, a failure response is issued.
251 |
252 | ### WebSocket Disconnection
253 |
254 | A WebSocket disconnect can occur on the server-side or the client-side. We take each scenario in turn. By far the most likely occurrence will be a client-side disconnection.
255 |
256 | #### Client-Side Disconnection
257 |
258 | Client-side disconnections can happen for four main reasons.
259 |
260 | 1. The user moves away from the page being logged by LogUI, causing the LogUI client library to be unloaded.
261 | 2. The code controlling the LogUI client programmatically instructs the LogUI client to stop.
262 | 3. The user's Internet connection is interrupted.
263 | 4. The user's browser and/or computer crashes.
264 |
265 | Not much can be done to recover from the fourth reason. For the third reason, the process is [outlined in this section](#attempting-to-reconnect). However, for the first two reasons, the LogUI client needs to send to the LogUI server any events that it has saved before unloading.
266 |
267 | In this eventuality, the LogUI client is expected to send the following request to the LogUI server.
268 |
269 | ```json
270 | {
271 | "messageType": "logui-client-shutdown",
272 | "clientShutdownTimestamp": "641143800",
273 | "saveEvents": {
274 | "messageType": "logui-event-payload",
275 | "events": [
276 | {
277 | "timestamp": "123456789",
278 | "eventName": "click"
279 | },
280 | ...
281 | ]
282 | }
283 | }
284 | ```
285 |
286 | The `logui-client-shutdown` request includes a `saveEvents` field, which is itself an encapsulated `logui-event-payload` request. Zero or more `events` can be sent with this request. The `logui-client-shutdown` request also includes a `clientShutdownTimestamp` field, the value of which is the UNIX timestamp (from the client's clock) for the point at which the request is sent.
287 |
288 | Upon the receipt of this request, the LogUI server will simply close the connection to the LogUI client. This is considered acknowledgement that the events have been successfully saved, and all loose ends on the LogUI server have been cleared up.
289 |
290 | #### Server-Side Disconnection
291 |
292 | WebSocket disconnections are initiated by the LogUI server when it is about to be shut down, or dies. Where possible, the LogUI server will send a message alerting any LogUI clients connected to it of the impending shutdown.
293 |
294 | ```json
295 | {
296 | "messageType": "logui-server-shutdown-alert"
297 | }
298 | ```
299 |
300 | This simple message is then expected to be followed up by a response from the LogUI client. As the server is about to go down, all events that the LogUI client presently has stored need to be flushed to the server. The client should respond in a timely manner with the following request.
301 |
302 | ```json
303 | {
304 | "messageType": "logui-server-shutdown-acknowledge",
305 | "clientShutdownTimestamp": "641143800",
306 | "saveEvents": {
307 | "messageType": "logui-event-payload",
308 | "events": [
309 | {
310 | "timestamp": "123456789",
311 | "eventName": "click"
312 | },
313 | ...
314 | ]
315 | }
316 | }
317 | ```
318 |
319 | This request contains a packaged version of a `logui-event-payload` request (via the `saveEvents` field), containing all of the events that the LogUI client requests to be saved. The request also includes a `clientShutdownTimestamp` field, which represents the UNIX timestamp (as per the client's clock) at the point when the request is sent. If the server is still active, the server will save the events, and respond with the following message.
320 |
321 | ```json
322 | {
323 | "messageType": "logui-server-shutdown-saved"
324 | }
325 | ```
326 |
327 | After this has been received, the LogUI client should expect the LogUI server to close the WebSocket connection.
328 |
329 | The `logui-server-shutdown-saved` message serves as confirmation to the LogUI client that the events that were passed were passed as part of the `logui-server-shutdown-acknowledge` request were successfully saved, and can be discarded by the LogUI client.
330 |
331 | In the eventuality that the WebSocket connection is closed before receiving this acknowledgement message, the LogUI client **should not assume that the events have been saved.** They should be retained by the client as it attempts to reconnect to the LogUI server.
332 |
333 | #### Attempting to Reconnect
334 |
335 | If the WebSocket connection was lost (either through the server closing the connection, or through some connection loss), the LogUI client should then continue to log events, storing them client-side temporarily. While the user continues to interact with the page being logged, the LogUI client should attempt to reconnect to the LogUI server every 30 seconds.
336 |
337 | If a WebSocket connection is re-established, the LogUI client should undertake the [handshake process](#logui-handshake) once more, taking care to include the `sessionUUID` field within the handshake to ensure continuity with the session being tracked. Immediately after a successful handshake, the LogUI client should send a `logui-event-payload` request to the LogUI server, flushing the buildup of logged events on the client. After this has been acknowledged by the LogUI server, normality can resume.
338 |
339 | As mentioned, the LogUI client will continue to log events while the LogUI server is down. However, there is obviously a limit to how much can be stored in the client's memory. This will be measured by the LogUI client, and if a sensible limit is reached before reconnecting to the LogUI server, the LogUI client will have to shut down.
340 |
341 | If reconnection to the LogUI server cannot be made before the user leaves the page that they are on, the saved event data is unfortunately lost as there is no way to save it.
342 |
343 | *When the browser is closed, all context is lost. If the user closes their browser tab or window, capturing the data when offline becomes an impossible task.*
344 |
345 | ## Dealing with Bad Requests
346 |
347 | If the LogUI client sends a request that the LogUI server does not understand, this is considered to be a **bad request**. A bad request can only occur in the **Event Listening** or **WebSocket Disconnection** stages of the LogUI protocol. If a bad request is sent during the LogUI Handshake stage, this is considered to be a `logui-handshake-failure`. Otherwise, the `messageType` will be a `logui-bad-request`.
348 |
349 | Refer to the [following section](#logui-failure-responses) for the possible error messages that can be sent back to the LogUI client in the case of failure.
350 |
351 | A total of five bad requests can be made before the server disconnects the LogUI client. After the fifth bad request is made, the LogUI server simply closes the WebSocket connection without any acknowledgement.
352 |
353 | ## LogUI Failure Responses
354 |
355 | If a request sent to the LogUI server results in some kind of failure, the specific failure type is sent back via `messageType`, with details specific to the failure reported in the `errorDetails` field. This contains at least two subfields:
356 |
357 | - `errorCode`, reporting a specific failure code (unique to the type of failure); and
358 | - `terminateConnection`, a boolean indicating whether the failure is severe enough to warrant a closing of the WebSocket.
359 |
360 | Some failures cannot be recovered from. For example, a `logui-handshake-failure` denotes that the handshake failed. With the authentication process not complete, the LogUI server will not take loggable events. Thus, the WebSocket connection is no longer required. See the example below.
361 |
362 | ```json
363 | {
364 | "messageType": "logui-handshake-failure",
365 | "failureDetails": {
366 | "failureCode": 10,
367 | "terminateConnection": true
368 | }
369 | }
370 | ```
371 |
372 | Some failures can be recovered from! In these scenarios, the server will respond with `terminateConnection` to `false`. If set to `true`, the server will be expected to close the WebSocket connection. The WebSocket connection should only be closed from the LogUI client side when:
373 |
374 | 1) the user instructs their browser to navigate to a different page, thus forcing the LogUI client library to close; or
375 | 2) the developer integrating the LogUI library instructs it to close (via the LogUI client API).
376 |
377 | In some failure scenarios, additional metadata about the failure may be present in the `failureDetails` field. However, one can be assured that the `failureCode` and `terminateConnection` fields will be present in all failure scenarios.
378 |
379 | The following subsections report the possible `failureCode` values possible for each failure type.
380 |
381 | ### `logui-handshake-failure`
382 |
383 | With this type of failure, the LogUI handshake was not successful. In all scenarios, the WebSocket connection will be terminated. Failure codes `100` through `109` are devoted to this failure type.
384 |
385 | - **Code `100` (Generic)**
386 | A generic failure code for `logui-handshake-failure`. Not documented here.
387 |
388 | - **Code `101` (Badly Formatted)**
389 | One or more fields missing from the handshake request.
390 |
391 | - **Code `102` (Bad `applicationIdentifier` String)**
392 | An invalid `applicationIdentifier` string was supplied. Decryption failure.
393 |
394 | - **Code `103` (Unknown Application or Flight ID)**
395 | An unknown `applicationID` or `flightID` were supplied. One did not match against the database.
396 |
397 | - **Code `104` (Version Mismatch)**
398 | A mismatched version was supplied from the `applicationIdentifier` string. A version of the LogUI Client library is being used that does not match with what is expected.
399 |
400 | - **Code `105` (Unsupported Client Version)**
401 | An unsupported LogUI client version is being used with the LogUI server endpoint.
402 |
403 | ### `logui-bad-request`
404 |
405 | A bad request encompasses all eventualities after the handshake stage of the protocol. Failure codes `200` to `209` are devoted to this failure type.
406 |
407 | - **Code `200` (Generic)**
408 | A generic failure code for requests that take place after the handshake stage. Not documented here.
409 |
410 | - **Code `201` (`logui-event-payload` Badly Formed)**
411 | The `logui-event-payload` request is badly formed. Either bad JSON was supplied, or one or more required fields were missing.
412 |
413 | - **Code `202` (`logui-event-payload` Missing Field)**
414 | One of the `logui-event-payload` event fields were missing. Ensure that the required fields are present for each field.
415 |
416 | - **Code `203` (`logui-application-specific-data-change` Badly Formed)**
417 | The `logui-application-specific-data-change` request is badly formed. The request contained badly formed JSON, or was missing one or more required fields.
418 |
419 | ### `logui-server-failure`
420 |
421 | A LogUI server failure will almost always entail that the WebSocket connection will be closed. Failure codes `300` to `309` are devoted to this failure type.
422 |
423 | - **Code `300` (Generic)**
424 | A generic LogUI server failure code for events that are not captured by their own identifying failure code.
425 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LogUI Client
2 |
3 | **Welcome to LogUI!** *LogUI* is a powerful, framework-agnostic client-side JavaScript library that can be used for logging interactions that take place on a webpage. Primarily designed for *Interactive Information Retrieval (IIR)* experiments, LogUI can in theory be used on any page or site that you wish to track fine-grained user interactions with UI components.
4 |
5 | Use the LogUI client in tandem with the LogUI server. You can find the LogUI server living at [this repository](https://github.com/logui-framework/server/).
6 |
7 | ## About LogUI
8 |
9 | The LogUI library is implemented by [Dr David Maxwell](https://github.com/maxwelld90/), a postdoctoral researcher at [TUDelft](https://www.tudelft.nl/) in the Netherlands. It has been developed in the Lambda Lab, headed by [Dr Claudia Hauff](https://chauff.github.io/). The library is borne out of the need for infrastructure that allows one to undertake the logging of user interactions in a consistent way, rather than the piecemeal approach that we've seen in IIR experimentation.
10 |
11 | We think that a one-size-fits-all logging library is just the ticket for your experiments!
12 |
13 | ## Using LogUI in Experiments?
14 |
15 | We're thrilled that you're using LogUI in your experiments! We ask that in return you provide due credit for this work. If you have a paper associated with your experiment, please do cite the associated demonstration paper that was published at [ECIR 2021](https://www.ecir2021.eu/). You can find the BibTeX source for the paper below.
16 |
17 | ```bibtex
18 | @inproceedings{maxwell2021logui,
19 | author = {Maxwell, David and Hauff, Claudia},
20 | title ="{LogUI: Contemporary Logging Infrastructure for Web-Based Experiments}",
21 | booktitle = {Advances in Information Retrieval (Proc. ECIR)},
22 | year = {2021},
23 | pages = {525--530},
24 | }
25 | ```
26 |
27 | ## Documentation and Quick Start Guide
28 |
29 | For documentation on the LogUI client library, please go and check the corresponding Wiki associated with this repository. There, you'll find detailed information about how to [acquire yourself a copy](https://github.com/logui-framework/client/wiki/Acquiring), how to [set the client library up](https://github.com/logui-framework/client/wiki/Quick-Start-Guide), how to integrate it with your existing application's code, and information which should allow you to gain a better understanding as to the thinking behind the library's implementation.
30 |
31 | ## Tests
32 |
33 | Tests are being developed for the LogUI client library and will be available in this repository soon.
34 |
35 | ## Found a Bug or have a Feature Request?
36 |
37 | It would be great to hear from you! Please [raise an issue in this repository](https://github.com/logui-framework/client/issues) and we can discuss what options that can be pursued to resolve it.
--------------------------------------------------------------------------------
/build/env/controller.js:
--------------------------------------------------------------------------------
1 | var LogUITestEnvDriver = (function(root) {
2 | var _public = {};
3 | var initTimestamp = null;
4 | var detectReference = null;
5 |
6 | _public.$ = root.document.querySelector.bind(root.document);
7 | _public.$$ = root.document.querySelectorAll.bind(root.document);
8 |
9 | const CONSOLE_LIST_ELEMENT = _public.$('#console-list');
10 |
11 | const STATUS_MESSAGES = {
12 | 'unloaded': 'LogUI unloaded',
13 | 'inactive': 'LogUI loaded; inactive',
14 | 'starting': 'LogUI loaded; starting...',
15 | 'stopping': 'LogUI loaded; stopping...',
16 | 'active': 'LogUI loaded; active'
17 | }
18 |
19 | _public.init = function() {
20 | initTimestamp = new Date();
21 | _public.clearConsole();
22 | _public.addEnvMessage('Initialising test environment');
23 | _public.addEnvMessage('Messages with a blue background (like this) are from the test environment.');
24 | setStatus('unloaded');
25 |
26 | detectReference = window.setInterval(detectLogUI, 500);
27 | bindButtonListeners();
28 | };
29 |
30 | _public.addEnvMessage = function(msg) {
31 | let newNode = document.createElement('li');
32 | let textNode = document.createTextNode(msg);
33 | newNode.appendChild(textNode);
34 | newNode.classList.add('env');
35 |
36 | CONSOLE_LIST_ELEMENT.insertBefore(newNode, CONSOLE_LIST_ELEMENT.firstChild);
37 | };
38 |
39 | _public.clearConsole = function() {
40 | CONSOLE_LIST_ELEMENT.innerHTML = '';
41 | };
42 |
43 | function bindButtonListeners() {
44 | _public.$('#control-clear').addEventListener('click', function() {
45 | _public.clearConsole();
46 | });
47 |
48 | _public.$('#control-start').addEventListener('click', function() {
49 | _public.addEnvMessage('Starting LogUI');
50 | setStatus('starting');
51 | _public.$('#control-start').disabled = true;
52 |
53 | window.LogUI.init(window.config)
54 | .catch(error => {throw Error(error)});
55 | });
56 |
57 | _public.$('#control-stop').addEventListener('click', function() {
58 | _public.addEnvMessage('Stopping LogUI');
59 | setStatus('stopping');
60 |
61 | _public.$('#control-stop').disabled = true;
62 |
63 | window.LogUI.stop().then(function(resolved) {
64 | _public.$('#control-start').disabled = false;
65 | setStatus('inactive');
66 | });
67 | });
68 |
69 | root.addEventListener('logUIStarted', function() {
70 | if (window.LogUI.isActive()) {
71 | _public.$('#control-stop').disabled = false;
72 | setStatus('active');
73 | _public.addEnvMessage('LogUI started; listening for events');
74 | }
75 | });
76 |
77 | // This listener is bound to demonstrate its functionality.
78 | // We could equally put this code in the .stop().then() call above.
79 | root.addEventListener('logUIStopped', function() {
80 | _public.addEnvMessage('LogUI stopped');
81 |
82 | _public.$('#control-start').disabled = false;
83 | _public.$('#control-stop').disabled = true;
84 | });
85 | };
86 |
87 | function setStatus(statusKey) {
88 | _public.$('#control-status').innerText = STATUS_MESSAGES[statusKey];
89 |
90 | if (statusKey == 'inactive') {
91 | _public.$('#control-version').style.display = 'inline';
92 | _public.$('#control-version').innerText = `Version ${LogUI.buildVersion}`;
93 | }
94 | };
95 |
96 | function detectLogUI() {
97 | if (window.LogUI) {
98 | window.clearInterval(detectReference);
99 | setStatus('inactive');
100 | _public.$('#control-start').disabled = false;
101 | }
102 | };
103 |
104 | return _public;
105 | })(window);
106 |
107 | document.addEventListener('DOMContentLoaded', function() {
108 | LogUITestEnvDriver.init();
109 |
110 | LogUITestEnvDriver.$('#test-dommanipulation-button').addEventListener('click', function() {
111 | if (this.innerHTML.includes('add')) {
112 | var element1 = document.createElement('div');
113 | element1.appendChild(document.createTextNode('No binding'))
114 | element1.id = 'test-dommanipulation-box1';
115 | element1.classList.add('test');
116 |
117 | var element2 = document.createElement('div');
118 | element2.appendChild(document.createTextNode('Hover and click binding'))
119 | element2.id = 'test-dommanipulation-box2';
120 | element2.classList.add('test');
121 |
122 | LogUITestEnvDriver.$('#test-dommanipulation-newcontainer').appendChild(element1);
123 | LogUITestEnvDriver.$('#test-dommanipulation-newcontainer').appendChild(element2);
124 |
125 | this.innerHTML = 'Click to destroy elements';
126 |
127 | return;
128 | }
129 |
130 | this.innerHTML = 'Click to add two new elements';
131 | LogUITestEnvDriver.$('#test-dommanipulation-newcontainer').innerHTML = '';
132 | });
133 | });
--------------------------------------------------------------------------------
/build/env/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
This is a simple LogUI test environment. Use this with a build of LogUI and the console dispatcher (npm run -s build:console) to have LogUI drive the console at the bottom of the screen.
30 |
Control LogUI with the buttons above, and interact with the sample elements below. Interact with the page, too. Try resizing the viewport, or scrolling with your mouse wheel or trackpad. Watch the corresponding log events appear in the console.
31 |
Note that the events that appear in the console below are the log events that would otherwise be directed to the LogUI server. Check out the browser's console for verbose output on what is going on in terms of initialisation (if verbose: true).
32 |
33 |
34 |
35 |
36 |
37 |
Basic Hover/Click Events
38 |
39 |
Hover!
40 |
41 |
Nested
42 |
43 |
44 |
45 |
46 |
Dynamic DOM Manipulation
47 |
48 |
49 |
50 |
51 |
52 |
53 |
Basic React Application
54 |
55 | The app is below the horizontal rule.
56 |
57 |
102 | This is a scrollable element. You should be able to scroll along the y axis to see more content. Look at the interaction logs to see the scroll events that are recorded. We should see a start and stop scroll event; not a multitude of scroll events that are recorded.
103 |