server/Application.js

  1. 'use strict';
  2. // External modules
  3. const http = require('http');
  4. const { EventEmitter } = require('node:events');
  5. const os = require('os');
  6. const socket_io = require('socket.io');
  7. // Local classes, etc
  8. const { constants, Message } = require('../shared.js');
  9. const { addStaticDirectory, getLocalIP, listen, router } = require('../predefined/routing.js');
  10. const Switchboard = require('./Switchboard.js');
  11. const WorkSpace = require('./WorkSpace.js');
  12. const ViewSpace = require('./ViewSpace.js');
  13. const MessageHandler = require('./MessageHandler.js');
  14. /**
  15. * This module defines the API endpoint.
  16. *
  17. * @memberof module:server
  18. *
  19. * @param {object} [settings={}] - Settings data to be forwarded to the server.
  20. * @param {boolean} [settings.applySmoothing=true] - Whether to apply smoothing
  21. * to gesture inputs on coarse pointer devices (e.g. touch screens).
  22. * @param {boolean} [settings.shadows=false] - Whether to show shadows of other views.
  23. * @param {boolean} [settings.status=false] - Whether to show debugging status information in the view.
  24. * @param {string} [settings.backgroundImage=undefined] - Optional background image for canvas.
  25. * Not recommended. Prefer defining your own HTML, CSS, server, and routing.
  26. * @param {string} [settings.clientScripts=undefined] - Optional extra javascript to load in client.
  27. * Not recommended. Prefer defining your own HTML, CSS, server, and routing.
  28. * @param {string} [settings.color=undefined] Background color for the workspace.
  29. * Not recommended. Prefer defining your own HTML, CSS, server, and routing.
  30. * @param {number} [clientLimit=10] - The number of active clients that are allowed
  31. * Not recommended. Prefer defining your own HTML, CSS, server, and routing.
  32. * @param {express.app} [appRouter=predefined.routing.router()] - Route handler to use.
  33. * @param {http.Server} [server=http.createServer()] - HTTP server to use.
  34. */
  35. class Application {
  36. constructor(settings = {}, appRouter = router(), server = undefined) {
  37. /**
  38. * Express app for routing.
  39. * @type {express.app}
  40. * @see {@link https://expressjs.com/en/4x/api.html#app}
  41. */
  42. this.router = appRouter;
  43. /**
  44. * HTTP server for sending and receiving data.
  45. *
  46. * @type {http.Server}
  47. */
  48. if (server) {
  49. this.httpServer = server;
  50. } else {
  51. this.httpServer = http.createServer(this.router);
  52. }
  53. /**
  54. * Socket.io instance using http server.
  55. */
  56. this.ioServer = new socket_io.Server(this.httpServer);
  57. /**
  58. * Socket.io namespace in which to operate.
  59. *
  60. * @type {Namespace}
  61. * @see {@link https://socket.io/docs/v4/server-api/}
  62. */
  63. this.namespace = this.ioServer.of(constants.NS_WAMS);
  64. /**
  65. * Settings for the application.
  66. *
  67. * @type {object}
  68. */
  69. this.settings = { ...Application.DEFAULTS, ...settings };
  70. /**
  71. * The main model for items.
  72. *
  73. * @type {module:server.WorkSpace}
  74. */
  75. this.workspace = new WorkSpace(this.namespace);
  76. /**
  77. * The MessageHandler responds to messages.
  78. *
  79. * @type {module:server.MessageHandler}
  80. */
  81. this.messageHandler = new MessageHandler(this);
  82. /**
  83. * The main model for views.
  84. *
  85. * @type {module:server.ViewSpace}
  86. */
  87. this.viewspace = new ViewSpace(this.messageHandler);
  88. /**
  89. * The switchboard allows communication with clients
  90. *
  91. * @type {module:server.Switchboard}
  92. */
  93. this.switchboard = new Switchboard(this, this.namespace, this.settings.clientLimit);
  94. }
  95. /**
  96. * Add a static route to the router.
  97. *
  98. * @memberof module:server.Application
  99. *
  100. * @param {string} staticDir - The path to the static directory to add
  101. */
  102. addStaticDirectory(staticDir) {
  103. addStaticDirectory(this.router, staticDir);
  104. }
  105. /**
  106. * Start the server on the given hostname and port.
  107. *
  108. * @memberof module:server.Application
  109. *
  110. * @param {number} [port=9000] - Valid port number on which to listen.
  111. * @param {string} [host=getLocalIP()] - IP address or hostname on which to
  112. * listen.
  113. * @see module:server.Application~getLocalIP
  114. */
  115. listen(port = 9000, host = '0.0.0.0') {
  116. listen(this.httpServer, host, port);
  117. }
  118. /**
  119. * Remove the given item from the workspace.
  120. *
  121. * @param {module:server.ServerItem} item - Item to remove.
  122. */
  123. removeItem(item) {
  124. this.workspace.removeItem(item);
  125. }
  126. /**
  127. * Spawn a new element with the given values in the workspace.
  128. *
  129. * @param {Object} values - Data describing the element to spawn.
  130. * @return {module:server.ServerElement} The newly spawned element.
  131. */
  132. spawnElement(values) {
  133. return this.workspace.spawnElement(values);
  134. }
  135. /**
  136. * Spawn a new image with the given values in the workspace.
  137. *
  138. * @param {Object} values - Data describing the image to spawn.
  139. * @return {module:server.ServerImage} The newly spawned image.
  140. */
  141. spawnImage(values) {
  142. return this.workspace.spawnImage(values);
  143. }
  144. /**
  145. * Spawn a new item with the given values in the workspace.
  146. *
  147. * @param {Object} itemdata - Data describing the item to spawn.
  148. * @return {module:server.ServerItem} The newly spawned item.
  149. */
  150. spawnItem(values) {
  151. return this.workspace.spawnItem(values);
  152. }
  153. /**
  154. * Spawn an object. Object type is determined by the
  155. * `type` key value of the argument object.
  156. *
  157. * @param {Object} itemdata - Data describing the object to spawn.
  158. * @return {module:server.ServerItem} The newly spawned object.
  159. */
  160. spawn(values) {
  161. switch (values.type) {
  162. case 'item':
  163. return this.spawnItem(values);
  164. case 'item/image':
  165. return this.spawnImage(values);
  166. case 'item/element':
  167. return this.spawnElement(values);
  168. default:
  169. return this.spawnItem(values);
  170. }
  171. }
  172. /**
  173. * Create a group for existing items in the workspace.
  174. * A group allows to interact with several elements simultaneously.
  175. *
  176. * @param {object} values properties for the group
  177. */
  178. createItemGroup(values) {
  179. return this.workspace.createItemGroup(values);
  180. }
  181. /**
  182. * Create a group for views in the viewspace. The views in a group will be
  183. * able to perform multi-device gestures together.
  184. */
  185. createViewGroup() {
  186. return this.viewspace.createViewGroup();
  187. }
  188. /**
  189. * Send Message to all clients to dispatch user-defined action.
  190. *
  191. * @param {string} action name of the user-defined action.
  192. * @param {object} payload argument of the user-defined action function.
  193. */
  194. dispatch(action, payload) {
  195. this.workspace.namespace.emit(Message.DISPATCH, { action, payload });
  196. }
  197. }
  198. Object.assign(Application.prototype, EventEmitter.prototype);
  199. /**
  200. * The default values for an Application.
  201. *
  202. * @type {object}
  203. */
  204. Application.DEFAULTS = Object.freeze({
  205. applySmoothing: true,
  206. backgroundImage: undefined,
  207. clientLimit: 10,
  208. clientScripts: undefined,
  209. color: undefined,
  210. maximizeCanvas: true,
  211. shadows: false,
  212. status: false,
  213. stylesheets: undefined,
  214. });
  215. module.exports = Application;