Sockets

There might be rare occasions where you need to extend the existing socket interface; e.g. after adding a new database table to CARE and you want to make this data available to the frontend. In this chapter we outline the steps necessary for either extending an existing socket or creating an entirely new one.

Note

Please also see the code conventions for the socket interface in Code Conventions

Sockets in CARE

The socket architecture is realized in the backend; in the following all paths are provided relative to the directory backend/webserver including all relevant components.

During start-up of the webserver all sockets specified in the sockets directory that extends the Socket base class are loaded. Each socket is specified in an individual .js-file exporting a class that extends the Socket base class. Hence, adding a socket means creating such a new file and overriding the abstract class.

This base class (specified in Socket.js) defines several convenience methods to communicate with the frontend, as well as attributes and methods to interact with the web server class starting the respective socket. In the following sections we elaborate which methods and attributes you should use or override to realize your own service.

Conceptually, a socket class encapsulates a socket-io connection established by the webserver, which you can use to listen to certain messages and which you can use to send back messages. For each frontend client that connects to the webserver, all socket classes are instantiated anew having an associated client id. This means, all messages you send and receive via the socket attribute are always specific to this one client connection; in other words, there is no state transfer between different connections or broadcasting possible. However, some of this functionality is made available through the server object – we address these options in the Extending a Socket section below.

Creating a Socket

Let’s say we want to add a new socket called TestSocket to CARE. First, we add a new file test.js to the backend/webserver/sockets directory that exports the new socket class:

const Socket = require("../Socket.js");
/**
 * <doc>
 */
module.exports = class TestSocket extends Socket {
    constructor(server, io, socket) {
        super(server, io, socket);
    }
}

While this would already add the socket automatically to CARE on restart of the webserver, you should always override the init() method of the base class. Here, you will add the message listeners; for now, we will just log that the socket has been initialized.

/**
 * HEADER BOILERPLATE...
 */
module.exports = class TestSocket extends Socket {
    constructor(server, io, socket) {
        super(server, io, socket);
    }

     init() {
        this.logger.debug(`TestSocket was created for client ${this.socket.id}`);
     }
}

In the next section we elaborate on how to populate a socket by message listeners and response functionality.

Extending a Socket

Let’s assume we want to extend a socket TestSocket defined in ./backend/webserver/sockets/test.js. Generally, socket logic should be kept lean and easy. The primary responsibility of a socket is to forward a new client request to another module for processing (e.g. the database interface), handle possible errors and send the results back to the client. In the following we explain the possible use-cases for extending a socket

Listening to a New Message Type

Let’s say we want to listen to new message types testIncrement and testReset that provides a data object parameters. For the sake of brevity we do not interact with the database, but write to a class attribute.

/**
 * HEADER BOILERPLATE...
 */
module.exports = class TestSocket extends Socket {
    constructor(server, io, socket) {
        super(server, io, socket);

        this.testVar = 0;
    }

    /* ... */

    // method for updating the variable
    updateTestVar(newVal){
        this.testVar = newVal;
        return this.testVar;
    }

    init() {
        /* ... */

        // listen to testIncrement
        this.socket.on("testIncrement", (data) => {
            try {
                this.socket.emit("testResult", {success: true, val: this.updateTestVar(this.testVar + data.inc)});
            } catch (e) {
                this.logger.error(e);
            }
        });

        // listen to testReset
        this.socket.on("testReset", () => {
            try {
                this.socket.emit("testResult", {success: true, val: this.updateTestVar(0)});
            } catch (e) {
                this.logger.error(e);
            }
        });
    }
}

Let’s decompose the steps to realize this example. First, we extend the init() method adding event listeners on the socket using the on(msg, callback) function of the socket.io client. We then call the class method that allows us to modify the state of the test variable (this.updateTestVar(...)). Finally, we return the resulting value to the client via emit(msg, data) with the resulting value.

Error and Rights Management

When interacting with the database the key challenge is error handling, marshalling (i.e. the translation of the DB data representation into a suitable format for the frontend) and rights management. For now, we assume that we want to call an already defined database a model Test specified in ./backend/db/models/test.js and integrate this call into the above example. Let’s also assume that only administrators are allowed to change this value.

Note

Please refer to the guide on how to extend the database and add interface methods in Database.

/**
 * HEADER BOILERPLATE...
 */
