UNPKG

copious-transitions

Version:
235 lines (219 loc) 15 kB
const LocalTObjectCache = require('../default_storage/local_object_cache') /** * Transition handling is a collection of POST method responses designed to provide executive action. * There are just a few methods that guide the process by puting the request through tests supplied by the * session manager, a validator, and a transition processor until a transition finalization method can be called. * * A transition may operate in response to a single request, or it might cache salietn data and utimately achieve finalization * after negotiating a second request with the requesting client. * * The basic method that runs a transition is `transition_handler`. And, if a secondary action is required for a transition, * the `secondary_transition_handler` method will be invoked in response to the appropriate request from the requesting client. * * Two other methods are provided by this class, `ws_transition` and `endpoint_transition`. These methods work in response to * messages from communication paths other than HTTP communication paths. While the `CopiousTransitions` sets up an HTTP(s) web * server and handles requests coming to certain types of API paths, the other methods use WebSockets in one case and TCP(tls) * communication in the second case. These methods call upon `transition_handler` and `secondary_transition_handler` to do their work. * They will then shape their responses to WS clients or relay clients. * * Note that one can imagine a transtion machine implemented explicitly in the application and that a single transition machine instance can * belong to a session. Some applications may implement the actual transition machine. Often, though, the transition machine * is a set of calls out to the DB interface or to the transition engine, which might send data somewhere or publish a message. * The point of these methods is to create a skeleton for the flow of transition processing. * * @memberof Contractual * @extends LocalTObjectCache */ class TransitionHandling extends LocalTObjectCache { // // constructor(sess_manager,validator,dynamics,cache_time) { // super(cache_time) this.going_ws_session = {} // this.session_manager = sess_manager this.validator = validator this.dynamics = dynamics } /** * The transition hanlder is the CopiousTransitions entry point into an application's state transition * implementation. This method supplies the basic framework for stepping the transition request through permission, * acceptance, processing setup, until the state transition can be finalized either in response to the first request or * in response to a secondary request (`secondary_action`). * * The transition handler first calls the session manager's guard. The guard can provide a certain amount of access control * and may also know about the state of the server and the permissibility of certain types of transactions. A failure * to pass the guard yields a reson of unavailability to the requester. * * If the guard is passed, the validator checks the trasition body for addmissable data types, etc. In many use case, * the call to the validator is moot. Yet, in some operations the application will prvodide checking methods that the * validator framework can use. * * After validation, the transition is examined for feasibility. By feasibility is meant that the state of the server relative to the session * can allow for the transition to a desired state and that the resources required for the transition are available. The check must be * determined by the application. The check can be any degree of complexity from something simple such as accepting the name of * the transition to something quite complicated such as checking that certain machines are attached to the server and that certain * measurement are with desired ranges. What is provided here, is that the transition server will call on the feasibility test provided * by the application after validation and before transition processing and, furthermore, the transition handler will wait (await) * the completion of the feasibility testing. * * Requests remaining after they are deemed feasible, can be processed using the session manager's `process_transition`, * which sets up the data needed for finalization and determines if the transaction will happen in one step or * require a secondary action. * * Those transitions that can be done in one step proceed immediately to finalization. The finalization method, provided * by the application, should set the state transition for the session. The method should result in a the transition machine * owned by the session being in an identifiable state. When the session is queried for the state of the machine, the state * of the last finalization should be reported. A query of the state should remain the same with respect to the session * until another state transition is requested. * * Those transitions requiring a secondary action before finalization will cache data in preparation for the second action to be done * in response to an ensuing request. The transition handler calls out to the dynamic data producer to produce the data to be cached. * The transition handler will cache the data using the methods provided by LocalTObjectCache; the data is cached * into a map structure with the transition's token as the key. Once the data is cached, * the partial transition object containing data for the client will be sent to the requester along with the parts of the generated data chosen to be sent. Data is sent * in preparation for the secondary action according to a contract of design established for the synchronization of client and server * operations and negotiations. * * Given the client comes back with the request for the seconary action, the next handler `secondary_transition_handler` will * continue the process of progression the transtion to finalization. * * @param {string} transition - the type of transition the client is requesting. See documentation about tagged transisions. * @param {object} body * @param {object} transmision_headers * @returns {Array} - a tupple really, that has: 1) the status code, 2) the JSON response, 3) possibly data or boolean (false for not in use) */ async transition_handler(transition,body,transmision_headers) { let proceed = await this.session_manager.guard(transition,body,transmision_headers) if ( !proceed ) { // asset exits, permission granted, etc. (check fail) return [200,{ 'type' : 'transition', 'OK' : 'false', 'reason' : 'unavailable' },false ] } // if ( this.validator.valid(body,this.validator.field_set[transition]) ) { // A field set may be in the configuration for named transitions - true by default let is_feasible = await this.session_manager.feasible(transition,body,transmision_headers) // can this session actually make the transition? if ( is_feasible ) { // let transitionObj = await this.session_manager.process_transition(transition,body,transmision_headers) // either fetch or produced transition data if ( transitionObj ) { // // Require a seconday action as part of the transition for finalization if ( transitionObj.secondary_action ) { // elements is purposely vague and may be application sepecific try { let [send_elements, store_elements] = await this.dynamics.fetch_elements(transition,transitionObj); // transition = ((transitionObj.transition !== undefined) ? transitionObj.transition : transition) // let tObjCached = { 'tobj' : transitionObj, 'elements' : store_elements, 'transition' : transition } this.add_local_cache_transition(transitionObj.token,tObjCached) // let t_state = { 'type' : 'transition', 'OK' : 'true', 'transition' : transitionObj, 'elements' : send_elements } return [200,t_state] } catch(e) { console.log(e) // nothing really... just report that you can't } } else { // Send back a finalization of transition right away. body.token = transitionObj.token // FINALIZE (not a final state) -- means that the state finally makes the transition let finalization_state = await this.session_manager.finalize_transition(transition,body,{},transmision_headers) if ( finalization_state ) { // relay the finalized transition and go on with business. let state = finalization_state.state let OK = finalization_state.OK let t_state = { 'type' : 'finalize', 'OK' : OK, 'state' : state, 'reason' : 'matched' } return [200,t_state] } } // } // } } return [200,{ 'type' : 'transition', 'OK' : 'false', 'reason' : 'unavailable' }] } /** * This method progresses a transtion request towards finalization provided that the transition can be identified by its token. * The previously generated data that was not sent to the client will be extracted from the transition object keyed by the token. * * Just one check, a match between the object sent in the request from the client and the cached transition object needs to pass. * The match test might be one of a number of possible checks, ranging in in complexity from the check to see is a field is present, to * checking if same name fields of the two objects are equal, to signature verification using elliptic key crypotgraphy, or perhaps more. * The match implementation is part of the application. * * Once the match is passed, the secondary transition handler calls up the transition finalization method for the transition machine * operating with respect to the current session. * * * @param {object} body * @param {object} transmision_headers * @returns {Array} - a tupple really, that has: 1) the status code, 2) the JSON response, 3) possibly data or boolean (false for not in use) */ async secondary_transition_handler(body,transmision_headers) { if ( body.token !== undefined ) { let cached_transition = this.fetch_local_cache_transition(body.token,body.next) if ( cached_transition !== undefined ) { if ( this.session_manager.match(body,cached_transition) ) { // check on matching tokens and possibly other things // some kind of transition takes place and becomes the state of the session. It may not be the same as the one // specified in the cached transition, but may be similar depending on how types (categories) are regulated let elements = cached_transition.elements let finalization_state = await this.session_manager.finalize_transition(cached_transition.transition,body,elements,transmision_headers) // FINALIZE (not a final state) if ( finalization_state ) { // relay the finalized transition and go on with business. let state = finalization_state.state let OK = finalization_state.OK // as a string let t_state = { 'type' : 'finalize', 'OK' : OK, 'state' : state, 'reason' : 'matched' } return [200,t_state] } // else nothing worked } } } return [200,{ 'type' : 'transition', 'OK' : 'false', 'reason' : 'unavailable' } ] } /** * This method does a very short one step version of transtion processing in response to websocket * messages inbound from the client. This method only check on the feasiblily of the transition and * if it finds it feasible, the transition will be move on to finalization. * * @param {string} transition * @param {object} body * @returns {Array} - a tupple really, that has: 1) the status code, 2) the JSON response, 3) possibly data or boolean (false for not in use) */ async ws_transition(transition,body) { let is_feasible = await this.session_manager.feasible(transition,body,null) if ( is_feasible ) { let finalization_state = await this.session_manager.finalize_transition(transition,body,{},null) // FINALIZE (not a final state) if ( finalization_state ) { return finalization_state } } return false } /** * This method is named `endpoint_transition` due to its servicing the operations of an endpoint server (as defined in the package * message-relay-server). This method looks for a token on the message (body). If the token is not there, it assumes the * message arriving at the endpoint is in the first phase of transition processing and sends the data on to the transition handler. * * If the token is present, the method assumes that the message is targeted to the secondary action. * * The branches return the result of the methods they call, and the UserMessageEndpoint instance is written to * send the response back to the relay client (message-relay-server) in an appropriate form using the transtion hanlder results. * * @param {string} transition * @param {object} body * @returns {Array} - a tupple really, that has: 1) the status code, 2) the JSON response, 3) possibly data or boolean (false for not in use) */ async endpoint_transition(transition,body) { try { if ( !(body.token) ) { // no transition token ... so treat this as primary return await this.transition_handler(transition,body,{}) } else { return await this.secondary_transition_handler(body,{}) } } catch (e) { return [200,{ 'type' : 'transition', 'OK' : 'false', 'reason' : 'unavailable' } ] } } } module.exports = TransitionHandling