{"id":13805677,"url":"https://github.com/rhyek/ember-master-tab","last_synced_at":"2025-04-23T16:23:50.393Z","repository":{"id":8878922,"uuid":"59927675","full_name":"rhyek/ember-master-tab","owner":"rhyek","description":"A library that provides a service which helps running a function on only one tab of an Ember application.","archived":false,"fork":false,"pushed_at":"2023-03-04T07:54:05.000Z","size":3329,"stargazers_count":11,"open_issues_count":25,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-23T16:23:43.566Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/rhyek.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-05-29T05:18:42.000Z","updated_at":"2022-10-17T06:37:27.000Z","dependencies_parsed_at":"2024-01-09T05:01:30.091Z","dependency_job_id":"fe91be7c-8c6c-442c-97a8-ecdcbbac5557","html_url":"https://github.com/rhyek/ember-master-tab","commit_stats":{"total_commits":57,"total_committers":6,"mean_commits":9.5,"dds":0.4385964912280702,"last_synced_commit":"2875a0144459b2216720c988c896aec65b1404d1"},"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhyek%2Fember-master-tab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhyek%2Fember-master-tab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhyek%2Fember-master-tab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rhyek%2Fember-master-tab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rhyek","download_url":"https://codeload.github.com/rhyek/ember-master-tab/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250468530,"owners_count":21435502,"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","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":[],"created_at":"2024-08-04T01:01:03.721Z","updated_at":"2025-04-23T16:23:50.330Z","avatar_url":"https://github.com/rhyek.png","language":"JavaScript","funding_links":[],"categories":["Packages"],"sub_categories":["Control flow"],"readme":"# Ember Master Tab\n\n## Synopsis\n\nThis addon provides a `service` that allows you to run code on a single tab of your ember\napplication. You might find this useful if your app has functionality that for some reason\ndoes not make sense or it is wasteful/redundant to have it run on every tab that is currently\nopen. For example, you might be continuously pulling some state from your server API that\nyou are saving on `localStorage` which you then use to update your UI through event listeners.\n\n## Compatibility\n\n* Ember.js v3.12 or above\n* Ember CLI v2.13 or above\n* Node.js v10 or above\n\n## Notes\n\n* The service ensures that only one master tab exists at any one time.\n* If the current master tab closes or refreshes, any other tab can take the responsability at that time.\n* If the current master tab crashes, ***currently*** no other tab will take the responsability until\n  a new tab is opened. \n* This service is most useful on objects that provide global functionality to your application, such as other services.\n\n\n`ember install ember-master-tab`\n\n## Code Example\n\nYou can clone this repository and have a look at the dummy app to see it in action.\n\n**`run(func1, options = {}).else(func2)`**\n- `func1`: If this is the master tab, run this function.\n- `options` *(optional)*:\n - `force` *(optional, default: `false`)*: If `true`, run `func1` irregardless of this being the master tab or not.\n- `func2`: If this is *not* the master tab, run this instead.\n\n```js\n// services/server-time-run.js\nimport Ember from 'ember';\n\nexport default Ember.Service.extend({\n  masterTab: Ember.inject.service(),\n  currentTime: null,\n  init() {\n    this._super(...arguments);\n    window.addEventListener('storage', e =\u003e { // only slave tabs will receive this event\n      if (e.key === 'current-time-run') {\n        this.set('currentTime', e.newValue);\n      }\n    });\n    this._updateTime();\n  },\n  _updateTime() {\n    Ember.run.later(() =\u003e {\n      this.updateTime();\n      this._updateTime();\n    }, 900);\n  },\n  updateTime(force = false) {\n    this.get('masterTab')\n      .run(() =\u003e {\n        Ember.$.getJSON('/api/current-time').then(data =\u003e { // will only run on the master tab\n          const currentTime = data.currentTime;\n          this.set('currentTime', currentTime);\n          localStorage['current-time-run'] = currentTime;\n        });\n      }, { force })\n      .else(() =\u003e {\n        // Master tab is handling it.\n      });\n  }\n});\n```\n*Notes*:\n- `else()` is optional.\n- `run()` takes a second optional `boolean` parameter. If `true` it will\n  make the function run irregardless of this being the master tab or not\n  for that call on that tab and the function passed to `else()` will not\n  run. Considering the previous example, this would be useful if a\n  controller calls `this.get('serverTimeRun').updateTime(true)` directly\n  on any tab.\n\n**`lock(lockName, func1, options = {}).wait(func2)`**\n- `lockName`: Name of the lock.\n- `func1`: Function which returns a `Promise` that will run only if this is the master tab.\n  Once the promise `resolve`s or `reject`s, the lock will be freed.\n- `options` *(optional)*:\n - `force` *(optional, default: `false`)*: If `true`, run `func1` irregardless of this being the master tab or not.\n - `waitNext` *(optional, default: `true`)*: If `true` and there is currently no lock present, wait a maximum of `waitNextDelay` until the lock has been obtained and released.\n - `waitNextDelay` *(optional, default: 1000)*: If `waitNext` is `true`, wait this amount of milliseconds.\n- `func2`: If this is *not* the master tab, run this instead once the lock has been freed.\n\n```js\n// services/server-time-lock.js\nimport Ember from 'ember';\n\nexport default Ember.Service.extend({\n  masterTab: Ember.inject.service(),\n  currentTime: null,\n  init() {\n    this._super(...arguments);\n    this._updateTime();\n  },\n  _updateTime() {\n    Ember.run.later(() =\u003e {\n      this.updateTime();\n      this._updateTime();\n    }, 900);\n  },\n  updateTime(force = false) {\n    this.get('masterTab')\n      .lock('server-time', () =\u003e {\n        return Ember.$.getJSON('/api/current-time').then(data =\u003e { // will only run on the master tab\n          const currentTime = data.currentTime;\n          this.set('currentTime', currentTime);\n          return currentTime; // will be passed to slave tabs\n        });\n      }, { force })\n      .wait(currentTime =\u003e { // will only run on slave tabs; currentTime is the result from the master tab\n        this.set('currentTime', currentTime);\n      });\n  }\n});\n```\n*Notes*:\n- `wait()` is optional. It can take a second callback which runs if the\n  promise failed.\n- If the master tab is currently running the promise (there is a lock present),\n  the callbacks passed to `wait()` will execute once that promise\n  resolves/rejects. Otherwise, they will run immediately. These callbacks\n  only run on \"slave\" tabs, generally.\n- You use this if you need \"slave\" tabs to wait for whatever the master\n  tab's promise returns. Maybe your service defers readiness of the application's\n  initialization and you need the master tab to finish loading giving slave\n  tabs its state.\n- The value passed to the `wait()` callbacks will be the last value returned\n  by the `lock()` promise.\n- If `options.force` is `true` it will\n  make the function run irregardless of this being the master tab or not for\n  that call on that tab. It sets a lock and the callbacks passed to `wait()`\n  will not run. If the master tab encounters a lock during this, it will instead\n  run the `wait()` callbacks. Considering the previous example, this would\n  be useful if a controller calls `this.get('serverTimeLock').updateTime(true)`\n  directly on any tab.\n- The service will save to `localStorage` whatever the promise returns.\n  This value will be passed to the appropriate callback given to `wait()`.\n  Note that `localStorage` only stores strings. So make sure whatever\n  your promise returns can easily be converted to something usable in\n  your `wait()` callbacks.\n  \n**`isMasterTab` event**\n\nWhenever a tab is promoted to master status, the `masterTab` service will emit an `isMasterTab` event.\nSo, following the theme of the previous examples, you could also work with `EventSource` objects\n(or `WebSocket`, etc.) like this:\n```js\n// services/server-time-sse.js\nimport Ember from 'ember';\n\nexport default Ember.Service.extend({\n  masterTab: Ember.inject.service(),\n  currentTime: null,\n  init() {\n    this._super(...arguments);\n    if (this.get('masterTab.isMasterTab')) {\n      this.setup();\n    }\n    this.get('masterTab').on('isMasterTab', isMaster =\u003e {\n      if (isMaster) {\n        this.setup();\n      }\n    });\n    window.addEventListener('storage', e =\u003e {\n      if (e.key === 'current-time-sse') {\n        this.set('currentTime', e.newValue);\n      }\n    });\n  },\n  setup() {\n    const sse = new EventSource('/sse');\n    sse.onmessage = e =\u003e {\n      this.set('currentTime', e.data);\n      window.localStorage['current-time-sse'] = e.data;\n    };\n    this.get('masterTab').on('isMasterTab', isMaster =\u003e {\n      if (!isMaster) {\n        sse.close();\n      }\n    });\n  }\n});\n```\n*Notes*:\n- The event is only raised after the application has been initialized. Therefore,\n  the master tab will not emit it. It will only be triggered if the master\n  tab is closed/refreshed and a different tab is promoted.\n\n## License\n\nEmber Master Tab is released under the [MIT Licencse](https://github.com/rhyek/ember-master-tab/blob/master/LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhyek%2Fember-master-tab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frhyek%2Fember-master-tab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frhyek%2Fember-master-tab/lists"}