import DataboxCore, { DbPreparedData } from "./DataboxCore"; import Bag from "../Bag"; import { InfoOption, TimestampOption, DbSessionData } from '../../main/databox/dbDefinitions'; import { DbInConnection, FetchRequest, SignalAction, Transaction } from './DataboxApiDefinitions'; import Socket from "../Socket"; import { DeepReadonly } from '../../main/utils/typeUtils'; import { DataboxConfig } from '../../main/config/definitions/parts/databoxConfig'; import { DbContentAPI, DataboxContentCompatible } from '../../main/databox/dbContentAPI'; /** * If you always want to present the most recent data on the client, * the Databox is the best choice. * The Databox will keep the data up to date on the client in real-time. * Also, it will handle all problematic cases, for example, * when the connection to the server is lost, * and the client did not get an update of the data. * It's also the right choice if you want to present a significant amount of data * because Databoxes support the functionality to stream the data * to the clients whenever a client needs more data. * Additionally, it keeps the network traffic low because it * only sends the changed data information, not the whole data again. * * Instead of the StaticDatabox, the Databox class allows you to have * multiple members that differ by a hashable value. * That is useful in many cases, for example, if you want to * have a Databox for user profiles. * Then, the members could be the ids of the users. * * You can override these methods: * - initialize * - fetch / singleFetch * * events: * - onConnection * - onDisconnection * - onReceivedSignal * - onTransaction * * middleware methods: * - memberMiddleware * - transactionMiddleware * - transmitSignalMiddleware */ export default class Databox extends DataboxCore implements DataboxContentCompatible { /** * Holds all information about every registered member. */ private readonly _regMembers; /** * Maps the sockets to the members. */ private readonly _socketMembers; private readonly _unregisterMemberTimeoutMap; private readonly _dbIdPreFix; private readonly _maxSocketInputChannels; private readonly _maxSocketMembers; private readonly _fetchImpl; private readonly _buildFetchManager; private readonly _sendTransactionToSockets; private readonly _sendSignalToSockets; private readonly _definedTransactionMiddleware; private readonly _hasOnTransactionListener; private readonly _onConnection; private readonly _onDisconnection; private readonly _onReceivedSignal; private readonly _onTransaction; private readonly _memberMiddleware; private readonly _transactionMiddleware; private readonly _transmitSignalMiddleware; constructor(identifier: string, bag: Bag, dbPreparedData: DbPreparedData, apiLevel: number | undefined); private _getFetchImpl; /** * Returns the send transaction to socket handler. * Uses only the complex send transaction to socket (with middleware) * when the middleware is defined. */ private _getSendTransactionToSocketsHandler; /** * Disconnects a socket. * @param socket * @param disconnectHandler * @param memberStr * @param member * @private */ private _disconnectSocket; private _fetch; private _resetSession; private _copySession; /** * Access the socket member mem securely. * Will automatically build unknown members. * @param member * @private */ private _accessMember; /** * Connects a socket internally with the Databox, if it's not already connected. * (To get updates of this member) * @param socket * @param member * @private */ private _connectSocket; /** * Removes a socket internally in the map. * @param socket * @param memberStr * @private */ private _rmSocket; /** * Clears the timeout to unregister the member. * @param memberStr * @private */ private _clearUnregisterMemberTimeout; /** * Creates (set or renew) the timeout to unregister a member. * @param memberStr * @private */ private _createUnregisterMemberTimeout; private _initLastTransactionDataMemory; /** * Registers for listening to a new member. * @param member * @private */ private _registerMember; /** * Unregisters for listening to a member. * @param memberStr * @private */ private _unregisterMember; /** * Sends a Databox package to all sockets of a member. * @param memberStr * @param dbClientPackage * @param processComplexTypes */ private _sendToSockets; /** * Sends a Databox signal package to sockets of the Databox after passing the signal middleware. * @param memberStr * @param member * @param dbClientPackage * @private */ private _sendSignalToSocketsWithMiddleware; /** * Sends a Databox transaction package to sockets of the Databox after passing the transaction middleware. * @param memberStr * @param member * @param dbClientPackage * @param processComplexTypes * @private */ private _sendTransactionToSocketsWithMiddleware; private _updateLastTransactionData; /** * Processes new transaction package. * @param member * @param transactionPackage * @param processComplexTypes */ private _processTransactionPackage; private _broadcastToOtherSockets; private _sendToServers; /** * Close the member of this Databox. * @param memberStr * @param closePackage * @private */ private _close; private _internalClose; /** * @description * The content API provides a typesafe and readable way to * update, delete, or insert new data in the databox. * It is also possible to do multiple cud operations in one bundle. * Don't forget to update your database before updating the databox. */ readonly content: DbContentAPI; /** * **Not override this method.** * The close function will close a Databox member for every client on every server. * You optionally can provide a code or any other information for the client. * Usually, the close function is used when the data is completely deleted from the system. * For example, a chat that doesn't exist anymore. * @param member The member to close * @param code * @param metadata * @param forEveryServer * @return The returned promise is resolved when * the close is fully processed on the current server. */ close(member: M, code?: number | string, metadata?: any, forEveryServer?: boolean): Promise; /** * **Not override this method.** * The reload function will force all connected * clients of the Databox member to reload the data. * @param member * @param forEveryServer */ forceReload(member: M, forEveryServer?: boolean): void; /** * **Not override this method.** * @description * This method creates a new transaction with unknown content changes. * You should always prefer the specific transaction methods provided by the content API, * as this method forces clients of the member to reload the entire data set. * In cases where specific content changes cannot be tracked effectively, * you may consider using this method. * Aside from the forceReload method, a new transaction will be created, * which causes clients to reload the data even if they missed the package duo to connection issues. * @param member The member to update * @param timestamp * @param code * @param metadata */ anyContentChange(member: M, { timestamp, code, metadata }?: InfoOption & TimestampOption): void; /** * **Not override this method.** * With this function, you can kick out a socket from a member of the Databox. * This method is used internally. * @param member * @param socket * @param code * @param data */ kickOut(member: M, socket: Socket, code?: number | string, data?: any): void; /** * With this function, you can do a recheck of all sockets on a specific member. * It can be useful when the access rights to member have changed, * and you want to kick out all sockets that not have access anymore. * Notice that the promise is resolved when the access was checked * on the current server and request sent to other servers. * @param member * @param forEveryServer */ recheckMemberAccess(member: M, forEveryServer?: boolean): Promise; /** * **Not override this method.** * Transmit a signal to all client Databoxes that * are connected with a specific member of this Databox. * The clients can listen to any received signal. * You also can send additional data with the signal. * @param member * @param signal * @param data * @param forEveryServer */ transmitSignal(member: M, signal: string, data?: any, forEveryServer?: boolean): void; /** * **Not override this method.** * This method returns an array with all * members where the socket is registered. * @param socket */ getSocketRegMembers(socket: Socket): DeepReadonly[]; /** * **Can be overridden.** * This method is used to fetch data for the clients of the Databox. * A client can call that method multiple times to fetch more and more data. * If you don't want to stream data you should look at the singleFetch method. * Notice that only one method can be overridden. * You usually request data from your database and return it, and if no more data is available, * you should throw a NoDataError. If you return undefined or null, the NoDataError is automatically thrown. * The counter property of the request indicates the number of the current call, it starts counting at zero. * Notice that the counter only increases when the fetch was successful (means no error was thrown). * The client can send additional data when calling the fetch process (fetchInput), * this data is available in the input property of the request. * Also, you extra get a session object, this object you can use to save variables that are * important to get more data in the future, for example, the last id of the item that the client had received. * The session object is only available on the server-side and can not be modified on the client-side. * If the fetch was not successful and you modified the session object in the fetch, all changes will be reverted. * Notice that you can only store JSON convertible data in the session. Also, the data between different sessions, * such as reload or main session, needs to be compatible and mergeable. * * If you design the Databox in such a way that the next fetch is not depending on the previous one, * you can activate the parallelFetch option in the Databox config. * * The data that you are returning can be of any type. * The client will convert some data parts into specific databox storage components. * These components will allow you to access specific values with a selector. * There are three of them: * The Object: * It is a simple component that has no sequence, and you can access the values via property keys. * The client will convert each JSON object into this component. * * The KeyArray: * This component allows you to keep data in a specific sequence, * but you still able to access the values via a string key. * To build a key-array, you can use the buildKeyArray function. * Notice that JSON arrays will not be converted into this component type. * * The Array: * This component is a light way and simple component for an array. * Instead of the key-array, you only can access values via an array index. * Also, a difference is that the sequence of the elements is connected to the key (index). * That means sorting the values changes the keys. * All JSON arrays will be converted into this type. * If you need resorting, more specific keys, or you manipulate lots of data in the array, * you should use the key-array instead. * * When loading more data, the client will merge these data by using these components. * But notice that the client can only merge components from the same type. * Otherwise, the new value will override the old value. * * Whenever you are using the socket to filter secure data for a specific user, * you also have to use the transaction middleware to filter the transactions for the socket. * But keep in mind when you overwrite the transaction middleware * the Databox switches to a less performant implementation. * @param request {counter: number, input?: any, reload: boolean} * @param connection {member: M, socket: Socket, options?: any} * @param session */ protected fetch(request: FetchRequest, connection: DbInConnection, session: DbSessionData): Promise | T; /** * **Can be overridden.** * This method is used to fetch data for the clients of the Databox. * A client can call that method to fetch the data of this Databox. * Instead of the fetch method, this method uses the counter internally to allow * the client to fetch data only one time. * If you want more freedom or stream data you should look at the fetch method. * Notice that only one method can be overridden. * You usually request data from your database and return it. * The client can send additional data when calling the fetch process (fetchInput), * this data is available in the input property of the request. * * The data that you are returning can be of any type. * The client will convert some data parts into specific databox storage components. * These components will allow you to access specific values with a selector. * There are three of them: * The Object: * It is a simple component that has no sequence, and you can access the values via property keys. * The client will convert each JSON object into this component. * * The KeyArray: * This component allows you to keep data in a specific sequence, * but you still able to access the values via a string key. * To build a key-array, you can use the buildKeyArray function. * Notice that JSON arrays will not be converted into this component type. * * The Array: * This component is a light way and simple component for an array. * Instead of the key-array, you only can access values via an array index. * Also, a difference is that the sequence of the elements is connected to the key (index). * That means sorting the values changes the keys. * All JSON arrays will be converted into this type. * If you need resorting, more specific keys, or you manipulate lots of data in the array, * you should use the key-array instead. * * Whenever you are using the socket to filter secure data for a specific user, * you also have to use the transaction middleware to filter the transactions for the socket. * But keep in mind when you overwrite the transaction middleware * the Databox switches to a less performant implementation. * @param request {counter: number, input?: any, reload: boolean} * @param connection {member: M, socket: Socket, options?: any} */ protected singleFetch(request: FetchRequest, connection: DbInConnection): Promise | T; /** * **Can be overridden.** * A function that gets triggered on each transaction of the Databox. * The event will only be processed internally when you overwrite this method. * @param member * @param transaction */ protected onTransaction(member: DeepReadonly, transaction: Transaction): Promise | void; /** * **Can be overridden.** * A function that gets triggered whenever a new socket is connected to the Databox. */ protected onConnection(member: DeepReadonly, socket: Socket): Promise | void; /** * **Can be overridden.** * A function that gets triggered whenever a socket is disconnected from the Databox. * Notice that means all input channels are closed. */ protected onDisconnection(member: DeepReadonly, socket: Socket): Promise | void; /** * **Can be overridden.** * A function that gets triggered whenever a * socket from this Databox received a signal. */ protected onReceivedSignal(connection: DbInConnection, signal: string, data: any): Promise | void; /** * **Can be overridden.** * With the member middleware, you can protect your Databox against invalid members. * For example, when you have a Databox for user-profiles and the member represents * the user id you can block invalid user ids. * To block the member, you can return a Block instance or false or throwing a Block instance. * If you don't return anything, the member will be allowed. * The Bag instance can be securely accessed with the variable 'bag'. * @param member */ protected memberMiddleware(member: DeepReadonly): Promise | boolean | object | any; /** * **Can be overridden.** * The transaction middleware. * Notice that when you overwrite this middleware, the Databox switches * to a less performant implementation. * It is not recommended to invoke long processes in this middleware. * Instead, try to prepare stuff in the token of the socket or the socket attachment. * The middleware will be called before each socket reaches a transaction. * You are able to block transactions for specific clients by returning a * Block instance or false or throwing a Block instance. * If you don't return anything, the transaction will be transmitted to the client. * The Bag instance can be securely accessed with the variable 'bag'. * @param member * @param socket * @param transaction */ protected transactionMiddleware(member: DeepReadonly, socket: Socket, transaction: Transaction): Promise | void; /** * **Can be overridden.** * The transmit signal middleware. * Notice that when you overwrite the transmit signal middleware, * the Databox switches to a less performant implementation of processing signals. * It is not recommended to invoke long processes in this middleware. * Instead, try to prepare stuff in the token of the socket or the socket attachment. * The middleware will be called before each socket reaches a transmitted signal. * You can change the data for a socket with the property changeData of the action by simply calling * the function with the new data. * You are also able to block the complete action for a socket * by returning a Block instance or false or throwing a Block instance. * The Bag instance can be securely accessed with the variable 'bag'. * @param member * @param socket * @param signalAction */ protected transmitSignalMiddleware(member: DeepReadonly, socket: Socket, signalAction: SignalAction): Promise | void; } export declare type DataboxClass = (new (...args: any[]) => Databox) & { config: DataboxConfig; prototype: Databox; };