{"id":17362380,"url":"https://github.com/isocroft/beamzer-client","last_synced_at":"2025-10-15T01:32:03.450Z","repository":{"id":57189375,"uuid":"72220477","full_name":"isocroft/beamzer-client","owner":"isocroft","description":"JavaScript wrapper script for Server Sent Events with support for JavaScript Workers","archived":false,"fork":false,"pushed_at":"2019-10-19T11:40:58.000Z","size":60,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-21T11:55:28.986Z","etag":null,"topics":["eventsourcing","javascript","realtime-notifications","server-sent-events","wrapper-script"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/isocroft.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-10-28T15:42:50.000Z","updated_at":"2019-10-19T11:41:00.000Z","dependencies_parsed_at":"2022-09-15T06:21:48.517Z","dependency_job_id":null,"html_url":"https://github.com/isocroft/beamzer-client","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/isocroft/beamzer-client","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fbeamzer-client","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fbeamzer-client/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fbeamzer-client/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fbeamzer-client/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/isocroft","download_url":"https://codeload.github.com/isocroft/beamzer-client/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/isocroft%2Fbeamzer-client/sbom","scorecard":{"id":495930,"data":{"date":"2025-08-11","repo":{"name":"github.com/isocroft/beamzer-client","commit":"dbf46409d8fdd458ad65878e796a8d8e43d77234"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-19T20:25:15.635Z","repository_id":57189375,"created_at":"2025-08-19T20:25:15.635Z","updated_at":"2025-08-19T20:25:15.635Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279033009,"owners_count":26089390,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eventsourcing","javascript","realtime-notifications","server-sent-events","wrapper-script"],"created_at":"2024-10-15T19:38:03.872Z","updated_at":"2025-10-15T01:32:03.417Z","avatar_url":"https://github.com/isocroft.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BeamzerClient\r\n\r\nThis is a wrapper script and libarary which makes use of the JS polyfill for _Server-Sent Events_ located at https://github.com/amvtek/EventSource/ and makes it easy to manage several connections to *push notification* or *event* sources. This library can also be used inside *Web Workers* (Dedicated or Shared workers)\r\n\r\n## File Size\r\n\r\n- 15.2kb (Minified)\r\n\r\n## Usage\r\n \r\n```html\r\n  \u003c!DOCTYPE html\u003e\r\n  \u003chtml lang=\"en\"\u003e\r\n     \u003chead\u003e\r\n\t      \u003cmeta charest=\"utf-8\"\u003e\r\n\t\t    \u003ctitle\u003eBeamzerClient - Example\u003c/title\u003e\r\n\t\t  \r\n\t\t    \u003cscript type=\"text/javascript\" src=\"/path/to/beamzer-client.min.js\"\u003e\u003c/script\u003e\r\n\t   \u003c/head\u003e\r\n\t   \u003cbody class=\"screen\"\u003e\r\n\t      \u003cscript type=\"text/javascript\"\u003e\r\n\t   \tvar beam = new BeamzerClient({\r\n                    source:\"http://www.example.com/beamrays\",\r\n                    params:{\r\n                        id:\"id\"\r\n                    },\r\n                    options:{loggingEnabled:true, interval:4500,crossdomain:true}\r\n               });\r\n\r\n               // open a connection with relevant callbacks\r\n               beam.start(function onopenCalback(e){\r\n\t\t      \r\n\t\t      }, function onerrorCalback(e){ \r\n\t\t      \tif (e.readyState == EventSource.CLOSED) {\r\n                    \t\tconsole.log('Event was closed');\r\n\t\t\t}\r\n\t\t      }, function onmessageCalback(e){\r\n\t\t      \r\n\t\t      });\r\n               // register an event [update]\r\n               beam.on(\"update\", function(e){ });\r\n               // register another event [noupdate]\r\n               beam.on(\"noupdate\", function(e){ });\r\n               // recreate a connection. \r\n               beam.newClient({source:\"http://www.example.com/beamrays\",params:{},options:{}});\r\n               // unregister an event [update]\r\n               beam.off(\"update\");\r\n               // close all the connection(s)\r\n               beam.stop(function(e){ });\r\n\t\t    \u003c/script\u003e\r\n\t   \u003c/body\u003e\r\n  \u003c/html\u003e\r\n```\r\n\r\n\u003eIt is important to note that whenever.\r\n\r\n**AngularJS UseCase Example**\r\n\r\nThe idea here is to loosely couple communications to beamzer-client in an AngularJS app and\r\n\r\n```js\r\n \r\n  /* \r\n    \r\n    The idea here is to loosely couple the different controllers to the beamzer-client event stream from the server.\r\n    Instead of having multiple observers for each controller scope listening for incoming streams, we have a single\r\n    mediator object powered by $rootScope for this purpose.\r\n\r\n  */\r\n\r\n;(function(w, angular){\r\n\r\n   var module = angular.module(\"TheAppServices\"); // assumes this module has been defined before\r\n\r\n   module.factory(\"$activityStreamer\", [function(){\r\n   \r\n        // assumes that [$values] is another service defined before that contains the stream URL data \r\n\r\n        var ENDPOINT = 'http://www.example.com/streamer.php',\r\n\r\n            CLIENT = null, \r\n\r\n            started = false;\r\n \r\n        return {\r\n           \r\n            start:function(openCallback, errorCallback, msgCallback){\r\n                 \r\n               CLIENT = new w.BeamzerClient({\r\n                  source:ENDPOINT,\r\n                  params:{id:'68AWtlwGTE65hDE34j9Lm'},\r\n                  options:{loggingEnabled:true, interval:4500}\r\n               });  \r\n \r\n               CLIENT.start(openCallback, errorCallback, msgCallback); \r\n\r\n               started = true;           \r\n            },\r\n            addEvent:function(event, callback){\r\n\r\n                CLIENT.on(event, callback);\r\n\r\n            },\r\n            removeEvent:function(event){\r\n\r\n                CLIENT.off(event);\r\n            },\r\n            newConnection:function(settings){\r\n\r\n               CLIENT.newClient(settings);\r\n            },\r\n            end:function(closeCallback){\r\n                \r\n               CLIENT.stop(closeCallback);\r\n            },\r\n            isStarted:function(){\r\n\r\n                return started;\r\n            }\r\n\r\n        }       \r\n\r\n   }]);\r\n\r\n}(this, this.angular));  \r\n\r\n\r\n;(function(w, angular){\r\n\r\n  var module = angular.module(\"TheAppServices\");\r\n\r\n  module.factory(\"$sessionStorage\", ['$window', function($window){\r\n      var data = null, keys = {};\r\n\r\n      function setData(){\r\n         $window.name = angular.toJSON(data);\r\n      }\r\n\r\n      function clearData(){\r\n        $window.name = data ? (data = {}) \u0026\u0026 '' : '';\r\n      }\r\n\r\n      function getData(){\r\n        return data || angular.fromJSON($window.name || '{}');\r\n      }\r\n\r\n      data = getData();\r\n\r\n      return ('sessionStorage' in $window) ? $window.sessionStorage : {\r\n            length:0,\r\n            clear:function(){\r\n              keys = {};\r\n              clearData((this.length = 0));\r\n            },\r\n            getItem:function(key){\r\n              var _data = getData(); \r\n              return (key in _data)? _data[key] : null;\r\n            },\r\n            setItem:function(key, value){\r\n              var _data = getData();\r\n              _data[key] = angular.toJSON(value);\r\n              setData(keys[String(++this.length)] = key);\r\n            },\r\n            key:function(i){\r\n              var _i = String(i);\r\n              return (_i in keys)? keys[i] : null;\r\n            },\r\n            removeItem:function(key){\r\n              var _data = getData();\r\n              delete _data[key];\r\n              setData(keys[String(--this.length)] = key);\r\n            }\r\n      };\r\n\r\n  }]);\r\n\r\n}(this, this.angular));\r\n\r\n\r\n;(function(w, angular){\r\n\r\n     var usebuffer = false, \r\n     app = angular.module(\"TheApp\", [\"TheAppServices\"]);\r\n\r\n     // we assume here that this ficticious app (TheAppServices) has a '#/feeds' route registered prior\r\n\r\n     app.run(['$activityStreamer', \r\n              '$sessionStorage', \r\n              '$rootScope', \r\n              '$route', function($activityStreamer, $sessionStorage, $rootScope, $route){\r\n\r\n          \r\n          $rootScope.$on('newStreamerRequest', function(event, data){\r\n\r\n                  // assuming -- $scope.$emit('newStreamerRequest', {settings:{url:'...',params:{}}}) -- is called\r\n\r\n                  $activityStreamer.newConnection(data.settings);\r\n\r\n          });\r\n\r\n          \r\n          $rootScope.$on('removeStreamerEvent', function(event, data){ \r\n\r\n               // assuming -- $scope.$emit('removeStreamerEvent', {eventName:'update'}) -- is called\r\n\r\n               $activityStreamer.removeEvent(data.eventName);\r\n\r\n          });\r\n\r\n          $rootScope.$on('$destroy', function(event, data){\r\n\r\n                    // if we go off from the app itself, kill it... fast!!\r\n\r\n                    $activityStreamer.end(function(e){\r\n\r\n                          $rootScope.$broadcast('allStreamerExit', e); // notify all listening scopes (perhaps...)\r\n\r\n                    });\r\n          });\r\n\r\n          $rootScope.$on('$routeChangeStart', function(event, current, previous){\r\n\r\n                // if we move away from a 'feeds' route page, buffer all real-time data in sessionStorage!!\r\n\r\n                if(previous.url.indexOf('feeds') \u003e -1){ \r\n\r\n                      useBuffer = true;                    \r\n\r\n                }    \r\n          });\r\n\r\n          $rootScope.$on('$routeChangeSuccess', function(event, current, previous){\r\n\r\n                /*$rootScope.title = current.title;*/\r\n\r\n\r\n                // if we move to a 'feeds' route page, prepare to display real-time data in view\r\n\r\n                if(current.url.indexOf('feeds') \u003e -1){ \r\n\r\n                        useBuffer = false;\r\n\r\n                        if($activityStreamer.isStarted()){\r\n\r\n                              return;\r\n                        }\r\n\r\n                        $activityStreamer.start(function(e){\r\n\r\n                              $sessionStorage.setItem('ACTIVITY_STREAM_BUFFER', {});\r\n\r\n                              $rootScope.$broadcast('newStreamerSuccess', e); // notify all listening scopes\r\n                        },\r\n\r\n                        function(e){\r\n               \r\n                            $rootScope.$broadcast('newStreamerFailure', e); // notify all listening scopes\r\n\r\n                        },\r\n                        \r\n                        function(e){\r\n\r\n                              setTimeout(streams.bind(null, e), 0);\r\n\r\n                        });\r\n\r\n                        $activityStreamer.addEvent('update', function(e){\r\n\r\n                              setTimeout(streams.bind(null, e), 0);\r\n\r\n                        });\r\n\r\n                        $activityStreamer.addEvent('noupdate', function(e){\r\n\r\n                            $rootScope.$broadcast('noNewStreamerMessageRecieved', e); // notify all listening scopes\r\n\r\n                        });\r\n\r\n                }\r\n\r\n                function streams(e){\r\n\r\n                          var _buffer = $sessionStorage.getItem('ACTIVITY_STREAM_BUFFER');\r\n\r\n                          var _timestamps;\r\n\r\n                          if(useBuffer){\r\n\r\n                                _buffer[String(new Date*1)] = e;\r\n\r\n                                // from here, you can trigger a toast notification on the screen perhaps...\r\n\r\n                          }else{\r\n\r\n                              if($sessionStorage.length){\r\n\r\n                                    _timestamps = Object.keys(_buffers);\r\n\r\n                                    // we need to make sure that when all 'buffered' messages are sent\r\n                                    // to all $scopes as and at when they were recieved in buffer mode\r\n\r\n                                    _timestamps.sort(function(a, b){\r\n                                        return (b - a);\r\n                                    })\r\n\r\n                                    angular.forEach(_timestamps, function(value){\r\n\r\n                                        $rootScope.$broadcast('newStreamerMessageRecieved', _buffer[value]);\r\n\r\n                                        delete _buffer[value];\r\n\r\n                                    });\r\n\r\n                              }\r\n\r\n                              $rootScope.$broadcast('newStreamerMessageRecieved', e); // notify all listening scopes\r\n                          }\r\n\r\n                          $sessionStorage.setItem('ACTIVITY_STREAM_BUFFER', _buffer);\r\n                }\r\n\r\n          });         \r\n     });\r\n\r\n}(this, this.angular));\r\n\r\n\r\n\r\n/* controller action */\r\n\r\n;(function(w, angular){\r\n  \r\n    var app = angular.module(\"TheApp\");\r\n\r\n    app.controller(['$scope', function($scope){\r\n\r\n          $scope.updatesList = [];\r\n\r\n          $scope.$on('newStreamerMessageRecieved', function(event, data){\r\n\r\n              var payload = data.data; // the event object from the handler has a 'data' property...\r\n\r\n              // assuming [payload] variable is an array...\r\n\r\n              angular.forEach(payload, function(item){\r\n                \r\n                   $scope.updateList.push(item); // this updates the view via 'ng-repeat' in the view...\r\n\r\n              });\r\n          });\r\n\r\n    }]);\r\n\r\n}(this, this.angular));\r\n \r\n```\r\n\r\n\u003eThis is a simple SSE server script written in PHP\r\n\r\n```php\r\n\r\n # Simple Implementation of event streams on the server - http://www.example.com/streamer.php\r\n\r\n sleep(5); // simulate real prod server activity e.g. database read operation \r\n\r\n $data = array('status' =\u003e 'OK');\r\n \r\n $payload = \"id: 5RWF637yh9983az021mn \\n\";\r\n $payload .= \"event: update \\n\";\r\n $payload .= \"data: \" . json_encode($data) . \" \\n\\n\"; // the last line must end with 2 line feed characters\r\n\r\n header('Content-Type: text/event-stream');\r\n\r\n echo $payload;\r\n\r\n exit;\r\n\r\n```\r\n\u003eThis is another simple SSE server script written in Node.js (using [ssenode](https://github.com/isocroft/ssenode))\r\n\r\n```js\r\n\r\n// Simple Implementation of event streams on the server - http://www.example.com/stream\r\n\r\nconst { Source, EventStream } = require('ssenode');\r\nconst uuidv4 = require('uuid/v4');\r\nconst exp = require('express');\r\n\r\nconst app = exp()\r\nconst source = new Source(uuidv4);\r\n\r\napp.use(EventStream.init(source, { no_id:false }));\r\n\r\napp.get('/stream', (req, res) =\u003e {\r\n   setTimeout(() =\u003e { // simulate real prod server activity e.g. database read operation\r\n\t    source.send({\r\n\t\tstatus:'OK'\r\n\t    }, null, 'update')\r\n   }, 5000); \r\n})\r\n\r\n```\r\n\r\n### Web Worker example\r\n\r\n```js\r\n\r\n\tconst worker = new Worker(\"./push-notifs.js\");\r\n\t\r\n\tworker.addEventListener(\"message\", (event) =\u003e {\r\n\t\tlet data = event.data;\r\n\t\tif(data.streamClosed){\r\n\t\t\tsetTimeout(function() =\u003e {\r\n\t\t\t\tworker.terminate();\r\n\t\t\t},0);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tconsole.log(JSON.stringify({data}));\r\n\t}, false);\r\n\t\r\n\tworker.postMessage({start:true});\r\n\t\r\n\tsetTimeout(() =\u003e {\r\n\t\tworker.postMessage({stop:true});\r\n\t}, 8900);\r\n\t\r\n\t// push-notifs.js file\r\n\t\r\n\timportScripts(\"./path/to/beamzer-client.min.js\");\r\n\t\r\n\tlet globale = self, beam = null;\r\n\t\r\n\taddEventListener(\"message\", (event) =\u003e {\r\n\t\tif(event.data.start === true){\r\n\t\t\tbeam = new BeamzerClient({\r\n\t\t\t\tsource:\"https://stream.stock-details.com\",\r\n\t\t\t\toptions:{interval:4500}\r\n\t\t\t});\r\n\r\n\t\t\tbeam.open((e) =\u003e {\r\n\t\t\t\tglobale.postMessage({streamOpened:true});\r\n\t\t\t},\r\n\r\n\t\t\t(e) =\u003e {\r\n\t\t\t\tglobale.postMessage({streamMalfunction:true});\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tbeam.on(\"notification\", (e) =\u003e {\r\n\t\t\t\tglobale.postMessage({streamData:e.data});\r\n\t\t\t});\r\n\t\t}\r\n\t\t\r\n\t\tif(event.data.stop === true){\r\n\t\t\tif(beam !== null){\r\n\t\t\t\tbeam.close((e) =\u003e {\r\n\t\t\t\t\tglobale.postMessage({streamClosed:true});\r\n\t\t\t\t\tbeam = null; // reclaim memory\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\t}, false);\r\n\r\n```\r\n\r\n### Helper Hints\r\n\r\nWhile you implement **beamzer-client**, there are afew things to watch out for.\r\n\r\n1. If users create multiple tabs for your beamzer-enabled web app, they will invariably be creating multiple SSE (EventSource) connection to your server.\r\n   So, you might want to consider disconnecting from the server, whenever a user minimizes the window where your web app is loaded or \t\r\n   switches from the tab where your web app is loaded. Use the page-visiblity JavaScript API or `document.hasFocus()`. Thankfully, \r\n   there's this JavaScript Library called [Idle.js](https://github.com/shawnmclean/Idle.js/) for watching the users' every move and it's \r\n   easy to setup and use and i very much recommend it. \r\n\r\n2. Use `localStorage` \"storage\" events to update event-source data across all non-active tabs. Watch out for Edge though (you may have \r\n   to use **cookie polling** as a fall back) as it lacks support for cross-tab \"storage\" events. I think it should be fixed by Edge 16+ \r\n\r\n## Support\r\n\r\nAvailable on all major browsers including IE8 - If you discover any bugs, please log an issue for it and i'll surely get look into it. If you wish to provide fallback support for web workers in Old IE (IE 8 / 9). Then you can check out [this polyfill library](https://github.com/calvinmetcalf/web-worker)\r\n\r\n## Credits\r\n\r\n@isocroft.\r\n\r\n## Contributing\r\n\r\nI welcome all contributions to make this software better. please send an [email](mailto:isocroft@gmail.com) to communicate your thoughts on imporvements and features.\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisocroft%2Fbeamzer-client","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fisocroft%2Fbeamzer-client","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fisocroft%2Fbeamzer-client/lists"}