UNPKG

@qikdev/sdk

Version:

Promise based Javascript SDK

458 lines (341 loc) 22.9 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>qik.socket.js - Documentation</title> <script src="scripts/prettify/prettify.js"></script> <script src="scripts/prettify/lang-css.js"></script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc.css"> <script src="scripts/nav.js" defer></script> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <input type="checkbox" id="nav-trigger" class="nav-trigger" /> <label for="nav-trigger" class="navicon-button x"> <div class="navicon"></div> </label> <label for="nav-trigger" class="overlay"></label> <nav > <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="access.html">access</a><ul class='methods'><li data-type='method'><a href="access.html#.actionableScopes">actionableScopes</a></li><li data-type='method'><a href="access.html#.allPermissions">allPermissions</a></li><li data-type='method'><a href="access.html#.allPermissionTypes">allPermissionTypes</a></li><li data-type='method'><a href="access.html#.allUserScopes">allUserScopes</a></li><li data-type='method'><a href="access.html#.canCreate">canCreate</a></li><li data-type='method'><a href="access.html#.canDeleteItem">canDeleteItem</a></li><li data-type='method'><a href="access.html#.canEditItem">canEditItem</a></li><li data-type='method'><a href="access.html#.canEraseItem">canEraseItem</a></li><li data-type='method'><a href="access.html#.canKnowOf">canKnowOf</a></li><li data-type='method'><a href="access.html#.canListItem">canListItem</a></li><li data-type='method'><a href="access.html#.canRestoreItem">canRestoreItem</a></li><li data-type='method'><a href="access.html#.canViewItem">canViewItem</a></li><li data-type='method'><a href="access.html#.checkActionAccess">checkActionAccess</a></li><li data-type='method'><a href="access.html#.getAllDescendants">getAllDescendants</a></li><li data-type='method'><a href="access.html#.has">has</a></li><li data-type='method'><a href="access.html#.hasFeature">hasFeature</a></li><li data-type='method'><a href="access.html#.hashPermissions">hashPermissions</a></li><li data-type='method'><a href="access.html#.isAdministrator">isAdministrator</a></li><li data-type='method'><a href="access.html#.isAssignee">isAssignee</a></li><li data-type='method'><a href="access.html#.isOwner">isOwner</a></li></ul></li><li><a href="api.html">api</a><ul class='methods'><li data-type='method'><a href="api.html#.generateEndpointURL">generateEndpointURL</a></li></ul></li><li><a href="auth.html">auth</a><ul class='methods'><li data-type='method'><a href="auth.html#.changeOrganisation">changeOrganisation</a></li><li data-type='method'><a href="auth.html#.ensureValidToken">ensureValidToken</a></li><li data-type='method'><a href="auth.html#.getCurrentToken">getCurrentToken</a></li><li data-type='method'><a href="auth.html#.getCurrentUser">getCurrentUser</a></li><li data-type='method'><a href="auth.html#.impersonate">impersonate</a></li><li data-type='method'><a href="auth.html#.login">login</a></li><li data-type='method'><a href="auth.html#.logout">logout</a></li><li data-type='method'><a href="auth.html#.retrieveUserFromResetToken">retrieveUserFromResetToken</a></li><li data-type='method'><a href="auth.html#.sendResetPasswordRequest">sendResetPasswordRequest</a></li><li data-type='method'><a href="auth.html#.set">set</a></li><li data-type='method'><a href="auth.html#.sync">sync</a></li><li data-type='method'><a href="auth.html#.updateUserWithToken">updateUserWithToken</a></li></ul></li><li><a href="cache.html">cache</a><ul class='methods'><li data-type='method'><a href="cache.html#.get">get</a></li><li data-type='method'><a href="cache.html#.reset">reset</a></li></ul></li><li><a href="content.html">content</a><ul class='methods'><li data-type='method'><a href="content.html#.create">create</a></li><li data-type='method'><a href="content.html#.delete">delete</a></li><li data-type='method'><a href="content.html#.get">get</a></li><li data-type='method'><a href="content.html#.getFromSlug">getFromSlug</a></li><li data-type='method'><a href="content.html#.glossary">glossary</a></li><li data-type='method'><a href="content.html#.list">list</a></li><li data-type='method'><a href="content.html#.patch">patch</a></li><li data-type='method'><a href="content.html#.restore">restore</a></li><li data-type='method'><a href="content.html#.scopeGlossary">scopeGlossary</a></li><li data-type='method'><a href="content.html#.update">update</a></li><li data-type='method'><a href="content.html#.validateField">validateField</a></li><li data-type='method'><a href="content.html#.variables">variables</a></li></ul></li><li><a href="files.html">files</a><ul class='methods'><li data-type='method'><a href="files.html#.downloadUrl">downloadUrl</a></li><li data-type='method'><a href="files.html#.duration">duration</a></li><li data-type='method'><a href="files.html#.filesize">filesize</a></li><li data-type='method'><a href="files.html#.getBinaryTypeFromMime">getBinaryTypeFromMime</a></li><li data-type='method'><a href="files.html#.mediaUrl">mediaUrl</a></li></ul></li><li><a href="filter.html">filter</a><ul class='methods'><li data-type='method'><a href="filter.html#.activeFilterComparators">activeFilterComparators</a></li><li data-type='method'><a href="filter.html#.activeFilterKeys">activeFilterKeys</a></li><li data-type='method'><a href="filter.html#.activeFilterOperators">activeFilterOperators</a></li><li data-type='method'><a href="filter.html#.activeFilters">activeFilters</a></li><li data-type='method'><a href="filter.html#.activeFilterValues">activeFilterValues</a></li><li data-type='method'><a href="filter.html#.filterChangeString">filterChangeString</a></li><li data-type='method'><a href="filter.html#.isValidFilter">isValidFilter</a></li></ul></li><li><a href="geo.html">geo</a><ul class='methods'><li data-type='method'><a href="geo.html#.distance">distance</a></li><li data-type='method'><a href="geo.html#.getDefaultCountry">getDefaultCountry</a></li><li data-type='method'><a href="geo.html#.getDefaultTimezone">getDefaultTimezone</a></li><li data-type='method'><a href="geo.html#.getPosition">getPosition</a></li><li data-type='method'><a href="geo.html#.stopWatchPosition">stopWatchPosition</a></li><li data-type='method'><a href="geo.html#.watchPosition">watchPosition</a></li></ul></li><li><a href="module-QikStorage-CookieStorageAdapter.html">CookieStorageAdapter</a><ul class='methods'><li data-type='method'><a href="module-QikStorage-CookieStorageAdapter.html#getCookieOptions">getCookieOptions</a></li><li data-type='method'><a href="module-QikStorage-CookieStorageAdapter.html#isCookieMode">isCookieMode</a></li></ul></li><li><a href="module-QikStorage-LocalStorageAdapter.html">LocalStorageAdapter</a></li><li><a href="module-QikStorage-StorageAdapter.html">StorageAdapter</a><ul class='methods'><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#clearUser">clearUser</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getAccessToken">getAccessToken</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getRefreshToken">getRefreshToken</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getTokenExpiry">getTokenExpiry</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#getUser">getUser</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#isAvailable">isAvailable</a></li><li data-type='method'><a href="module-QikStorage-StorageAdapter.html#setUser">setUser</a></li></ul></li><li><a href="socket.html">socket</a><ul class='methods'><li data-type='method'><a href="socket.html#.channel">channel</a></li><li data-type='method'><a href="socket.html#.close">close</a></li><li data-type='method'><a href="socket.html#.connect">connect</a></li><li data-type='method'><a href="socket.html#.reconnect">reconnect</a></li><li data-type='method'><a href="socket.html#.subscribe">subscribe</a></li><li data-type='method'><a href="socket.html#.unsubscribe">unsubscribe</a></li></ul></li><li><a href="system.html">system</a><ul class='methods'><li data-type='method'><a href="system.html#.countries">countries</a></li></ul></li><li><a href="utils.html">utils</a><ul class='methods'><li data-type='method'><a href="utils.html#.cleanValue">cleanValue</a></li><li data-type='method'><a href="utils.html#.comma">comma</a></li><li data-type='method'><a href="utils.html#.errorMessage">errorMessage</a></li><li data-type='method'><a href="utils.html#.exists">exists</a></li><li data-type='method'><a href="utils.html#.extractFromArray">extractFromArray</a></li><li data-type='method'><a href="utils.html#.formatCurrency">formatCurrency</a></li><li data-type='method'><a href="utils.html#.getTypeFromID">getTypeFromID</a></li><li data-type='method'><a href="utils.html#.guid">guid</a></li><li data-type='method'><a href="utils.html#.hash">hash</a></li><li data-type='method'><a href="utils.html#.id">id</a></li><li data-type='method'><a href="utils.html#.ids">ids</a></li><li data-type='method'><a href="utils.html#.isValidEmailAddress">isValidEmailAddress</a></li><li data-type='method'><a href="utils.html#.loadExternalScript">loadExternalScript</a></li><li data-type='method'><a href="utils.html#.loadExternalStyle">loadExternalStyle</a></li><li data-type='method'><a href="utils.html#.machineName">machineName</a></li><li data-type='method'><a href="utils.html#.mapParameters">mapParameters</a></li><li data-type='method'><a href="utils.html#.matchInArray">matchInArray</a></li><li data-type='method'><a href="utils.html#.parseBoolean">parseBoolean</a></li><li data-type='method'><a href="utils.html#.parseDate">parseDate</a></li><li data-type='method'><a href="utils.html#.parseEmail">parseEmail</a></li><li data-type='method'><a href="utils.html#.parseInt">parseInt</a></li><li data-type='method'><a href="utils.html#.parseNumber">parseNumber</a></li><li data-type='method'><a href="utils.html#.parseURL">parseURL</a></li></ul></li></ul><h3>Modules</h3><ul><li><a href="module-QikStorage.html">QikStorage</a><ul class='methods'><li data-type='method'><a href="module-QikStorage.html#.createStorageAdapter">createStorageAdapter</a></li></ul></li></ul><h3>Global</h3><ul><li><a href="global.html#EventDispatcher">EventDispatcher</a></li></ul> </nav> <div id="main"> <h1 class="page-title">qik.socket.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>import { EventDispatcher } from './qik.utils.js'; /////////////////////////////////////////////////// /** * Creates a new instance of QikSocket a module of the SDK * that contains all helper functions to do with realtime sockets * @name socket * @constructor * @hideconstructor * @param {QikAPI} qik A reference to the parent instance of the QikCore module. This module is usually created by a QikCore instance that passes itself in as the first argument. */ const QikSocket = function(qik, mode) { mode = mode || 'production'; if (!qik.auth) { throw new Error(`Can't Instantiate QikSocket before QikAuth exists`); } const windowID = qik.utils.guid(); /////////////////////////////////////////////////// let service = { debug: false, url: `wss://iqtm6zjz3l.execute-api.ap-southeast-2.amazonaws.com/${mode}`, connected: false, windowID, autoreconnect:true, } let socket; let timer; let buckets = {}; const channels = {}; //Create a new dispatcher const dispatcher = new EventDispatcher(); dispatcher.bootstrap(service); /////////////////////////////////////////////////// function getSubscriptions() { return Object.keys(buckets); } /////////////////////////////////////////////////// /** * @description Create an event dispatcher instance and subscribe to a new socket channel. * This can then be used to receive socket events for this channel * @alias socket.channel * @param {String} key The key or name of the channel to subscribe to * @example * const socketChannel = await sdk.socket.channel('awesome:notifications') * socketChannel.addEventListener('message', function(message) { * console.log('Received a new message from the socket') * }) * * // Disconnect from this channel and remove all listeners * socketChannel.destroy(); * */ service.channel = async function(key) { if (buckets[key]) { return buckets[key]; } const bucket = new EventDispatcher(); function dispatchMessage(message) { bucket.dispatch('message', message); } // Destroy this channel - stop receiving its events and tell the // server to unsubscribe. Only touches this channel's own listeners, // leaving every other channel (and connection-level listeners) intact. bucket.destroy = function() { service.removeEventListener(key, dispatchMessage); service.unsubscribe(key); bucket.removeAllListeners(); delete buckets[key]; } // Cache the fully-formed bucket (with destroy attached) BEFORE the // connect() await below. A concurrent channel(key) call can hit the // early return above while we're suspended awaiting connect(); if we // registered the bucket before assigning destroy, that caller would // receive a bucket whose destroy() is missing and throw on teardown. buckets[key] = bucket; if (!service.connected) { // Return the socket await service.connect(); } // Subscribe to this channel service.subscribe(key); // Listen for all relevant events service.addEventListener(key, dispatchMessage); return buckets[key]; } /////////////////////////////////////////////////// function ping() { broadcast({ action: 'ping', }) } function startHeartbeat() { if (timer) { return; } timer = setInterval(ping, 10000); } function stopHeartbeat() { if (!timer) { return; } clearInterval(timer) timer = null; } /////////////////////////////////////////////////// function socketOpened(event) { service.debug ? console.log("[socket] Connection open", event) : null; service.connected = true; dispatcher.dispatch('connected', event); // startHeartbeat(); } async function socketClosed(event) { if (event.wasClean) { service.debug ? console.log(`[socket] Connection closed cleanly, code=${event.code} reason=${event.reason}`) : null; } else { // e.g. server process killed or network down // event.code is usually 1006 in this case service.debug ? console.log('[socket] - connection closed due to error', event) : null } service.connected = false; dispatcher.dispatch('disconnected', event); socket = null; if(service.autoreconnect) { service.reconnect(); } } function socketError(error) { console.log('[event] socketError', event); service.debug ? console.log("[socket] Error", error) : null; dispatcher.dispatch('error', error); } function socketMessageReceived(event) { const eventData = JSON.parse(event.data); service.debug ? console.log("[socket] message received", eventData) : null; // Dispatch a generic message dispatcher.dispatch('message', eventData); if (eventData.channel) { dispatcher.dispatch(eventData.channel, eventData); } } async function broadcast(data) { socket.send(JSON.stringify(data)); } /////////////////////////////////////////////////// function wait() { return new Promise(function(resolve) { function check() { if (service.connected) { return resolve(socket); } setTimeout(check, 1000); } check(); }) } /////////////////////////////////////////////////// /** * @description Connect to the socket service * @alias socket.connect * @example * const connected = await sdk.socket.connect() * */ service.connect = async function() { if (socket) { if (!socket.connected) { return wait(); } console.log('[socket] - Socket is already connected'); return socket; } const accessToken = qik.auth.getCurrentToken(); if (!accessToken) { service.debug ? console.log('[socket] - Must be authenticated to connect to socket') : null; return; } socket = new WebSocket(`${service.url}?access_token=${accessToken}&amp;windowid=${windowID}`); socket.addEventListener('close', socketClosed); socket.addEventListener('error', socketError); socket.addEventListener('open', socketOpened); socket.addEventListener('message', socketMessageReceived); service.debug ? console.log('[socket] - Connected with event listeners') : null; return wait(); } /** * @description Reconnect to the socket service * @alias socket.reconnect * @example * const connected = await sdk.socket.reconnect() * */ service.reconnect = async function() { if(service.connected) { return; } service.debug ? console.log(`[socket] reconnecting...`) : null; // Reconnect all listeners const result = await service.connect(); // Resubscribe to all previously connected channels const subscribedChannels = Object.keys(channels); subscribedChannels.forEach(function(channel) { service.subscribe(channel); }) return result; } /////////////////////////////////////////////////// /** * @description Close the connection to the socket service * @alias socket.close * @example * const closed = await sdk.socket.close() * */ service.close = async function() { if (!socket) { console.log('[socket] - Socket is not connected'); return; } const previousSetting = service.autoreconnect; service.autoreconnect = false; socket.close(); socket.removeEventListener('close', socketClosed); socket.removeEventListener('error', socketError); socket.removeEventListener('open', socketOpened); socket.removeEventListener('message', socketMessageReceived); socket = null; socketClosed({ wasClean: true }); service.autoreconnect = previousSetting; } service.disconnect = service.close; /////////////////////////////////////////////////// /** * @description Subscribe to a socket channel * @alias socket.subscribe * @param {String} key The name of the channel to subscribe to * @example * const subscribed = await sdk.socket.subscribe('some:cool:alert') * */ service.subscribe = async function(channel) { if (!socket) { console.log(`[socket] - Can't subscribe to channel as socket is not connected`); return; } broadcast({ action: 'subscribe', channel, }) channels[channel] = true; dispatcher.dispatch('subscribe', channel) service.debug ? console.log(`[socket] - subscribed to ${channel}`) : null; } /////////////////////////////////////////////////// /** * @description Unsubscribe from a socket channel * @alias socket.unsubscribe * @param {String} key The name of the channel to unsubscribe from * @example * const unsubscribed = await sdk.socket.unsubscribe('some:cool:alert') * */ service.unsubscribe = async function(channel) { if (!socket) { console.log(`[socket] - Can't unsubscribe from channel as socket is not connected`); return; } broadcast({ action: 'unsubscribe', channel, }) delete channels[channel]; dispatcher.dispatch('unsubscribe', channel) service.debug ? console.log(`[socket] - unsubscribed from ${channel}`) : null; } /////////////////////////////////////////////////// // Reconnect the socket whenever the authenticated organisation changes. // The WebSocket authenticates with an access token at connect time, so the // connection stays bound to whichever organisation was active when it // opened. Switching organisations issues a new token but leaves the socket // untouched, so without this it keeps streaming the previous // organisation's events. Reconnecting re-authenticates with the new token. let currentOrganisationID = qik.utils.id(qik.auth.getCurrentUser()?.organisation); qik.auth.addEventListener('change', async function(user) { const organisationID = qik.utils.id(user?.organisation); // Ignore auth changes that don't change the organisation (e.g. routine // access token refreshes) so we don't churn the connection needlessly. if (organisationID === currentOrganisationID) { return; } currentOrganisationID = organisationID; // Nothing to reconnect if the socket was never opened. if (!socket) { return; } service.debug ? console.log('[socket] - organisation changed, reconnecting') : null; await service.close(); await service.reconnect(); }); /////////////////////////////////////////////////// return service; } export default QikSocket;</code></pre> </article> </section> </div> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.11</a> on Mon Jul 13 2026 18:49:48 GMT+1000 (Australian Eastern Standard Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme. </footer> <script>prettyPrint();</script> <script src="scripts/polyfill.js"></script> <script src="scripts/linenumber.js"></script> </body> </html>