@adjust/core
Version:
A framework for creating highly customisable open source software
410 lines • 15 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const remoteModuleProxy_1 = require("./remoteModuleProxy");
const programState_1 = require("../state/programState");
const settings_1 = require("../storage/settings/settings");
const stateData_1 = require("../state/stateData");
const requestPath_1 = require("./requestPath/requestPath");
const registry_1 = require("../registry/registry");
const moduleProxy_1 = require("./moduleProxy");
const extendedObject_1 = require("../utils/extendedObject");
exports.baseConfig = {
settings: {},
initialState: {
isStopping: false,
isStopped: false,
},
abstract: true,
type: null,
viewClass: undefined,
getPriority: () => 1,
};
/**
* A class containing data for importing it (its actual file location),
* a state that can be serialized and deserialized and
* a settings object that stores settings for this type of component
*/
class Module {
/**
* The core building block for Adjust applications
* @param request The relevant data of the request that created this instance
* @param moduleID The ID of this module
* @returns An unregistered instance of this module
*/
constructor(request, moduleID, initialState, parents) {
// Setup request related data
this.requestData = request;
this.ID = moduleID;
this.parents = parents;
this.parent = this.parents[0];
// Settings initialization
this.settingsObject = new settings_1.Settings(this, this.getConfig().settings);
this.settings = this.settingsObject.get;
// State initialization
this.stateObject = new stateData_1.StateData(initialState);
this.state = this.stateObject.get;
}
/**
* Get the request path for this module based on its parent and the ID
* @param moduleID The ID of this module
* @param parent The parent of this module
* @param data The json data that was send with this request
* @returns The request path obtained
*/
static createRequestPath(moduleID, parent, data) {
if (parent) {
// Extend the parent's path
const parentRequestPath = parent.getRequestPath();
return parentRequestPath.extend(moduleID, data);
}
else {
// If the module is a root, create a path from scratch
return new requestPath_1.RequestPath(moduleID, data);
}
}
/**
* Creates an instance of this module, given an ID for the instance and a request
* @param request The request that started the creation of the module
* @param moduleID The ID that the new instance should have
* @returns A new instance of this class
*/
static createInstance(request, moduleID) {
// Obtain the required data to instanciate the module
const initialState = this.getConfig().initialState;
request.requestPath = this.createRequestPath(moduleID, request.parent, request.data);
const parents = request.parent ? [request.parent] : [];
// Create the instance
return new this(request, moduleID, initialState, parents);
}
// Initialisation
/**
* A method that gets called to perform initialisation,
* should be called only once, after having been added to the program state
* (will be called by external setup method, such as in classModuleProvider)
*/
init() {
this.onInit();
}
/**
* A method that gets called to perform any required initialization on reload
* (will be called by internal setup method; deserialize)
*/
reloadInit() {
this.onReloadInit();
}
/**
* A method that gets called to perform any initialization,
* will be called only once, after having been added to the state
*/
onInit() { }
/**
* A method that gets called to perform any required initialization on reload
*/
onReloadInit() { }
// State related methods
/**
* Retrieves the entire state object of the module
* @returns The entire state object on which listeners could be registered
*/
getStateObject() {
return this.stateObject;
}
/**
* Changes the current state of the module
* @param changedProps An object containing any fields of the state that have changed
* @returns A promise that resolves once all listeners have resolved
*/
async setState(changedProps) {
return this.stateObject.changeData(changedProps);
}
// Settings related methods
/**
* Retrieves the entire settings object of the module
* @returns The entire settings object on which listeners could be registered
*/
getSettingsObject() {
return this.settingsObject;
}
// Serialization related methods
/**
* Serializes the entire module, based on the state
* @returns An object containing all the module's relevant data
*/
serialize() {
const requestData = this.getRequest();
return {
$type: this.getClass().getPath(),
data: {
request: Object.assign({}, extendedObject_1.ExtendedObject.filter(requestData, (v, k) => ["use", "type"].indexOf(k) == -1), { requestPath: requestData.requestPath.toString(), parent: requestData.parent && requestData.parent.getID().toString() }),
parents: this.parents.map(parent => parent.getID().toString()),
state: this.stateObject.serialize(),
},
};
}
/**
* Creates an instance of this module, given an ID for the instance and serialized data representing an instance
* @param serializedData The serialized data, obtained by serializing a previous instance
* @param moduleID The ID that the new instance should have
* @returns A new instance of this class
*/
static recreateInstance(serializedData, moduleID) {
// The data is a serialized module
const data = serializedData.data;
// Obtain the required data to instanciate the module
const request = Object.assign({}, data.request, { requestPath: new requestPath_1.RequestPath(data.request.requestPath) });
// Create the instance
return new this(request, moduleID, {}, []);
}
/**
* Deserializes the data that defines the module's own state
* @param data The data to be deserialized
*/
deserialize(data) {
// Update the parents
const parents = data.parents.map(parent => programState_1.ProgramState.getModule(parent).createProxy());
this.parents.push.apply(this.parents, parents);
this.parent = this.parents[0];
// Deserialize the state
this.stateObject.deserialize(data.state, this);
// Finish by calling the init hook
this.reloadInit();
}
// Request related methods
/**
* Returns the ID of this module
* @returns The ID
*/
getID() {
return this.ID;
}
/** @override */
toString() {
return this.ID.toString();
}
/**
* Retrieves the request that instanciated this module
* @returns The request
*/
getRequest() {
return this.requestData;
}
/**
* Retrieves the request path for this module
* @returns The request path
*/
getRequestPath() {
return this.requestData.requestPath;
}
/**
* Retrieves the request data for this module
* @returns The request data
*/
getData() {
return this.requestData.data;
}
/**
* Retrieves the parent of this module
* @returns The parent that made the request
*/
getParent() {
return this.parent;
}
/**
* Retrievs the additional parents of this module of any
* @returns An array of the additional parents
*/
getParents() {
return this.parents;
}
/**
* Adds an additonal parent to the module (for when obtained with instance module provider)
* @param parent The new parent to add
*/
addParent(parent) {
this.parents.push(parent);
}
/**
* Removes an additional parent from the module (for when an additional parent closes the child)
* @param parent The parent to remove
* @returns Whether this was the last parent
*/
removeParent(parent) {
// Remove the parent
const index = this.parents.indexOf(parent);
if (index >= 0) {
this.parents.splice(index, 1);
// Notify about the parent disconnect
this.onRemoveParent(parent);
// Check if this is the main parent, and if so, replace it
if (parent == this.parent) {
this.parent = this.parents[0];
// Check if there is a replacement
if (this.parent)
this.onChangeParent(this.parent, parent);
else
return true;
}
}
}
/**
* Called when any parent is removed (Either the main or additional parent)
* @param parent The parent that was removed
*/
onRemoveParent(parent) { }
/**
* Called when the main parent is removed, but an additional parent may take over
* @param newParent The additional parent that is taking over
* @param oldParent The previously main parent that got removed
*/
onChangeParent(newParent, oldParent) { }
async request(request) {
return registry_1.Registry.request(Object.assign({ parent: this }, request));
}
/**
* Retrieves the context that this method was called from, should be called before any awaits
* @returns The program node from which the method was called
* @throws {IllegalStateException} If the method is not called from the start of a interface method
*/
getCallContext() {
if (this.callContext === undefined)
throw Error("Method shouldn't be called after an async call");
return this.callContext;
}
/**
* Updates the call context, should only be invoked by the proxy
* @param callContext The new context
*/
setCallContext(callContext) {
this.callContext = callContext;
}
// Closing related methods
/**
* Stop and close the module
*/
async close() {
// Get the caller of the method, and make sure it's a parent
const context = this.getCallContext();
if (context.isParentof(this)) {
// Remove the parent, and only close the module if it was the last parent
if (this.removeParent(context)) {
// Stop and destroy thismodule
await this.stop();
await this.destroy();
}
}
else
throw Error("Module may only be closed by its parent");
}
/**
* Stops the program node's tasks
*/
async stop() {
// Indicate we are now attempting to stop
await this.setState({
isStopping: true,
});
// Perform stopping methods
await this.stopChildren();
await this.onStop();
// TODO: Close communication channel
// Indicate the module has now stopped
this.setState({
isStopped: true,
});
}
/**
* A hook for tasks to execute when the node is stopped
*/
async onStop() { }
/**
* Stops all of the children and awaits them
*/
async stopChildren() {
// TODO: make sure to check whether module is closed before removing it from state
// Retrieve all the modules in the state
const modules = [];
extendedObject_1.ExtendedObject.forEach(this.state, (key, val) => (val instanceof moduleProxy_1.ModuleProxy ? modules.push(val) : null), true);
// Close all of the modules and wait for them to finish
await Promise.all(modules.map((module) => module.close()));
}
/**
* Disposes all stored resources of the node and unlinks itself from the state
*/
async destroy() {
programState_1.ProgramState.removeModule(this);
this.settingsObject.destroy();
}
/**
* Gets a 'singleton' remote proxy class for this module class
* @returns The remoteModuleProxy for this module class
*/
static getRemoteProxyClass() {
if (!this.remoteProxyClass)
this.remoteProxyClass = remoteModuleProxy_1.RemoteModuleProxy.createClass(this);
return this.remoteProxyClass;
}
/**
* Gets a 'singleton' proxy class for this node
* @returns The programNodeProxy for this programNode class
*/
static getProxyClass() {
if (!this.proxyClass)
this.proxyClass = moduleProxy_1.ModuleProxy.createClass(this);
return this.proxyClass;
}
/**
* Creates a proxy for this program node
* @returns The created proxy
*/
createProxy() {
// Get the proxy class for this program node
const ProxyClass = this.getClass().getProxyClass();
// Create and return a new instance of this proxy class
return ProxyClass.createInstance(this);
}
/**
* Retrieves the config of the module
* @returns the module's config
*/
static getConfig() {
return this.config;
}
/**
* Retrieves the config of the module
* @returns the module's config
*/
getConfig() {
return this.getClass().config;
}
/**
* Assigns a view class to the config of this module
* @param viewClass The view class to relate to this module class
*/
static setViewClass(viewClass) {
this.getConfig().viewClass = viewClass;
}
// Importing related methods
getClass() {
// Get the class out of this object instance
return this.__proto__.constructor;
}
/**
* Returns the path to this file, relative to the modules folder
* @returns The path to this file
*/
static getPath() {
return this.path;
}
/**
* Returns the path to this module class
* @returns The path to this module class
* @public
*/
static toString() {
return this.getPath();
}
}
// Config related methods
Module.config = exports.baseConfig; // The config of the module, will be replaced by createModule
Module.path = ""; // The path of the importable class TODO: refer to some 'missing' path
exports.Module = Module;
//# sourceMappingURL=module.js.map