Source: corelink.browser.lib.js

  1. /* eslint-disable prefer-const */
  2. /* eslint-disable no-unused-vars */
  3. /* eslint-disable max-len */
  4. /* eslint-disable func-names */
  5. /* eslint-disable no-async-promise-executor */
  6. /* eslint-disable no-restricted-syntax */
  7. /**
  8. * @file Browser client library for Corelink
  9. * @author Robert Pahle, Ted Wong
  10. * @version V3.0.0.0
  11. */
  12. // V3.0.0.0 support new data protocol
  13. // V2.0.0.0 self building version based on the corelink.lib.js library
  14. // V1.0.0.0 initial version
  15. // test for nodejs
  16. if (typeof window === 'undefined') { throw new Error('This library is designed to run in a browser.') }
  17. // test if namespace is already used
  18. // eslint-disable-next-line no-undef
  19. if (typeof window.corelink !== 'undefined') { throw new Error('The corelink namespace is already registered.') }
  20. // Main Namespace
  21. const corelink = {};
  22. (function () {
  23. // set debug to true for additional error reporting
  24. let debug = true
  25. // const headerSize = Buffer.alloc(6)
  26. let attempts = 0
  27. let connected = false
  28. const receiverStream = {}
  29. const senderStreams = []
  30. const allowedStreams = []
  31. let token = null
  32. let sourceIP = null
  33. let connCredentials = null
  34. let connConfig = null
  35. let dataCb = null
  36. let receiverCb = null
  37. let senderCb = null
  38. let staleCb = null
  39. let droppedCb = null
  40. let closeCb = null
  41. /**
  42. * Helper function to create random exponential delay to reconnect
  43. * @param {string} data JSON string to decode.
  44. * @returns {object} Javascript object with the parsed data
  45. * @private
  46. * @module parseJson
  47. */
  48. function generateInterval(k) {
  49. let maxInterval = ((2 ** k) - 1) * 1000
  50. if (maxInterval > 30 * 1000) {
  51. maxInterval = 30 * 1000
  52. }
  53. return Math.random() * maxInterval
  54. }
  55. // helper function to convert buffer to string
  56. function ab2str(buf) {
  57. return String.fromCharCode.apply(null, new Uint8Array(buf))
  58. }
  59. /**
  60. * Helper function to parse JSON
  61. * @param {string} data JSON string to decode.
  62. * @returns {object} Javascript object with the parsed data
  63. * @private
  64. * @module parseJson
  65. */
  66. function parseJson(data) {
  67. return (new Promise((resolve, reject) => {
  68. try {
  69. const parsedData = JSON.parse(`[${data.toString().replace(/}{/g, '},{')}]`)
  70. resolve(parsedData)
  71. } catch (e) {
  72. reject(new Error(`Received message not a proper JSON: ${data.toString()}`))
  73. }
  74. }))
  75. }
  76. // all control stream functions
  77. const control = {}
  78. control.client = null
  79. control.ID = 0
  80. control.connect = async (IP, port) => (new Promise(async (resolve, reject) => {
  81. try {
  82. // eslint-disable-next-line no-undef
  83. control.client = new WebSocket(`wss://${IP}:${port}/`)
  84. } catch (e) {
  85. reject(e)
  86. }
  87. control.client.binaryType = 'arraybuffer'
  88. control.client.onopen = () => {
  89. resolve()
  90. }
  91. control.request = async (data) => {
  92. if (debug) console.log('request', data)
  93. // eslint-disable-next-line no-shadow
  94. return (new Promise(async (resolve, reject) => {
  95. const wdata = data
  96. wdata.ID = control.ID
  97. control.ID += 1
  98. let retries = 5
  99. control.client.addEventListener('message', async (content) => { // it should be data instead of message (Abhishek Khanna), Rob : please review
  100. const parsed = await parseJson(content.data).catch((e) => reject(e))
  101. console.log('parsed all', parsed)
  102. for (let i = 0; i < parsed.length; i += 1) {
  103. retries -= 1
  104. if ('ID' in parsed[i] && parsed[i].ID === wdata.ID) {
  105. delete parsed[i].ID
  106. if (debug) console.log('parsed', parsed[i])
  107. if ('statusCode' in parsed[i]) {
  108. if (parsed[i].statusCode === 0) {
  109. resolve(parsed[i])
  110. break
  111. } if ('message' in parsed[i]) reject(new Error(`${parsed[i].message} (${parsed[i].statusCode})`))
  112. else reject(new Error(`Error (${parsed[i].statusCode}) with out specific message returned.`))
  113. } reject(new Error('Status code or function not found in answer.'))
  114. }
  115. if (retries === 0) reject(new Error('Server did not responded with an answer.'))
  116. }
  117. })
  118. control.client.send(JSON.stringify(wdata))
  119. if (debug) console.log('request sent:', JSON.stringify(wdata))
  120. }))
  121. }
  122. /**
  123. *Call the on function with either data or close function.
  124. * @param {string} data The module is defined to parse JSON data. The client checks for the
  125. 'function' in data. The event 'update' or 'status' will be triggered
  126. and accordingly 'receiver_cb' or 'stale_cb' will be checked for
  127. null values.
  128. * @param {string} close The callback function close_cb is checked for null values and close_cb()
  129. is called
  130. * @module on
  131. */
  132. // set callbacks
  133. control.client.addEventListener('message', async (data) => {
  134. const parsedJson = await parseJson(data.data)
  135. if (debug) console.log('stream.on', parsedJson)
  136. for (let i = 0; i < parsedJson.length; i += 1) {
  137. const parsedData = parsedJson[i]
  138. if ('function' in parsedData) {
  139. switch (parsedData.function) {
  140. case 'update':
  141. if (receiverCb != null) {
  142. delete parsedData.function
  143. receiverCb(parsedData)
  144. } else console.log('No receiver update callback provided.')
  145. break
  146. case 'subscriber':
  147. if (senderCb != null) {
  148. delete parsedData.function
  149. senderCb(parsedData)
  150. } else console.log('No sender update callback provided.')
  151. break
  152. case 'stale':
  153. if (staleCb != null) {
  154. delete parsedData.function
  155. staleCb(parsedData)
  156. } else console.log('No stale update callback provided.')
  157. break
  158. case 'dropped':
  159. if (droppedCb != null) {
  160. delete parsedData.function
  161. droppedCb(parsedData)
  162. } else console.log('No dropped update callback provided.')
  163. break
  164. default:
  165. console.log('Unknown callback. Maybe this library is outdated?')
  166. }
  167. }
  168. }
  169. })
  170. // make connect attemps and manage exponential backoff in connection attempts
  171. function reconnect() {
  172. control.connect(connCredentials, connConfig).catch((err) => {
  173. console.log('Connection failed.', err)
  174. })
  175. }
  176. control.client.onclose = () => {
  177. console.log('reconnect: ', connConfig.autoReconnect)
  178. if (connConfig.autoReconnect) {
  179. if (closeCb != null) closeCb()
  180. else console.log('No close connection callback provided.')
  181. console.log(`Control connection lost: Retrying websocket (${attempts})`)
  182. const time = generateInterval(attempts)
  183. console.log(`Waiting ${time}ms to reconnect.`)
  184. setTimeout(() => {
  185. attempts += 1
  186. reconnect(attempts)
  187. }, time)
  188. } else
  189. if (closeCb != null) closeCb()
  190. else {
  191. console.log('No close connection callback provided.')
  192. }
  193. }
  194. }))
  195. async function setupSender(streamID = null, port = null) {
  196. return (new Promise(((resolve, reject) => {
  197. if (debug) console.log('setting up data WS', 'streamID', streamID, 'port', port)
  198. try {
  199. // eslint-disable-next-line no-undef
  200. senderStreams[streamID].client = new WebSocket(`wss://${connConfig.ControlIP}:${port}/`)
  201. } catch (e) {
  202. reject(e)
  203. }
  204. senderStreams[streamID].client.binaryType = 'arraybuffer'
  205. senderStreams[streamID].client.addEventListener('open', () => {
  206. // if ((streamID != null) && (port != null)) receiverSetup(streamID, port)
  207. resolve(true)
  208. })
  209. senderStreams[streamID].client.addEventListener('error', (err) => {
  210. reject(err)
  211. })
  212. })))
  213. }
  214. async function setupReceiver(streamID = null, port = null) {
  215. return (new Promise(((resolve, reject) => {
  216. if (debug) console.log('setting up data WS', 'streamID', streamID, 'port', port)
  217. try {
  218. // eslint-disable-next-line no-undef
  219. receiverStream.client = new WebSocket(`wss://${connConfig.ControlIP}:${port}/`)
  220. } catch (e) {
  221. reject(e)
  222. }
  223. receiverStream.client.binaryType = 'arraybuffer'
  224. receiverStream.client.addEventListener('open', () => {
  225. // adding header
  226. const buffersize = 8
  227. const buffer = new ArrayBuffer(buffersize)
  228. new DataView(buffer).setUint16(0, 0, true)
  229. new DataView(buffer).setUint16(2, 0, true)
  230. new DataView(buffer).setUint32(4, streamID, true)
  231. console.log('bufferlength to send:', buffer.byteLength)
  232. receiverStream.client.send(buffer)
  233. resolve(true)
  234. })
  235. receiverStream.client.addEventListener('error', (err) => {
  236. reject(err)
  237. })
  238. receiverStream.client.addEventListener('message', (message) => {
  239. if (dataCb != null) {
  240. let header
  241. const view = new DataView(message.data)
  242. const headSize = view.getUint16(0, true)
  243. // const dataSize = view.getUint16(2, true)
  244. const sourceID = view.getUint16(4, true)
  245. const data = message.data.slice(8 + headSize)
  246. if (headSize > 0) {
  247. header = ab2str(message.data.slice(8, 8 + headSize))
  248. try {
  249. header = JSON.parse(header)
  250. } catch (e) {
  251. console.log(`Received header not a proper JSON: ${message.toString()}`)
  252. return
  253. }
  254. }
  255. dataCb(sourceID, data, header)
  256. } else console.log('Data received, but no callback for data available.')
  257. })
  258. })))
  259. }
  260. /**
  261. *Requests a 'sender' function from Corelink protocol which needs input like
  262. *workspace,protocol,Source IP, token etc. The client awaits to read, after
  263. *which it parses the JSON data. The statusCode checks for streamID,port,MTU
  264. *and message in the content. After getting values for workspace,protocol,type
  265. *and metadata, UDP protocol is set up. The function expects and object with the
  266. *following parameters:
  267. *@param {String} workspace Workspaces are used to separate out groups of streams
  268. so that several independent groups of researchers can work together.
  269. *@param {String} protocol Protocol for the stream to use (UDP/TCP/WS)
  270. *@param {String} type type information can be freely selected.But well known
  271. formats will be published.At the moment those are 3d, audio.
  272. *@param {String} metadata Information about data
  273. *@param {String} from sender information
  274. *@module createSender
  275. */
  276. async function createSender(options) {
  277. return (new Promise(async (resolve, reject) => {
  278. const workOptions = options
  279. // checking inputs
  280. if (!('workspace' in workOptions)) reject(new Error('Workspace not found.'))
  281. if (!('type' in workOptions)) reject(new Error('Type not defined.'))
  282. workOptions.protocol = 'ws'
  283. if (!('metadata' in workOptions)) workOptions.metadata = ''
  284. if (!('from' in workOptions)) workOptions.from = ''
  285. if (!('alert' in workOptions)) workOptions.alert = false
  286. const request = `{"function":"sender","workspace":"${workOptions.workspace}","proto":"${workOptions.protocol}","IP":"${sourceIP}","port":0,"alert":${workOptions.alert},"type":"${workOptions.type}","meta":${JSON.stringify(workOptions.metadata)},"from":"${workOptions.from}","token":"${token}"}`
  287. const content = await control.request(JSON.parse(request)).catch((e) => reject(e))
  288. if (debug) console.log('CreateSender Result:', content)
  289. if ('streamID' in content) senderStreams[content.streamID] = []
  290. else reject(new Error('StreamID not found.'))
  291. if ('port' in content) senderStreams[content.streamID].port = content.port
  292. else reject(new Error('Target port not found.'))
  293. if ('MTU' in content) senderStreams[content.streamID].MTU = content.MTU
  294. else reject(new Error('Target MTU not found.'))
  295. senderStreams[content.streamID].workspace = workOptions.workspace
  296. senderStreams[content.streamID].protocol = workOptions.protocol
  297. senderStreams[content.streamID].type = workOptions.type
  298. senderStreams[content.streamID].metadata = workOptions.metadata
  299. senderStreams[content.streamID].from = workOptions.from
  300. senderStreams[content.streamID].alert = workOptions.alert
  301. if (workOptions.protocol === 'ws') await setupSender(content.streamID, content.port).catch((err) => { console.log(err) })
  302. if (debug) console.log('createSender returns:', content.streamID)
  303. resolve(content.streamID)
  304. }))
  305. }
  306. /**
  307. *It takes arameters streamID and port and requests a receiver function from
  308. *Corelink protocol which has inputs workspace name, streamID,source IP,token etc.
  309. *Further,it checks for statusCode,streamID,port,potocol,streamList,MTU. UDP
  310. *protocol is the set up on receiver's side. The function expects and object with the
  311. *following parameters:
  312. *@param {String} workspace Workspaces are used to separate out groups of streams
  313. so that several independent groups of researchers can work together.
  314. *@param {String} protocol Protocol for the stream to use (UDP/TCP/WS)
  315. *@param {String} streamIDs Allows a user to select streams to Subscribe to.
  316. If StreamID is not given, all streams are sent
  317. *@param {String} type type information can be freely selected.But well known
  318. formats will be published.At the moment those are 3d, audio.
  319. *@param {String} alert alert is an optional argument that false,if new streams
  320. of this type are registered will send the streamID’s to the client via server
  321. initiated controlFunction (default is false)
  322. *@param {String} echo receive data that was sent by the same user (default is false)
  323. *@param {String} receiverID Receiver streamID to reuse
  324. *@module createReceiver
  325. */
  326. async function createReceiver(options) {
  327. return (new Promise(async (resolve, reject) => {
  328. // checking inputs
  329. const workOptions = options
  330. if (!('workspace' in workOptions)) reject(new Error('Workspace not found.'))
  331. workOptions.protocol = 'ws'
  332. if (!('streamIDs' in workOptions)) workOptions.streamIDs = []
  333. if (!('type' in workOptions)) workOptions.type = []
  334. if (!('alert' in workOptions)) workOptions.alert = false
  335. if (!('echo' in workOptions)) workOptions.echo = false
  336. if ('receiverID' in workOptions) workOptions.receiverID = `,"receiverID":"${workOptions.receiverID}"`
  337. else workOptions.receiverID = ''
  338. const request = `{"function":"receiver","workspace":"${workOptions.workspace}"${workOptions.receiverID},"streamIDs":${JSON.stringify(workOptions.streamIDs)},"proto":"${workOptions.protocol}","IP":"${sourceIP}","port":0,"echo":${workOptions.echo},"alert":${workOptions.alert},"type":${JSON.stringify(workOptions.type)},"token":"${token}"}`
  339. if (debug) console.log('createReceiver request', request)
  340. const content = await control.request(JSON.parse(request)).catch((e) => reject(e))
  341. if (debug) console.log('create Receiver content', content)
  342. if ('streamID' in content) receiverStream.streamID = content.streamID
  343. else reject(new Error('StreamID not found.'))
  344. if (debug) console.log(`createReceiver port: ${content.port}`)
  345. if ('port' in content) receiverStream.port = content.port
  346. else reject(new Error('Target port not found.'))
  347. if ('proto' in content) receiverStream.proto = content.proto
  348. else reject(new Error('Target proto not found.'))
  349. if ('streamList' in content) receiverStream.streamList = content.streamList
  350. else reject(new Error('Target streamList not found.'))
  351. if ('MTU' in content) receiverStream.MTU = content.MTU
  352. else reject(new Error('Target MTU not found.'))
  353. receiverStream.workspace = workOptions.workspace
  354. receiverStream.protocol = workOptions.protocol
  355. receiverStream.type = workOptions.type
  356. receiverStream.alert = workOptions.alert
  357. receiverStream.echo = workOptions.echo
  358. for (const stream in content.streamList) {
  359. if (!allowedStreams.includes(content.streamList[stream].streamID)) {
  360. allowedStreams.push(content.streamList[stream].streamID)
  361. }
  362. }
  363. await setupReceiver(content.streamID, content.port).catch((e) => console.log(e))
  364. resolve(content.streamList)
  365. }))
  366. }
  367. /**
  368. *The function first checks if the array of streamIDs is not empty as well as
  369. *the protocol set up is UDP. After the header and packet are defined, the
  370. *message is send at the appropriate port number and target IP.
  371. *@param {String} streamID Allows a user to select streams to Subscribe to.
  372. If StreamID is not given, all streams are sent
  373. *@param {String} data Refers to the data to be sent.
  374. *@param {object} header Is a JSON object that will be placed in the header in general these could be arbitrary information,
  375. however, several tags will be decoded
  376. - stamp: the server will fill in the server time stamp
  377. - limit: array of receiver ids, the server will send the packet only to these id's
  378. *@module send
  379. */
  380. function send(streamID, data, header) {
  381. if ((typeof senderStreams[streamID] !== 'undefined') && (senderStreams[streamID].protocol === 'ws')) {
  382. let workingHeader
  383. if (typeof header === 'object') workingHeader = JSON.stringify(header)
  384. else workingHeader = ''
  385. // adding header
  386. const buffersize = 8 + workingHeader.length + data.byteLength
  387. const buffer = new ArrayBuffer(buffersize)
  388. new DataView(buffer).setUint16(0, 0, true)
  389. new DataView(buffer).setUint16(2, data.byteLength, true)
  390. new DataView(buffer).setUint32(4, streamID, true)
  391. const bufView = new Uint8Array(buffer)
  392. const dataView = new Uint8Array(data)
  393. // add json header to send buffer
  394. for (let i = 0, strLen = workingHeader.length; i < strLen; i += 1) {
  395. bufView[i + 8] = workingHeader.charCodeAt(i)
  396. }
  397. // add the data to buffer
  398. for (let i = 0; i < dataView.byteLength; i += 1) {
  399. bufView[i + 8 + workingHeader.length] = dataView[i]
  400. }
  401. try {
  402. senderStreams[streamID].client.send(buffer)
  403. } catch (e) {
  404. console.log('socket error', e)
  405. }
  406. if (debug) console.log(`sent: ID${streamID} h${0},d${data.byteLength}`)
  407. }
  408. }
  409. // Begin Common Functions
  410. /**
  411. *set the debug variable to enable enhanced debug output
  412. *@param {boolean} flag A boolen that will set the enhanced debug functionality
  413. *@module setDebug
  414. */
  415. function setDebug(flag) {
  416. debug = flag
  417. }
  418. /**
  419. *Login to the corelink server
  420. *with username and password or token
  421. *If the credentials are incorrect then it throws an error.
  422. *The module ends with a
  423. Promise. A promise is an object which can be returned synchronously from an asynchronous function.
  424. *@param {String} credentials Refers to all login credentials like username,password and token.
  425. *@module login
  426. */
  427. async function login(credentials) {
  428. return (new Promise(async (resolve, reject) => {
  429. let request = ''
  430. if ((typeof credentials.username === 'undefined')
  431. && (typeof credentials.password === 'undefined')) {
  432. if ((typeof credentials.token === 'undefined')) reject(new Error('Credentials not found.'))
  433. else request = `{"function":"auth","token":"${credentials.token}"}`
  434. } else request = `{"function":"auth","username":"${credentials.username}","password":"${credentials.password}"}`
  435. if (debug) console.log('Login ', request)
  436. const content = await control.request(JSON.parse(request)).catch((e) => reject(e))
  437. if (!content) return
  438. if (debug) console.log('Login result ', content)
  439. if ('token' in content) token = content.token
  440. else reject(new Error('Token not found.'))
  441. if ('IP' in content) sourceIP = content.IP
  442. else reject(new Error('SourceIP not found.'))
  443. resolve(true)
  444. }))
  445. }
  446. /**
  447. *Connects with client at the specified Port and IP number after verifying login credentials
  448. *like username,password and token defined in the 'login' function.
  449. *If the credentials are incorrect then it throws an error.
  450. *The 'await' keyword waits for a value of ControlPort and ControlIP. The module ends with a
  451. Promise. A promise is an object which can be returned synchronously from an asynchronous function.
  452. *@param {String} credentials Refers to all login credentials like username,password and token.
  453. *@param {String} config Refers to connection configuration like ControlPort and ControlIP
  454. *@module connect
  455. */
  456. async function connect(credentials, config) {
  457. return (new Promise(async (resolve, reject) => {
  458. if (debug) console.log('Connecting...')
  459. connConfig = config
  460. connCredentials = credentials
  461. // dns.lookup(connConfig.ControlIP, (err, address) => {
  462. // connConfig.ControlIP = address
  463. // if (debug) console.log('address:', address)
  464. // })
  465. if (debug) console.log('Target IP:', connConfig.ControlIP, 'target port :', connConfig.ControlPort)
  466. let conn
  467. connected = true
  468. // connect the client
  469. await control.connect(connConfig.ControlIP, connConfig.ControlPort).catch((e) => {
  470. connected = false
  471. if (debug) console.log('not connectd', e)
  472. reject(e)
  473. })
  474. if (connected) {
  475. // check if there is a token and if it is valid
  476. // checkToken()
  477. // login if we dont have a good token
  478. if (token == null) conn = await login(credentials).catch((e) => reject(e))
  479. // if all streams are still valid
  480. // if (conn) checkConnections()
  481. }
  482. if (conn) {
  483. attempts = 1
  484. resolve(conn)
  485. } else reject(new Error('Problem loggin in'))
  486. }))
  487. }
  488. /**
  489. *Queries the corelink relay for available User
  490. *@param {string} config the parameter name that should be set
  491. *@param {string} context context that this cofiguration applies to global (server global
  492. settings), profile (global user specific settings), app (app global
  493. settings), or private (app user specific settings), if omitted or empty
  494. it is a global configuration parameter
  495. *@param {string} app name of the app that this cofiguration applies to, can be omitted or
  496. empty for global or profile configuration parameters
  497. *@param {string} plugin name of the plugin that this cofiguration applies to, can be omitted or
  498. empty for global or profile configuration parameters
  499. *@param {string} user name of the user that this cofiguration applies to, can be omitted
  500. or empty for global or app configuration parameters, only an admin can
  501. set this parameter, otherwise the logged in username will be taken.'
  502. *@param {string} value value to apply to the parameter, all parameters are stored as strings,
  503. but are applied in the defined type',
  504. *@exports setConfig
  505. *@module setConfig
  506. */
  507. async function setConfig(options) {
  508. return (new Promise(async (resolve, reject) => {
  509. const request = options
  510. if (!('config' in request)) { reject(new Error('Name of the configuration parameter required.')); return }
  511. if (!('value' in request)) { reject(new Error('Context of the configuration parameter required.')); return }
  512. if (!('context' in request) || (request === '')) { request.context = 'global' }
  513. if (!('app' in request)) { request.app = '' }
  514. if (!('plugin' in request)) { request.plugin = '' }
  515. if (!('user' in request)) { request.user = '' }
  516. request.function = 'setConfig'
  517. request.token = token
  518. console.log(request)
  519. await control.request(request).catch((e) => reject(e))
  520. resolve(true)
  521. }))
  522. }
  523. // User management functions
  524. /**
  525. *Queries the corelink relay to add a user
  526. *@param {String} username name that you would like to add
  527. *@param {String} password password of the user that you would like to add
  528. *@param {String} email emailid of the user that you would like to add
  529. *@param {String} first name that you would like to add
  530. *@param {String} last name that you would like to add
  531. *@param {boolean} admin admin property of the new user
  532. *@exports addUser
  533. *@module addUser
  534. */
  535. async function addUser(options) {
  536. return (new Promise(async (resolve, reject) => {
  537. const workOptions = options
  538. // checking inputs
  539. if (!('username' in workOptions)) {
  540. reject(new Error('user not found.'))
  541. return
  542. }
  543. if (!('password' in workOptions)) {
  544. reject(new Error('password not found.'))
  545. return
  546. }
  547. if (!('email' in workOptions)) {
  548. reject(new Error('email name not defined.'))
  549. return
  550. }
  551. if (!('first' in workOptions)) workOptions.first = workOptions.username
  552. if (!('last' in workOptions)) workOptions.last = workOptions.username
  553. if (!('admin' in workOptions)) workOptions.admin = false
  554. const request = `{"function":"addUser","username":"${workOptions.username}","password":"${workOptions.password}","first":"${workOptions.first}",
  555. "email":"${workOptions.email}","last":"${workOptions.last}","admin":${workOptions.admin},"token":"${token}"}`
  556. await control.request(JSON.parse(request)).catch((e) => reject(e))
  557. resolve(true)
  558. }))
  559. }
  560. /**
  561. *Queries the corelink relay to change the password of the user
  562. *@param {String} password password that need to be change
  563. *@exports password
  564. *@module password
  565. */
  566. async function password(options) {
  567. return (new Promise(async (resolve, reject) => {
  568. const workOptions = options
  569. // checking inputs
  570. if (!('password' in workOptions)) {
  571. reject(new Error('password not found.'))
  572. return
  573. }
  574. const request = `{"function":"password","password":"${workOptions.password}","token":"${token}"}`
  575. await control.request(JSON.parse(request)).catch((e) => reject(e))
  576. resolve(true)
  577. }))
  578. }
  579. /**
  580. *Queries the corelink relay to remove a user
  581. *@param {String} username user name that you would like to remove
  582. *@exports rmUser
  583. *@module rmUser
  584. */
  585. async function rmUser(options) {
  586. return (new Promise(async (resolve, reject) => {
  587. const workOptions = options
  588. // checking inputs
  589. if (!('username' in workOptions)) {
  590. reject(new Error('username not given.'))
  591. return
  592. }
  593. const request = `{"function":"rmUser","username":"${workOptions.username}","token":"${token}"}`
  594. await control.request(JSON.parse(request)).catch((e) => reject(e))
  595. resolve(true)
  596. }))
  597. }
  598. /**
  599. *Queries the corelink relay for available User
  600. *@returns {Array} Array of User, empty array if no User are available
  601. *@exports listUsers
  602. *@module listUsers
  603. */
  604. async function listUsers() {
  605. return (new Promise(async (resolve, reject) => {
  606. const request = `{"function":"listUsers","token":"${token}"}`
  607. const content = await control.request(JSON.parse(request)).catch((e) => reject(e))
  608. if ('userList' in content) resolve(content.userList)
  609. else reject(new Error('Users not found.'))
  610. }))
  611. }
  612. // Group management functions:
  613. /**
  614. *Queries the corelink relay to add a user to group(login user should either admin/ owner)
  615. *@param {String} User name that you would like to user to group
  616. *@param {String} group Groupname that will have a new user
  617. *@module addUserGroup
  618. */
  619. async function addUserGroup(options) {
  620. return (new Promise(async (resolve, reject) => {
  621. const workOptions = options
  622. // checking inputs
  623. if (!('username' in workOptions)) reject(new Error('user not found.'))
  624. else if (!('group' in workOptions)) reject(new Error('group not found.'))
  625. else {
  626. const request = `{"function":"addUserGroup","username":"${workOptions.username}","group":"${workOptions.group}","token":"${token}"}`
  627. await control.request(JSON.parse(request)).catch((e) => reject(e))
  628. }
  629. resolve(true)
  630. }))
  631. }
  632. /**
  633. *Queries the corelink relay to remove user from group(login user should either admin/ owner)
  634. *@param {String} User name that you would like to user to group
  635. *@param {String} group Groupname that will have a new user
  636. *@module rmUserGroup
  637. */
  638. async function rmUserGroup(options) {
  639. return (new Promise(async (resolve, reject) => {
  640. const workOptions = options
  641. // checking inputs
  642. if (!('username' in workOptions)) reject(new Error('user not found.'))
  643. else if (!('group' in workOptions)) reject(new Error('group not found.'))
  644. else {
  645. const request = `{"function":"rmUserGroup","user":"${workOptions.username}","group":"${workOptions.group}","token":"${token}"}`
  646. await control.request(JSON.parse(request)).catch((e) => reject(e))
  647. }
  648. resolve(true)
  649. }))
  650. }
  651. /**
  652. *Queries the corelink relay for available group
  653. *@returns {Array} Array of group, empty array if no grou[] are available
  654. *@exports listGroups
  655. *@module listGroups
  656. */
  657. async function listGroups() {
  658. return (new Promise(async (resolve, reject) => {
  659. const request = `{"function":"listGroups","token":"${token}"}`
  660. const content = await control.request(JSON.parse(request)).catch((e) => reject(e))
  661. if ('groupList' in content) resolve(content.groupList)
  662. else reject(new Error(' groupList not found.'))
  663. }))
  664. }
  665. /**
  666. *Queries the corelink relay to add a Group
  667. *@param {String} Group Group name that you would like to add
  668. *@param {String} owner owner name of the new group
  669. *@exports addGroup
  670. *@module addGroup
  671. */
  672. async function addGroup(options) {
  673. return (new Promise(async (resolve, reject) => {
  674. const workOptions = options
  675. if (!('group' in workOptions)) {
  676. reject(new Error('new groupname not found.'))
  677. return
  678. }
  679. const request = `{"function":"addGroup","group":"${workOptions.group}","token":"${token}"}`
  680. await control.request(JSON.parse(request)).catch((e) => reject(e))
  681. resolve(true)
  682. }))
  683. }
  684. /**
  685. *Queries the corelink relay to remove a group
  686. *@param {String} Group group name that you would like to remove
  687. *@exports rmGroup
  688. *@module rmGroup
  689. */
  690. async function rmGroup(options) {
  691. return (new Promise(async (resolve, reject) => {
  692. const workOptions = options
  693. // checking inputs
  694. if (!('group' in workOptions)) {
  695. reject(new Error('remove group not found.'))
  696. return
  697. }
  698. const request = `{"function":"rmGroup","group":"${workOptions.group}","token":"${token}"}`
  699. await control.request(JSON.parse(request)).catch((e) => reject(e))
  700. resolve(true)
  701. }))
  702. }
  703. /**
  704. *Queries the corelink relay to change the ownerhip of group
  705. *@param {String} Group group name that you would like to change owner
  706. *@param {String} username user name that you would like to be owner
  707. *@exports changeOwner
  708. *@module changeOwner
  709. */
  710. async function changeOwner(options) {
  711. return (new Promise(async (resolve, reject) => {
  712. const workOptions = options
  713. // checking inputs
  714. if (!('group' in workOptions)) {
  715. reject(new Error('groupname not found.'))
  716. return
  717. }
  718. if (!('username' in workOptions)) {
  719. reject(new Error('username not found.'))
  720. return
  721. }
  722. const request = `{"function":"changeOwner","group":"${workOptions.group}","username":"${workOptions.username}","token":"${token}"}`
  723. await control.request(JSON.parse(request)).catch((e) => reject(e))
  724. resolve(true)
  725. }))
  726. }
  727. // Workspace management functions:
  728. /**
  729. *Queries the corelink relay for available workspaces
  730. *@returns {Array} Array of workspaces, empty array if no workspaces are available
  731. *@exports listWorkspaces
  732. *@module listWorkspaces
  733. */
  734. async function listWorkspaces() {
  735. return (new Promise(async (resolve, reject) => {
  736. const request = `{"function":"listWorkspaces","token":"${token}"}`
  737. const content = await control.request(JSON.parse(request)).catch((e) => reject(e))
  738. if ('workspaceList' in content) resolve(content.workspaceList)
  739. else reject(new Error('workspaceList not found.'))
  740. }))
  741. }
  742. /**
  743. *Queries the corelink relay to add a workspace
  744. *@param {String} workspace workspace name that you would like to add
  745. *@exports addWorkspace
  746. *@module addWorkspace
  747. */
  748. async function addWorkspace(workspace) {
  749. return (new Promise(async (resolve, reject) => {
  750. const request = `{"function":"addWorkspace","workspace":"${workspace}","token":"${token}"}`
  751. await control.request(JSON.parse(request)).catch((e) => reject(e))
  752. resolve(true)
  753. }))
  754. }
  755. /**
  756. *Queries the corelink relay to remove a workspaces
  757. *@param {String} workspace workspace name that you would like to remove
  758. *@exports rmWorkspace
  759. *@module rmWorkspace
  760. */
  761. async function rmWorkspace(workspace) {
  762. return (new Promise(async (resolve, reject) => {
  763. const request = `{"function":"rmWorkspace","workspace":"${workspace}","token":"${token}"}`
  764. await control.request(JSON.parse(request)).catch((e) => reject(e))
  765. resolve(true)
  766. }))
  767. }
  768. /**
  769. *Queries the corelink relay to set a default workspace
  770. *@param {String} workspace workspace name that you would like to set as default
  771. *@exports setDefaultWorkspace
  772. *@module setDefaultWorkspace
  773. */
  774. async function setDefaultWorkspace(workspace) {
  775. return (new Promise(async (resolve, reject) => {
  776. const request = `{"function":"setDefaultWorkspace","workspace":"${workspace}","token":"${token}"}`
  777. await control.request(JSON.parse(request)).catch((e) => reject(e))
  778. resolve(true)
  779. }))
  780. }
  781. /**
  782. *Queries the corelink relay to get the current default workspace
  783. *@returns {String} workspace name that is currently set as default
  784. *@exports getDefaultWorkspace
  785. *@module getDefaultWorkspace
  786. */
  787. async function getDefaultWorkspace() {
  788. return (new Promise(async (resolve, reject) => {
  789. const request = `{"function":"getDefaultWorkspace","token":"${token}"}`
  790. const content = await control.request(JSON.parse(request)).catch((e) => reject(e))
  791. if ('workspace' in content) resolve(content.workspace)
  792. else reject(new Error('No default workspace found.'))
  793. }))
  794. }
  795. // introspect functions functions
  796. /**
  797. *Queries the corelink relay for available functions
  798. *@returns {Array} Array of functions that the server has available
  799. *@exports listFunctions
  800. *@module listFunctions
  801. */
  802. async function listFunctions() {
  803. return (new Promise(async (resolve, reject) => {
  804. const request = {}
  805. request.function = 'listFunctions'
  806. request.token = token
  807. const content = await control.request(request).catch((e) => reject(e))
  808. if ('functionList' in content) resolve(content)
  809. else reject(new Error('functionList not found.'))
  810. }))
  811. }
  812. /**
  813. *Queries the corelink relay for available functions
  814. *@returns {object} the object has functionList which is an array of functions names that the
  815. server has available
  816. *@exports listServerFunctions
  817. *@module listServerFunctions
  818. */
  819. async function listServerFunctions() {
  820. return (new Promise(async (resolve, reject) => {
  821. const request = {}
  822. request.function = 'listServerFunctions'
  823. request.token = token
  824. const content = await control.request(request).catch((e) => reject(e))
  825. if ('functionList' in content) resolve(content)
  826. else reject(new Error('functionList not found.'))
  827. }))
  828. }
  829. /**
  830. *Queries a specific function to get its self descriptor
  831. *@param {String} options Options object, it has to have a function name
  832. *@returns {Object} JSON object that describes the function
  833. *@exports describeFunction
  834. *@module describeFunction
  835. */
  836. async function describeFunction(options) {
  837. return (new Promise(async (resolve, reject) => {
  838. // checking inputs
  839. if (typeof options !== 'object') reject(new Error('No object supplied.'))
  840. if (!('functionName' in options)) reject(new Error('No function name found.'))
  841. const request = {}
  842. request.function = 'describeFunction'
  843. request.functionName = options.functionName
  844. request.token = token
  845. const content = await control.request(request).catch((e) => reject(e))
  846. if ('description' in content) resolve(content)
  847. else reject(new Error('description not found.'))
  848. }))
  849. }
  850. /**
  851. *Queries a specific function to get its self descriptor
  852. *@param {String} options Options object, it has to have a server function name
  853. *@returns {Object} JSON object that describes the function
  854. *@exports describeServerFunction
  855. *@module describeServerFunction
  856. */
  857. async function describeServerFunction(options) {
  858. return (new Promise(async (resolve, reject) => {
  859. // checking inputs
  860. if (!('functionName' in options)) reject(new Error('No function name found.'))
  861. const request = {}
  862. request.function = 'describeServerFunction'
  863. request.functionName = options.functionName
  864. request.token = token
  865. const content = await control.request(request).catch((e) => reject(e))
  866. if ('description' in content) resolve(content)
  867. else reject(new Error('description not found.'))
  868. }))
  869. }
  870. /**
  871. *Get the receiverID of the current active receiver
  872. *@returns {String} receiverID Receiver streamID to remove streams from
  873. *@module getReceiverID
  874. */
  875. async function getReceiverID() {
  876. return receiverStream.streamID
  877. }
  878. /**
  879. *Queries the corelink relay for available functions
  880. *@param {array} workspaces an array of workspaces to list streams, omitted or empty array
  881. will list all workspaces that a user has access to
  882. *@param {array} types an array of types of streams to list
  883. *@returns {object} with senderStreams and receiverStreams arrays
  884. *@exports listStreams
  885. *@module listStreams
  886. */
  887. async function listStreams(options) {
  888. return (new Promise(async (resolve, reject) => {
  889. const request = options || {}
  890. request.function = 'listStreams'
  891. request.token = token
  892. if (!('workspaces' in request)) request.workspaces = []
  893. if (!('types' in request)) request.types = []
  894. const content = await control.request(request).catch((e) => reject(e))
  895. resolve(content)
  896. }))
  897. }
  898. /**
  899. *In the request made, subscribe function from Corelink protocol is called which
  900. *gets the value of streamID and token. Further,the streamList array is checked
  901. *to see if it has target streamList.
  902. *@param {String} options objet. Allows a user to select streams to subscribe to.
  903. The options object should hold and array of streamIDs. If streamIDs or options
  904. is not given, all streams are sent
  905. *@module subscribe
  906. */
  907. async function subscribe(options) {
  908. return (new Promise(async (resolve, reject) => {
  909. let workOptions
  910. if (typeof options === 'undefined') workOptions = {}
  911. else workOptions = options
  912. if (!('streamIDs' in workOptions)) workOptions.streamIDs = []
  913. const request = `{"function":"subscribe","receiverID":"${receiverStream.streamID}","streamIDs":${JSON.stringify(workOptions.streamIDs)},"token":"${token}"}`
  914. if (debug) console.log('subscribe request', request)
  915. const content = await control.request(JSON.parse(request)).catch((e) => reject(e))
  916. if (debug) console.log('subscribe json', content)
  917. if ('streamList' in content) receiverStream.streamList = content.streamList
  918. else reject(new Error('Target streamList not found.'))
  919. for (const stream in content.streamList) {
  920. if (!allowedStreams.includes(content.streamList[stream].streamID)) {
  921. allowedStreams.push(content.streamList[stream].streamID)
  922. }
  923. }
  924. if (debug) console.log('subscribe streamList', content.streamList)
  925. resolve(content.streamList)
  926. }))
  927. }
  928. /**
  929. *In the request made, subscribe function from Corelink protocol is called which
  930. *gets the value of streamID and token. Further,the streamList array is checked
  931. *to see if it has target streamList. The function expects an object with the
  932. *following parameters:
  933. *@param {array} streamIDs Allows a user to select streams to unsubscribe from.
  934. If streamIDs are not given or empty all are unsubscribed from
  935. *@module unsubscribe
  936. */
  937. async function unsubscribe(options) {
  938. return (new Promise(async (resolve, reject) => {
  939. let workOptions
  940. if (typeof options === 'undefined') workOptions = {}
  941. else workOptions = options
  942. if (!('streamIDs' in workOptions)) workOptions.streamIDs = []
  943. const request = `{"function":"unsubscribe","receiverID":${receiverStream.streamID},"streamIDs":${JSON.stringify(workOptions.streamIDs)},"token":"${token}"}`
  944. if (debug) console.log('unsubscribe request', request)
  945. const content = await control.request(JSON.parse(request)).catch((e) => reject(e))
  946. if (debug) console.log('unsubscribe json', content)
  947. for (let i = 0; i < allowedStreams.length; i += 1) {
  948. if (content.streamList.includes(allowedStreams[i])) delete allowedStreams[i]
  949. }
  950. resolve(content.streamList)
  951. }))
  952. }
  953. /**
  954. *Function to register event handlers for specific events
  955. *@param {array} type type of event:
  956. - receiver: calls back with new streams that are available
  957. - sender: calls back with new streams that are subscribed
  958. - stale: call back with streams that went stale for this receiver and can be unsubscribed
  959. - dropped: calls back with streams that dropped the subscription for a sender
  960. - close: the control connection was closed
  961. - data: calls back when data has arrived with (streamID, data, header)
  962. *@param {array} cb a function with the corresponding callback
  963. *@module on
  964. */
  965. const on = (async function on(type, cb) {
  966. switch (type) {
  967. case 'receiver':
  968. receiverCb = cb
  969. break
  970. case 'sender':
  971. senderCb = cb
  972. break
  973. case 'data':
  974. dataCb = cb
  975. break
  976. case 'stale':
  977. staleCb = cb
  978. break
  979. case 'dropped':
  980. droppedCb = cb
  981. break
  982. case 'close':
  983. closeCb = cb
  984. break
  985. default:
  986. break
  987. }
  988. })
  989. /**
  990. *Queries server functions that dont need special parameters
  991. *@param {object} options Options object,
  992. if generic is called the function name and the parameters need to be in a JSON object according to
  993. https://corelink.hpc.nyu.edu/documentation/server-documentation/client-initiated-control-functions
  994. *@returns {Object} JSON object that contains the function result
  995. *@exports generic
  996. *@module generic
  997. */
  998. async function generic(options) {
  999. return (new Promise(async (resolve, reject) => {
  1000. // checking inputs
  1001. const workOptions = options
  1002. if (typeof workOptions !== 'object') reject(new Error('No object supplied.'))
  1003. if (!('function' in workOptions)) reject(new Error('No function name'))
  1004. // adding token to request
  1005. if (!workOptions.token) workOptions.token = token
  1006. if (debug) console.log(workOptions)
  1007. const content = await control.request(workOptions).catch((e) => reject(e))
  1008. if (content) resolve(content)
  1009. else reject(new Error('Wrong Expresions.'))
  1010. }))
  1011. }
  1012. /**
  1013. *Requests a disconnect streams
  1014. *@param {String} workspaces Workspaces are used to separate out groups of
  1015. streams so that several independent groups of researchers can work together.
  1016. *@param {String} types disconnect only streams of a particular type.
  1017. *@param {String} streamIDs Allows a user to select streams to Subscribe to. If
  1018. StreamID is not given, all streams are sent, the streamIDs have to be numbers
  1019. *@returns {object} in the object there will be the array streamList
  1020. *@module disconnect
  1021. */
  1022. async function disconnect(options) {
  1023. return (new Promise(async (resolve, reject) => {
  1024. console.log('options', options)
  1025. const request = {}
  1026. request.types = []
  1027. request.workspaces = []
  1028. request.streamIDs = []
  1029. if (typeof options === 'object') {
  1030. if (('types' in options) && Array.isArray(options.types) && (options.types.length > 0)) request.types = request.types.concat(options.types)
  1031. if (('types' in options) && (typeof options.types === 'string')) request.types.push(options.types)
  1032. if (('workspaces' in options) && Array.isArray(options.workspaces) && (options.workspaces.length > 0)) request.workspaces = request.workspaces.concat(options.workspaces)
  1033. if (('workspaces' in options) && (typeof options.workspaces === 'string')) request.workspaces.push(options.workspaces)
  1034. if (('streamIDs' in options) && Array.isArray(options.streamIDs) && (options.streamIDs.length > 0)) request.streamIDs = request.streamIDs.concat(options.streamIDs)
  1035. if (('streamIDs' in options) && (typeof options.streamIDs === 'number')) request.streamIDs.push(options.streamIDs)
  1036. }
  1037. request.function = 'disconnect'
  1038. request.token = token
  1039. if (debug) console.log('disconnect request', request)
  1040. const content = await control.request(request).catch((e) => reject(e))
  1041. if (debug) console.log('disconnect content', content)
  1042. resolve(content)
  1043. }))
  1044. }
  1045. /**
  1046. *This module gets all local streamIDs and,checks if they are undefined and
  1047. pushes them. The connection then waits for a disconnect to occur.
  1048. *@param {String} resolve If all streamIDs are fetched successfully for a
  1049. successful disconnect
  1050. *@param {String} reject If all streamIDs are not fetched, an error message is displayed
  1051. *@module exit
  1052. */
  1053. async function exit() {
  1054. console.log('Trying to exit.')
  1055. return (new Promise((async (resolve, reject) => {
  1056. // get all local streamID's
  1057. const streamIDs = []
  1058. if (typeof receiverStream.streamID !== 'undefined') streamIDs.push(receiverStream.streamID)
  1059. for (const streamID in senderStreams) if (streamID) streamIDs.push(parseInt(streamID, 10))
  1060. if (debug) console.log('Exit: Disconnect Request.')
  1061. const dis = await disconnect({ streamIDs }).catch((err) => reject(err))
  1062. if (dis === true) resolve(true)
  1063. else reject(dis)
  1064. })))
  1065. }
  1066. // End Common Functions
  1067. this.debug = debug
  1068. this.on = on
  1069. this.connect = connect
  1070. this.setDebug = setDebug
  1071. this.generic = generic
  1072. this.addWorkspace = addWorkspace
  1073. this.rmWorkspace = rmWorkspace
  1074. this.setDefaultWorkspace = setDefaultWorkspace
  1075. this.getDefaultWorkspace = getDefaultWorkspace
  1076. this.listWorkspaces = listWorkspaces
  1077. this.listFunctions = listFunctions
  1078. this.listServerFunctions = listServerFunctions
  1079. this.describeFunction = describeFunction
  1080. this.describeServerFunction = describeServerFunction
  1081. this.createSender = createSender
  1082. this.send = send
  1083. this.subscribe = subscribe
  1084. this.unsubscribe = unsubscribe
  1085. this.disconnect = disconnect
  1086. this.createReceiver = createReceiver
  1087. this.getReceiverID = getReceiverID
  1088. this.exit = exit
  1089. this.login = login
  1090. this.setConfig = setConfig
  1091. this.listStreams = listStreams
  1092. this.listUsers = listUsers
  1093. this.rmUser = rmUser
  1094. this.addUser = addUser
  1095. this.password = password
  1096. this.listGroups = listGroups
  1097. this.rmGroup = rmGroup
  1098. this.addGroup = addGroup
  1099. this.addUserGroup = addUserGroup
  1100. this.rmUserGroup = rmUserGroup
  1101. this.changeOwner = changeOwner
  1102. }).apply(corelink)