module.exports = class TestSocket extends Socket {
    /* ... */

    // method for updating the variable
    async updateTestVar(newVal){
        // use base class method to check for admin rights
        if (this.isAdmin()) {
            try {
                const result = await this.models["test"].updateById("x", {val: newVal});
                this.socket.emit("testResult", {success: true, val: result});
            } catch (e) {
                this.socket.emit("testResult", {success: false, message: "Failed to update test!"});
                this.logger.error("DB error while updating test" + JSON.stringify(e));
            }
        } else {
            // respond with a negative message
            this.socket.emit("testResult", {success: false, message: "User rights and argument mismatch"});
        }
    }

    init() {
        /* ... */

        // listen to testIncrement
        this.socket.on("testIncrement", async (data) => {
            try {
                await this.updateTestVar(this.testVar + data.inc);
            } catch (e) {
                this.logger.error(e);
            }
        });

        // listen to testReset
        this.socket.on("testReset", async () => {
            try {
                await this.updateTestVar(0);
            } catch (e) {
                this.logger.error(e);
            }
        }
    }
}

Let’s decompose the example again! We first update the updateTestVar to use the dbUpdateTest function interfacing the database. Before making the actual call to the database, we check whether the user is an admin through the base class isAdmin() method accessing the backend user information associated with the current connection. Because the database query might fail, we add a try-catch-block around it. In case of an error at any stage, we send a negative testResult back to the client.

Note

It is crucial that you do full error handling on the socket level, i.e. at some point all exceptions should have been caught by a catch block. Otherwise, the webserver can crash due to minor errors during database interaction.

Broadcasting Responses

To realize collaboration features, it might be desirable to join clients into rooms and multicast messages to these groups. In exceptional cases, even a broadcast might make sense. You should use these two options very sparingly, because they imply a lot of network traffic.

To join a client to a socketio room, you can simply use the join(name) function on the socket object:

this.socket.join("roomName");

To multicast a message to a room, you need to access the io object of the base class.

this.io.to("roomName").emit("msg", data);

Testing

Please think about how to test your socket. In general, you should test the functionality in isolation. We refer to the section on Tests for more information on how to test your code.

Socket Communication Schema

In order to streamline socket communication, especially when interacting with the database, CARE defines a standardized way to register and handle socket events using the createSocket method provided by ./backend/webserver/Socket.js. This schema helps to ensure consistent error handling, optional transaction safety, and frontend compatibility.

createSocket: How it works

The createSocket method allows you to define a socket event with built-in support for:

  • Error handling

  • Optional transaction wrapping

  • Automatic frontend success/failure callbacks

  • Clean backend-to-frontend event emission after database changes

createSocket(eventName, func, options = {}, transaction = false)
Arguments:
  • eventName: Name of the socket event to register

  • func: Async function handling the request, with parameters (data, options)

  • options: Object passed through the socket pipeline; can contain any contextual data (e.g., transaction info, user session, metadata)

  • transaction: If true, wraps execution in a Sequelize DB transaction

Warning

If your handler function performs any database write operations (e.g., create, update), you must set transaction = true and use the provided transaction object inside the function. Failing to do so can result in inconsistent or partial data states.

Note

The createSocket function should always be called within the init() method of socket classes located in ./backend/webserver/sockets/*. The handler function passed to it should be a method of the class itself, not an inline function or a function defined inside init().

See a full example of the usage of createSocket in Example Lifecycle.

Transaction Usage

If your socket operation modifies data, set transaction = true. This will:
  • Open a Sequelize transaction

  • Pass it to your function via options.transaction

  • Automatically commit/rollback

  • Allow you to define afterCommit() behavior

init() {
    this.createSocket("studySaveAsTemplate", this.saveStudyAsTemplate, {}, true);
    }
/**
* Save the current study as a template (creates a new study with template: true).
*
* @param {object} data - The input data from the socket call.
* @param {number} data.id - ID of the study to duplicate as a template.
* @param {object} options - Context passed through the socket pipeline.
* @returns {Promise<object>} The newly created template study.
*/
async saveStudyAsTemplate(data, options) {
    const study = await this.models["study"].getById(data.id);

    if (this.checkUserAccess(study.userId)) {
        const template = await this.models["study"].add({
            ...study,
            id: undefined,
            hash: undefined,
            template: true,
        }, {transaction: options.transaction});

        // Code executed after transaction commit success (code only, without further DB changes!)
        options.transaction.afterCommit(() => {
            this.emit("studyRefresh", template);
        });

        return template; // The value sent back to the frontend via the socket callback
    } else {
        throw new Error("No permission to save study as template");
    }
}

Note

  • Socket handler functions should always include clear JSDoc-style docstrings describing the expected data parameters, transaction context, and return type. For full documentation standards, refer to: Conventions and Paradigms.

  • For a detailed explanation of how to notify the frontend after a transaction, including automatic tracking (via autoTable) and manual afterCommit hooks, see: Tracking DB Changes (afterCommit).

See a further example of the transaction usage in Example Lifecycle.

Transaction Failure Handling

If an error is thrown inside a socket using transaction = true, the transaction will be automatically rolled back.

Warning

Do not call transaction.commit() or transaction.rollback() manually when using createSocket. These are handled automatically, and calling them yourself can lead to runtime errors — they can only be called once per transaction.

Try-Catch Behavior

Using createSocket wraps your function call in an automatic try-catch block. If an error is thrown:
  • The transaction is rolled back (if enabled)

  • A callback with success: false and the error message is sent to the frontend

See the section above on createSocket: How it works for more details.

No need to write:

try {
    ...
} catch (e) {
    ...
}

Just throw an error inside your function:

if (!this.checkUserAccess(...)) {
    throw new Error("Access denied");
}

Socket Callback Responses

When a socket event completes, a callback function on the frontend receives a standardized response object:

{
    success: true,        // true if the backend handler completed without errors
    data: {...},          // the return value from the backend function (if successful)
    message: "..."        // an optional error message if an error was thrown
}

This structure is automatically handled by createSocket:

  • If the backend handler throws an error, success is set to false and message contains the error.

  • If the function returns a value, it is included as data and success is set to true.

The following example uses CARE’s global toast system to display success or failure. For full documentation on toast messages, see Toast Messages.

this.$socket.emit("studySaveAsTemplate", {id: 1}, (result) => {
    if (!result.success) {
        this.eventBus.emit("toast", {
            title: "Template Save Failed",
            message: result.message,
            variant: "danger",
        });
    } else {
        this.eventBus.emit("toast", {
            title: "Success",
            message: "Study saved as template",
            variant: "success",
        });
    }
});

Tracking DB Changes (afterCommit)

In most cases, when using models with autoTable = true, CARE will automatically track database changes and push updates directly to the frontend. This includes creation, updates, and deletions — no need to manually emit a ...Refresh event.

However, in special cases where autoTable is not used or doesn’t apply, you may need to manually notify the frontend after a successful transaction. Examples include:

  • Writing files to disk

  • Returning plain objects or custom responses

  • Using models that do not have autoTable = true

In these cases, you can register a manual afterCommit hook on the transaction object:

const doc = await this.models["document"].add({
    name: data.name,
    type: data.type,
    userId: this.userId
}, {transaction: options.transaction});

fs.writeFileSync(target, data.file);

options.transaction.afterCommit(() => {
    this.emit("documentRefresh", doc);
});

Warning

In normal use cases (e.g., with autoTable = true), you do not need to use `afterCommit()` or manually emit refresh events. CARE tracks database changes automatically and handles frontend updates for you.

Example Lifecycle

Here is a full example showing how the backend and frontend work together when using createSocket for creating a new document.

Frontend: emit event and handle result with a toast

this.$socket.emit("documentCreate", {
  type: 1,
  name: this.name,
}, (res) => {
  if (res.success) {
    this.$refs.createModal.close();
    this.eventBus.emit("toast", {
    title: "Success",
    message: res.data, // This will be: "Document successfully created"
    variant: "success",
    });
  } else {
    this.$refs.createModal.waiting = false;
    this.eventBus.emit("toast", {
    title: "Error",
    message: res.message, // This will be: "Missing required fields: name and type"
    variant: "danger",
    });
  }
});

Backend: register the socket

init() {
    this.createSocket("documentCreate", this.createDocument, {}, true);
    }

Backend: handler function implementation

/**
 * Create document (HTML)
 * @param data {type: number, name: string}
 * @param options
 * @returns {Promise<object>}
 */
async createDocument(data, options) {
    const doc = await this.models["document"].add({
        name: data.name,
        type: data.type,
        userId: this.userId
    }, {transaction: options.transaction});

    options.transaction.afterCommit(() => {
        // Backend log entry instead of manual frontend emit; frontend is auto-updated via autoTable
        this.logger.info(`Document created (id: ${doc.id}) by user ${this.userId}`);
    });

    return "Document successfully created";
}

What happens internally:

  1. createSocket listens for “documentCreate” on the socket

  2. When the event is triggered from the frontend:

    • The handler createDocument runs

    • A Sequelize transaction is opened and passed as options.transaction

  3. If the function completes without error:

    • The transaction is committed

    • The afterCommit hook logs a backend info message (no manual frontend emit is needed; updates are handled automatically via autoTable)

    • The frontend receives { success: true, data: "Document successfully created" }

    • A success toast is displayed using that message

  4. If an error is thrown:

    • The transaction is rolled back

    • The frontend receives { success: false, message: "..." } with the error message

    • An error toast is displayed using that message

Socket functions

Annotation Socket Functions (annotation.js)

class AnnotationSocket()

Handle all annotation through websocket

AnnotationSocket.getAnnotationsByDoc(data, options)

Returns the annotations for a given document by its id.

Arguments:
  • data – the input

  • data.documentId (number) – the id of the document to retrieve the annotations for

  • options – not used

Returns:

Promise.<void>

AnnotationSocket.loadCommentsByAnnotation(annotationId)

Load all comments for a document by annotation

Arguments:
  • annotationId (number)

Returns:

Promise.<void>

AnnotationSocket.sendAnnotation(data, options)

Send an annotation to the client by id

Arguments:
  • data (Object) – the input data from the frontend

  • options (Object) – not used

  • data.annotationId (number) – the id of the annotation

Returns:

Promise.<*>

AnnotationSocket.updateAnnotation(data, options)

Updates the annotations in the database. If the provided annotation is a new annotation, it will be created in the database otherwise the existing entry is overriden.

Arguments:
  • data (Object) – the input data from the frontend

  • options (Object) – containing transactions

  • options.transaction – the DB transaction

  • data.annotationId (number) – the id of the annotation to update

  • data.documentId (number) – the id of the document to update

  • data.studySessionId (number) – the id of the study session

  • data.studyStepId (number) – the id of the study step

  • data.tagId (number) – the id of the tag

  • data.selectors (string) – the selectors of the annotation

  • data.deleted (boolean) – indicates if the data is deleted

  • data.anonymous (boolean) – indicates if the data is anonymous

Returns:

Promise.<void>

App Socket Functions (app.js)

class AppSocket()

Send data for building the frontend app

AppSocket.sendData(data)

Sends the data stored under data.table.

Arguments:
  • data (Object) – The input data from the frontend

  • data.table (String) – Table to send

  • data.filter (Object) – Filters

  • data.include (Object) – what data to include

Returns:

Promise.<void>

AppSocket.sendDataByHash(data, options)

Send data by hash

Arguments:
  • data (Object) – The input data from the frontend

  • data.hash (String) – The hash value

  • data.table (String) – Table to send the data from

  • options (Object) – Additional configuration parameter

Returns:

Promise.<void>

AppSocket.sendInit(data, options)

Send all data needed for the frontend app for initialization

Arguments:
  • data (Object) – The input data from the frontend

  • options (Object) – Sequelize transaction options

Returns:

Promise.<void>

AppSocket.sendOverallSetting(data, options)

Send overall settings including user setting

Arguments:
  • data (Object) – The input data from the frontend

  • data.key (String) – The key in the user setting table

  • data.value (String) – The value in the user setting table

  • options (Object) – Additional configuration parameter

Returns:

Promise.<void>

AppSocket.sendSettings(sendToAll=false)

Send all settings to the client

Arguments:
  • sendToAll (boolean) – broadcast to all clients

Returns:

Promise.<void>

AppSocket.sendSystemRoles()

Sends all the roles CARE has from the DB.

Returns:

Promise.<void>

AppSocket.sendTables()

Send tables data to the client for automatic table generation

Returns:

Promise.<void>

AppSocket.sendUser()

Sends the user information for this user loaded from the db.

Returns:

Promise.<void>

AppSocket.subscribeAppData(data)

Subscribe to app data

Arguments:
  • data (Object) – The input data from the frontend

  • data.table (String) – The name of the table to subscribe to

Throws:

Error

  • If data.table is not provided

Returns:

Promise.<void>

AppSocket.unsubscribeAppData(data, options)

Unsubscribe from app data

Arguments:
  • data (Object) – The input data from the frontend

  • options (Object) – Additional configuration parameter

Returns:

Promise.<void>

AppSocket.updateAppData(data, options)

Update data for a specific table to the client

Arguments:
  • data (Object) – The input data from the frontend

  • options (Object) – Additional configuration parameter

  • options.transaction (Object) – Sequelize DB transaction options

Returns:

Promise.<*>

AppSocket.updateData(data, options)

Updates data in the database

Arguments:
  • data (Object) – The input data from the frontend

  • data.table (String) – The name of the table to update

  • data.data (Object) – New data to update

  • options (Object) – Additional configuration parameter

  • options.transaction (Object) – Sequelize DB transaction options

Returns:

Promise.<void>

Assignment Socket Functions (assignment.js)

class AssignmentSocket()

Handle user through websocket

AssignmentSocket.addReviewer(data, options)

Adds new sessions to a study.

Arguments:
  • data (Object) – The data for adding reviewers.

  • data.studyId (number) – The ID of the study to which reviewers are to be added.

  • data.reviewer (Array.<number>) – An array of user IDs representing the reviewers to be added.

  • options (Object) – The options for transaction data

Returns:

Promise.<void> – - A promise that resolves when the reviewers have been added to the study.

AssignmentSocket.createAssignment(data, options)

Assigns a peer review task to a list of reviewers based on a given template.

Arguments:
  • data (Object) – The data for assigning peer reviews.

  • data.assignment (Object) – The assignment object containing details of the assignment.

  • data.reviewers (Array) – An array of reviewer IDs who will be assigned to the peer review.

  • data.template (Object) – The template object containing the configuration for the peer review.

  • data.documents (Array) – The documents to be reviewed.

  • options (Object) – The option for transaction data.

Throws:

Error Will throw an error if the assignment cannot be created.

Returns:

Promise.<void> – A promise that resolves when the peer review has been assigned.

AssignmentSocket.createAssignmentBulk(data, options)

Creates multiple assignments based on the provided data.

Arguments:
  • data – The data for creating assignments.

  • data.template (Object) – The template to be used for the assignments.

  • data.selectedReviewer (Array.<Object>) – An array of reviewer objects to be assigned to the assignments.

  • data.selectedAssignments (Array.<Object>) – An array of assignment objects to be reviewed.

  • data.mode (String) – The mode of the assignment creation (i.e, role or reviewer)

  • data.documents (Array.<Array>) – List of document assignments

  • data.roleSelection (Object) – If the mode is role, the role selection object

  • data.reviewerSelection (Object) – If the mode is reviewer, the reviewer selection object

Returns:

Promise.<void>

AssignmentSocket.getAssignmentInfoFromCourse(data)

Retrieve all the assignments a course has.

Arguments:
  • data (Object) – The data required for getting the relevant assignment info.

  • data.options (Object) – The options object containing the API key and URL of the Moodle instance.

  • data.options.courseID (number) – The ID of the course to fetch users from.

  • data.options.apiKey (string) – The API token for the Moodle instance

  • data.options.apiUrl (string) – The URL of the Moodle instance.

Returns:

Promise.<ArrayLike.<T>>

Collab Socket Functions (collab.js)

class CollabSocket()

Handle collaboration through sockets

CollabSocket.updateCollab(data, options)

Updates the collaboration status in the database. If there is non existent on the given entity, it will create one, otherwise it will be updated.

Arguments:
  • data (Object) – the input collab object

  • data.collabId (number) – the id of the collaboration if existent

  • options (Object) – containing the transaction

Returns:

Promise.<void>

Comment Socket Functions (comment.js)

class CommentSocket()

Loading the comments through websocket

CommentSocket.addComment(data, options)

Add a comment

Arguments:
  • data (object) – comment object

  • options (object) – contains the transactions

Returns:

Promise.<void>

CommentSocket.addOrUpdateComment(data, options)

Add or update a comment if it has already existed

Arguments:
  • data (object) – The input data from the frontend

  • data.commentId (number) – The id of the comment

  • options (object) – holds the transaction

Returns:

Promise.<void>

CommentSocket.getCommentsByDocument(data, options)

Get all the comments of a certain document

Arguments:
  • data (object) – The input data from the frontend

  • data.documentId (number) – The id of the document

  • options (object) – not used

Returns:

Promise.<void>

CommentSocket.sendComment(data, options)

Send a comment to the client

Arguments:
  • data (object) – The input data from the frontend

  • data.commentId (number) – The id of the comment

  • options (object) – not used

Returns:

Promise.<void>

CommentSocket.updateComment(data, options)

Update comments

Arguments:
  • data (Object) – containing the inputs

  • data.commentId (number) – the id of the comment

  • data.comment (object) – the comment object

  • options (object) – containing transaction

Returns:

Promise.<void>

Document Socket Functions (document.js)

class DocumentSocket()

Handle all document through websocket

Loading the document through websocket

DocumentSocket.addDocument(data, options)

Uploads the given data object as a document. Stores the given pdf file in the files path and creates an entry in the database.

Arguments:
  • data (Object) – The data object containing the document details.

  • data.name (string) – The name of the document.

  • data.file (Buffer) – The binary content of the document.

  • data.userId (number) – The ID of the user who owns the document (optional).

  • data.isUploaded (boolean) – Indicates if the document is uploaded by an admin (optional).

  • options (Object) – The options object containing the transaction.

Returns:

Promise.<void>

DocumentSocket.checkDocumentAccess(documentId)

Check if user has rights to read the document data

The user has access to the document if: - The document is public - The document is owned by the user - The user is an admin - The document is used in a study where the user is a participant

Returns:

Promise.<boolean>

DocumentSocket.closeDocument(data, options)

Close the document and save it if necessary.

This method saves the document if there is no study session and removes the document from the list of open documents.

Arguments:
  • data (object) – The data object containing documentId and studySessionId.

  • data.documentId (number) – The ID of the document to close.

  • data.studySessionId (number) – The ID of the study session,

  • options (object) – The options object.

Returns:

Promise.<void>

DocumentSocket.createDocument(data, options)

Create document (html)

Returns:

Promise.<void>

DocumentSocket.documentGetMoodleSubmissions(data, options)

Get Moodle submissions from an assignment

Returns:

Promise.<ArrayLike.<T>>

DocumentSocket.editDocument(data, options)

Edits the document based on the provided data.

This method is called when the client requests to edit a document. It first checks if the user has access to the document, and if so, it applies the edits to the document and sends the updated document to the client.

Arguments:
  • data (object) – {documentId: number, “ops” array consisting of [offset: number, operationType: number, span: number, text: string, attributes: Object]}

  • options (object) – the options for the transaction

Returns:

Promise.<void>

DocumentSocket.getData(data)

Send document data to client And send additional data like annotations, comments, tags

Arguments:
  • data (object) – {documentId: number, studySessionId: number}

Returns:

Promise.<void>

DocumentSocket.getDocument(data, options)

Send a document to the client

This method checks if the user has access to the document and then retrieves and sends the document data. For HTML documents, it fetches and combines draft edits with the existing content before sending.

Returns:

Promise.<({document: Document, deltas: Delta}|{document: Document, file: Buffer})>

DocumentSocket.getPreviousStepId(studyStepId)

Helper method to get the previous step ID for a given study step ID

Arguments:
  • studyStepId (number) – The ID of the study step

Returns:

Promise.<(number|null)> – - The ID of the previous study step, or null if not found

DocumentSocket.loadDocument(filePath)

Load document delta from disk (for HTML documents)

This method reads the delta file from the disk and returns it as a Delta object.

Arguments:
  • filePath (string)

Returns:

Promise.<Delta>

DocumentSocket.openDocument(data, documentId, options)

Open the document and track it, if not already tracked

This method adds the document to the list of open documents, being tracked by the socket.

Arguments:
  • data (object) – The data object containing the documentId.

  • documentId (number)

  • options (object) – The options object.

Returns:

Promise.<void>

DocumentSocket.publishDocument(data, options)

Publish the document

Arguments:
  • data (object)

  • data.documentId (number) – The ID of the document to publish.

  • options (object) – The options object containing the transaction.

Returns:

Promise.<void>

Uploads review links to a Moodle assignment as feedback comments.

Arguments:
  • data (Object) – The data required for uploading login data.

  • data.options (Object) – The options object containing the API key and URL of the Moodle instance.

  • data.options.courseID (number) – The ID of the course to fetch users from.

  • data.options.assignmentID (number) – The ID of the Moodle assignment.

  • data.options.apiKey (string) – The API token for the Moodle instance

  • data.options.apiUrl (string) – The URL of the Moodle instance.

  • data.feedback (Array.<Object>) – An array of objects containing the feedback to send

Returns:

Promise.<Object> – - A promise that resolves when the passwords have been uploaded.

DocumentSocket.refreshAllDocuments(data, options)

Refresh all documents

Arguments:
  • data (Object) – The data object containing the request parameters.

  • options (Object) – The options object containing the transaction.

Returns:

Promise.<void>

DocumentSocket.saveData(data, options)

Save additional document data for a particular document/study_session/study_step like the nlpResults, links etc., to the document_data table.

Arguments:
  • data (*) – {userId: number, documentId: number, studySessionId: number, studyStepId: number, key: string, value: any}

  • options (*) – {transaction: Transaction}

Returns:

Promise.<void> – - A promise that resolves when the data has been saved.

DocumentSocket.saveDocument(documentId)

Save document delta to disk and mark edits as applied (for HTML documents)

This method saves the combined delta of the document on the disk and updates the edits in the database to mark them as applied.

Arguments:
  • documentId (number)

Returns:

Promise.<void>

DocumentSocket.sendByHash(data, options)

Send document by hash

Arguments:
  • data (object)

  • data.documentHash (string) – The hash of the document to send.

  • options (object) – The options object containing the transaction.

Returns:

Promise.<void>

DocumentSocket.sendDocumentDeltas(data, options)

Send merged deltas (from disk and database) to client (for HTML documents)

Arguments:
  • data (object)

  • data.documentId (number) – The ID of the document to send deltas for.

  • options (object) – The options for the transaction.

Returns:

Promise.<void>

DocumentSocket.subscribeDocument(data, options)

Subscribe to a document

Arguments:
  • data (Object)

  • data.documentId (number) – The ID of the document to subscribe to.

  • options (Object) – The options object containing the transaction.

Returns:

Promise.<void>

DocumentSocket.updateDocument(data, options)

Update a document

Arguments:
  • data – The data object containing the new document object.

  • options – The options object containing the transaction.

Returns:

Promise.<void>

Logger Socket Functions (log.js)

class LoggerSocket()

Handle logs through websocket

LoggerSocket.getAllLogs(data, options)

Get log messages

Arguments:
  • data – The log entry selection criteria

  • data.filter – Log entry filter

  • data.order – Log entry order

  • data.limit – Log entry limit

  • data.page – Log entry page

  • options – Unused

Returns:

void

LoggerSocket.log(data, options)

Log a message

Arguments:
  • data – The data to log

  • data.level – The log level

  • data.message – The log message

  • data.metadata – Optional log message metadata

  • options – Unused

Returns:

void

Service Socket Functions (service.js)

class ServiceSocket()

Handling Services through websockets

ServiceSocket.connectService(data, options)

Connects a service to the client

Arguments:
  • data (Object) – The input data

  • data.service (string) – The name of the service to connect

  • data.data (Object) – Additional data to pass to the service

  • options (Object) – not used

Returns:

Promise.<void>

ServiceSocket.disconnectService(data, options)

Disconnects a service from the client

Arguments:
  • data (Object) – The input data

  • data.service (string) – The name of the service to disconnect

  • data.data (Object) – Additional data to pass to the service

  • options (Object) – not used

Returns:

Promise.<void>

ServiceSocket.requestService(data, options)

Request a service (with default command)

Arguments:
  • data (Object) – The input data

  • data.service (string) – The name of the service to disconnect

  • data.data (Object) – Additional data to pass to the service

  • options (Object) – not used

Returns:

Promise.<void>

ServiceSocket.serviceCommand(data, options)

Request a service but with a specific command

Arguments:
  • data (Object) – The input data

  • data.service (string) – The name of the service to disconnect

  • data.data (Object) – Additional data to pass to the service

  • options (Object) – not used

Returns:

Promise.<void>

Setting Socket Functions (setting.js)

class SettingSocket()

Handle settings through websocket

SettingSocket.saveSettings(data, options)

Save settings to the database

Arguments:
  • data (Array.<{key: string, value: any}>) – List of settings to be saved

  • options (object) – Context passed through the socket pipeline

Returns:

Promise.<string> – Success message

SettingSocket.sendSettings(data, options)

Send current settings to the client

Arguments:
  • data (any) – Unused

  • options (object) – Context passed through the socket pipeline

Returns:

Promise.<Array.<{key: string, value: any}>> – All settings in flat key-value format

Statistic Socket Functions (statistic.js)

class StatisticSocket()

Add statistics about the website usage

Therefore, use in frontend:

this.$socket.emit(“stats”, {action: “action”, data: {}});

The data object can hold additional information!

StatisticSocket.addStats(data, options)

Add statistics

Arguments:
  • data (Object) – The data object containing the userId

  • data.action (Number) – The type of action (e.g. ‘mouseMove’)

  • options (Object) – not used

Returns:

Promise.<void> – - The statistics data

StatisticSocket.getStats(data, options)

Get statistics

Arguments:
  • data (Object) – The data object containing the userId

  • data.userId (Number) – The userId to get statistics for (optional)

  • options (Object) – not used

Throws:

Error

  • If the user does not have permission to access the data

Returns:

Promise.<Object> – - The statistics data

StatisticSocket.getStatsByUser(data, options)

Get a user’s statistics

Arguments:
  • data (Object) – The data object containing the userId

  • data.userId (Number) – The user’s ID

  • options (Object) – not used

Returns:

Promise.<void> – - The statistics data

StatisticSocket.sendStatsByUser(userId)

Send statistics to the user

Arguments:
  • userId (number)

Returns:

Promise.<void>

Study Session Socket Functions (study_session.js)

class StudySessionSocket()

Handle all study sessions through websocket

Loading the study sessions through websocket

StudySessionSocket.sendSessionsByStudyId(studyId)

Send all study sessions to the client

Returns:

Promise.<void>

StudySessionSocket.startStudySession(data, options)

Start a study session

Arguments:
  • data (object)

  • data.studyId (number) – A study id

  • data.studySessionId (number) – A study session id

  • options (object) – Transaction options

Returns:

Promise.<void>

StudySessionSocket.subscribeToStudySession(data, options)

Subscribe to a study session

Arguments:
  • data (object)

  • data.studyId (number) – A study id

  • options (object) – Transaction options

Returns:

Promise.<void>

StudySessionSocket.unsubscribeFromStudySession(data, options)

Unsubscribe from a study session

Arguments:
  • data (object)

  • data.studyId (number) – A study id

  • options (object) – Transaction options

Returns:

Promise.<void>

Study Socket Functions (study.js)

class StudySocket()

Handle all studies through websocket

Loading the studies through websocket

StudySocket.closeBulk(data, options)

Close a bulk of studies

Arguments:
  • data.projectId – the project id of the studies to close

  • data.ignoreClosedState – if true, also close studies that are already closed

  • data.progressId – the id of the progress bar to update

  • options – not used

Returns:

Promise.<void>

StudySocket.saveStudyAsTemplate(data, options)

Save the current study as a template (create a new study with template: true)

Arguments:
  • data (object)

  • data.id (number) – the id of the study to save as template

  • options (object) – the options for the transaction

Returns:

Promise.<*>

User Socket Functions (user.js)

class UserSocket()

Handle user through websocket

UserSocket.bulkCreateUsers(data)

Bulk create or update users

Arguments:
  • data (Object) – The data object containing the users and role map

Returns:

Promise.<{createdUsers: Array, errors: Array}> – - An object containing the created users and errors

UserSocket.checkUsersExists(data, options)

Check a list of users if they already exist in the database by email

Arguments:
  • data – The data object containing the users to check - at least email key is required

  • options – The options object

Returns:

UserSocket.createUser(data, options)

Add a user to the database

Throws:

Error

  • If the user is not an admin

Returns:

Promise.<*>

UserSocket.getUsers(role)

Get users by their roleu.status === “duplicate”

Arguments:
  • role (string) – The role of the users to fetch. Possible values: “student”, “mentor”, “all”

Returns:

Array.<string> – An array of users.

UserSocket.getUsersByRole(data, options)

Get users by their role

Arguments:
  • data (Object) – The input data from the frontend

  • data.role (string) – The role of the user

  • options (Object) – Additional configuration parameter

  • options.transaction (Object) – Sequelize DB transaction options

Throws:

Error

  • If the user does not have right

Returns:

Promise.<void>

UserSocket.getUsersFromCourse(options)

Retrieves users from a specified moodle course and returns the data as an array.

Arguments:
  • options (Object) – The data object containing the course ID, Moodle URL and the API token.

  • options.courseID (number) – The ID of the course to fetch users from.

  • options.options.apiKey (string) – The API token for the Moodle instance

  • options.options.apiUrl (string) – The URL of the Moodle instance.

Returns:

Promise.<*> – - The response from the RPC service

UserSocket.minimalFields(user)

Shows only specific fields of a user

Arguments:
  • user (object) – The user object

Returns:

UserSocket.resetUserPwd(data, options)

Reset user’s password

Arguments:
  • data (Object) – The input data from the frontend

  • data.userId (number) – The ID of the user

  • data.password (string) – The new password

  • options (Object) – Additional configuration parameter

  • options.transaction (Object) – Sequelize DB transaction options

Throws:

Error

  • If the user is not an admin or the user tries to reset other’s password

Returns:

Promise.<void>

UserSocket.updateCreatorName(data, key="userId", targetName="creator_name")

Adds the username as creator_name of a database entry with column creator

Accepts data as a list of objects or a single object Note: Always returns a list of objects

Arguments:
  • data (object|Array.<object>) – The data to update

  • key (string) – The key of the user ID field

  • targetName (string) – The name of the target field

Returns:

UserSocket.updateUserConsent(data, options)

Update user’s consent data

Arguments:
  • data (Object) – The consent data object

  • data.acceptTerms (boolean) – Indicates whether the user has consented to the terms of service

  • data.acceptStats (boolean) – Indicates whether the user has agreed to tracking

  • data.acceptDataSharing (boolean) – Indicates whether the user has agreed to donate their annotation data

  • data.acceptedAt (string) – Time when the user made the consent

  • options (Object) – Sequelize transaction options

Returns:

void

UserSocket.userPublishMoodle(data)

Uploads login data to a Moodle assignment as feedback comments.

Arguments:
  • data (Object) – The data required for uploading login data.

  • data.options (Object) – The options object containing the API key and URL of the Moodle instance.

  • data.options.courseID (number) – The ID of the course to fetch users from.

  • data.options.assignmentID (number) – The ID of the Moodle assignment.

  • data.options.apiKey (string) – The API token for the Moodle instance

  • data.options.apiUrl (string) – The URL of the Moodle instance.

  • data.users (Array.<Object>) – An array of objects containing the uploaded users.

Returns:

Promise.<Object> – - A promise that resolves when the passwords have been uploaded.