@pixpilot/chrome-lifecycle 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,7 +61,7 @@ initSidePanelStateManager();
61
61
 
62
62
  **2. Side panel script (frontend):**
63
63
 
64
- Call `initializeSidePanelStateTracker()` when your side panel loads. This sets up visibility tracking and a heartbeat to keep the connection alive.
64
+ Call `initializeSidePanelStateTracker()` when your side panel loads. This announces the panel state, tracks document visibility changes by default, and reconnects automatically if the Manifest V3 service worker restarts.
65
65
 
66
66
  ```typescript
67
67
  // sidepanel.ts
@@ -80,10 +80,16 @@ Initializes the backend state manager. **Must be called once at the top of your
80
80
 
81
81
  ##### `initializeSidePanelStateTracker()`
82
82
 
83
- Initializes the frontend tracker in your side panel. Sets up visibility change detection and a heartbeat (every 15 seconds) to maintain the connection.
83
+ Initializes the frontend tracker in your side panel. It reconnects automatically when the background service worker disconnects and, by default, reports `document.hidden` as hidden side panel state.
84
84
 
85
85
  **Returns:** Cleanup function to remove listeners and disconnect
86
86
 
87
+ Options:
88
+
89
+ | Property | Type | Default | Description |
90
+ | ------------------------- | --------- | ------- | -------------------------------------------------------------------------------- |
91
+ | `trackDocumentVisibility` | `boolean` | `true` | When true, `document.hidden` is reported as hidden state via `visibilitychange`. |
92
+
87
93
  ##### `isWindowSidePanelVisible(windowId)`
88
94
 
89
95
  Returns `true` if the side panel is visible for the given window.
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- const e=new Map;let t=!1;function n(){t||(t=!0,chrome.windows.onRemoved.addListener(t=>{let n=e.get(t);n&&(n.forEach(e=>{e()}),e.delete(t))}))}function r(t,r){n();let i=e.get(t);return i||(i=new Set,e.set(t,i)),i.add(r),()=>{let n=e.get(t);n&&(n.delete(r),n.size===0&&e.delete(t))}}const i=15e3;let a=!1;function o(){a&&console.warn(`Side panel state tracker already initialized`),a=!0;let e,t,n;return chrome.windows.getCurrent(r=>{if(r.id==null){console.error(`[side-panel-state-tracker] Could not get window ID`);return}e=chrome.runtime.connect({name:chrome.runtime.id});let i=r.id;function a(t){try{e.postMessage({...t,windowId:i,type:`side-panel-state-tracker`,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Failed to send message:`,e)}}t=setInterval(()=>{try{e.postMessage({type:`side-panel-heartbeat`,windowId:i,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Heartbeat failed:`,e),t&&clearInterval(t)}},15e3),n=()=>{a({state:document.hidden?`hidden`:`visible`,reason:`visibility-change`})},document.addEventListener(`visibilitychange`,n),e.onDisconnect.addListener(()=>{console.warn(`[side-panel-state-tracker] Background connection lost`),t&&clearInterval(t)}),e.onMessage.addListener(e=>{e.type===`close-side-panel`&&(t&&clearInterval(t),window.close())}),a({state:document.hidden?`hidden`:`visible`,reason:`document-load`})}),()=>{t&&clearInterval(t),n&&document.removeEventListener(`visibilitychange`,n),e&&e.disconnect(),a=!1}}const s=new Map,c=new Set;let l=!1;function u(){l||console.error(`Side panel state manager is not initialized.`,`Call initSidePanelStateManager() at the top of your background script before using any other functions.`)}function d(){l||(l=!0,chrome.action.onClicked.addListener(e=>{let t=s.get(e.windowId);t&&t.state===`visible`?t.port&&(s.delete(e.windowId),t.port.postMessage({type:`close-side-panel`})):chrome.sidePanel.open({windowId:e.windowId}).catch(console.error)}),chrome.runtime.onConnect.addListener(e=>{e.name===chrome.runtime.id&&(e.onMessage.addListener(t=>{t.type===`side-panel-state-tracker`&&t.state&&p({port:e,state:t.state,reason:t.reason??`unknown`,windowId:t.windowId,type:t.type})}),e.onDisconnect.addListener(e=>{Array.from(s.entries()).forEach(([t,n])=>{n.port&&n.port===e&&p({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}))}function f(e){if(e.type!==`side-panel-state-tracker`)return;let t={state:e.state,reason:e.reason,windowId:e.windowId};c.forEach(e=>{try{e(t)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function p(e){let{windowId:t,state:n}=e;if(n===`hidden`){s.delete(t),f(e);return}s.set(t,e),f(e)}function m(e){return u(),s.get(e)?.state}function h(e){return u(),m(e)===`visible`}function g(e){return u(),c.add(e),()=>{c.delete(e)}}exports.getSidePanelStateForWindow=m,exports.initSidePanelStateManager=d,exports.initializeSidePanelStateTracker=o,exports.isWindowSidePanelVisible=h,exports.onSidePanelStateChange=g,exports.onWindowClose=r;
1
+ const e=new Map;let t=!1;function n(){t||(t=!0,chrome.windows.onRemoved.addListener(t=>{let n=e.get(t);n&&(n.forEach(e=>{e()}),e.delete(t))}))}function r(t,r){n();let i=e.get(t);return i||(i=new Set,e.set(t,i)),i.add(r),()=>{let n=e.get(t);n&&(n.delete(r),n.size===0&&e.delete(t))}}const i=250,a=5e3,o=2;let s=!1;function c(e={}){if(s)return console.info(`Side panel state tracker already initialized`),()=>{};s=!0;let{trackDocumentVisibility:t=!0}=e,n,r,i=0,a,o=!1;function c(){r&&=(clearTimeout(r),void 0)}return chrome.windows.getCurrent(e=>{if(o)return;if(e.id==null){console.error(`[side-panel-state-tracker] Could not get window ID`);return}let s=e.id;function l(){return t&&document.hidden?`hidden`:`visible`}function u(){if(o||r)return;n=void 0,console.info(`[side-panel-state-tracker] Connection to background lost, scheduling reconnect...`);let e=Math.min(250*2**i,5e3);i+=1,r=setTimeout(()=>{r=void 0,f(`reconnected`)},e)}function d(e){let t=n;if(!t){u();return}try{t.postMessage({...e,windowId:s,type:`side-panel-state-tracker`,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Failed to send message:`,e),u()}}function f(e){if(!o){c();try{let t=chrome.runtime.connect({name:chrome.runtime.id});n=t,i=0,t.onDisconnect.addListener(()=>{n===t&&(console.info(`[side-panel-state-tracker] Background connection lost.`),u())}),t.onMessage.addListener(e=>{e.type===`close-side-panel`&&(o=!0,c(),window.close())}),d({state:e===`document-load`?`visible`:l(),reason:e})}catch(e){console.error(`[side-panel-state-tracker] Failed to connect:`,e),u()}}}t&&(a=()=>{d({state:l(),reason:`visibility-change`})},document.addEventListener(`visibilitychange`,a)),f(`document-load`)}),()=>{o=!0,c(),a&&document.removeEventListener(`visibilitychange`,a);let e=n;n=void 0,e?.disconnect(),s=!1}}const l=new Map,u=new Set;let d=!1;function f(){d||(d=!0,chrome.action.onClicked.addListener(e=>{let t=l.get(e.windowId);t&&t.state===`visible`?t.port&&(l.delete(e.windowId),t.port.postMessage({type:`close-side-panel`})):chrome.sidePanel.open({windowId:e.windowId}).catch(console.error)}),chrome.runtime.onConnect.addListener(e=>{e.name===chrome.runtime.id&&(e.onMessage.addListener(t=>{t.type===`side-panel-state-tracker`&&t.state&&h({port:e,state:t.state,reason:t.reason??`unknown`,windowId:t.windowId,type:t.type})}),e.onDisconnect.addListener(e=>{Array.from(l.entries()).forEach(([t,n])=>{n.port&&n.port===e&&h({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}))}function p(){d||=(f(),!0)}function m(e){if(e.type!==`side-panel-state-tracker`)return;let t={state:e.state,reason:e.reason,windowId:e.windowId};u.forEach(e=>{try{e(t)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function h(e){let{windowId:t,state:n}=e;if(n===`hidden`){l.delete(t),m(e);return}l.set(t,e),m(e)}function g(e){return p(),l.get(e)?.state}function _(e){return p(),g(e)===`visible`}function v(e){return p(),u.add(e),()=>{u.delete(e)}}exports.getSidePanelStateForWindow=g,exports.initSidePanelStateManager=f,exports.initializeSidePanelStateTracker=c,exports.isWindowSidePanelVisible=_,exports.onSidePanelStateChange=v,exports.onWindowClose=r;
package/dist/index.d.cts CHANGED
@@ -18,10 +18,17 @@ type CloseCallback = () => void;
18
18
  declare function onWindowClose(windowId: number, callback: CloseCallback): () => void;
19
19
  //#endregion
20
20
  //#region src/side-panel-state-tracker.d.ts
21
+ interface InitializeSidePanelStateTrackerOptions {
22
+ /**
23
+ * When true, visibilitychange events report document.hidden as hidden state.
24
+ * Disable this if you only want open/closed port state.
25
+ */
26
+ trackDocumentVisibility?: boolean;
27
+ }
21
28
  /**
22
29
  * Should only be called inside a Chrome extension side panel page.
23
30
  */
24
- declare function initializeSidePanelStateTracker(): () => void;
31
+ declare function initializeSidePanelStateTracker(options?: InitializeSidePanelStateTrackerOptions): () => void;
25
32
  //#endregion
26
33
  //#region src/types/side-panel.d.ts
27
34
  type SidePanelState = 'visible' | 'hidden';
@@ -60,4 +67,4 @@ declare function isWindowSidePanelVisible(windowId: number): boolean;
60
67
  */
61
68
  declare function onSidePanelStateChange(listener: SidePanelStateListener): () => void;
62
69
  //#endregion
63
- export { SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelStateChange, onWindowClose };
70
+ export { InitializeSidePanelStateTrackerOptions, SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelStateChange, onWindowClose };
package/dist/index.d.ts CHANGED
@@ -18,10 +18,17 @@ type CloseCallback = () => void;
18
18
  declare function onWindowClose(windowId: number, callback: CloseCallback): () => void;
19
19
  //#endregion
20
20
  //#region src/side-panel-state-tracker.d.ts
21
+ interface InitializeSidePanelStateTrackerOptions {
22
+ /**
23
+ * When true, visibilitychange events report document.hidden as hidden state.
24
+ * Disable this if you only want open/closed port state.
25
+ */
26
+ trackDocumentVisibility?: boolean;
27
+ }
21
28
  /**
22
29
  * Should only be called inside a Chrome extension side panel page.
23
30
  */
24
- declare function initializeSidePanelStateTracker(): () => void;
31
+ declare function initializeSidePanelStateTracker(options?: InitializeSidePanelStateTrackerOptions): () => void;
25
32
  //#endregion
26
33
  //#region src/types/side-panel.d.ts
27
34
  type SidePanelState = 'visible' | 'hidden';
@@ -60,4 +67,4 @@ declare function isWindowSidePanelVisible(windowId: number): boolean;
60
67
  */
61
68
  declare function onSidePanelStateChange(listener: SidePanelStateListener): () => void;
62
69
  //#endregion
63
- export { SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelStateChange, onWindowClose };
70
+ export { InitializeSidePanelStateTrackerOptions, SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelStateChange, onWindowClose };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- const e=new Map;let t=!1;function n(){t||(t=!0,chrome.windows.onRemoved.addListener(t=>{let n=e.get(t);n&&(n.forEach(e=>{e()}),e.delete(t))}))}function r(t,r){n();let i=e.get(t);return i||(i=new Set,e.set(t,i)),i.add(r),()=>{let n=e.get(t);n&&(n.delete(r),n.size===0&&e.delete(t))}}let i=!1;function a(){i&&console.warn(`Side panel state tracker already initialized`),i=!0;let e,t,n;return chrome.windows.getCurrent(r=>{if(r.id==null){console.error(`[side-panel-state-tracker] Could not get window ID`);return}e=chrome.runtime.connect({name:chrome.runtime.id});let i=r.id;function a(t){try{e.postMessage({...t,windowId:i,type:`side-panel-state-tracker`,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Failed to send message:`,e)}}t=setInterval(()=>{try{e.postMessage({type:`side-panel-heartbeat`,windowId:i,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Heartbeat failed:`,e),t&&clearInterval(t)}},15e3),n=()=>{a({state:document.hidden?`hidden`:`visible`,reason:`visibility-change`})},document.addEventListener(`visibilitychange`,n),e.onDisconnect.addListener(()=>{console.warn(`[side-panel-state-tracker] Background connection lost`),t&&clearInterval(t)}),e.onMessage.addListener(e=>{e.type===`close-side-panel`&&(t&&clearInterval(t),window.close())}),a({state:document.hidden?`hidden`:`visible`,reason:`document-load`})}),()=>{t&&clearInterval(t),n&&document.removeEventListener(`visibilitychange`,n),e&&e.disconnect(),i=!1}}const o=new Map,s=new Set;let c=!1;function l(){c||console.error(`Side panel state manager is not initialized.`,`Call initSidePanelStateManager() at the top of your background script before using any other functions.`)}function u(){c||(c=!0,chrome.action.onClicked.addListener(e=>{let t=o.get(e.windowId);t&&t.state===`visible`?t.port&&(o.delete(e.windowId),t.port.postMessage({type:`close-side-panel`})):chrome.sidePanel.open({windowId:e.windowId}).catch(console.error)}),chrome.runtime.onConnect.addListener(e=>{e.name===chrome.runtime.id&&(e.onMessage.addListener(t=>{t.type===`side-panel-state-tracker`&&t.state&&f({port:e,state:t.state,reason:t.reason??`unknown`,windowId:t.windowId,type:t.type})}),e.onDisconnect.addListener(e=>{Array.from(o.entries()).forEach(([t,n])=>{n.port&&n.port===e&&f({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}))}function d(e){if(e.type!==`side-panel-state-tracker`)return;let t={state:e.state,reason:e.reason,windowId:e.windowId};s.forEach(e=>{try{e(t)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function f(e){let{windowId:t,state:n}=e;if(n===`hidden`){o.delete(t),d(e);return}o.set(t,e),d(e)}function p(e){return l(),o.get(e)?.state}function m(e){return l(),p(e)===`visible`}function h(e){return l(),s.add(e),()=>{s.delete(e)}}export{p as getSidePanelStateForWindow,u as initSidePanelStateManager,a as initializeSidePanelStateTracker,m as isWindowSidePanelVisible,h as onSidePanelStateChange,r as onWindowClose};
1
+ const e=new Map;let t=!1;function n(){t||(t=!0,chrome.windows.onRemoved.addListener(t=>{let n=e.get(t);n&&(n.forEach(e=>{e()}),e.delete(t))}))}function r(t,r){n();let i=e.get(t);return i||(i=new Set,e.set(t,i)),i.add(r),()=>{let n=e.get(t);n&&(n.delete(r),n.size===0&&e.delete(t))}}let i=!1;function a(e={}){if(i)return console.info(`Side panel state tracker already initialized`),()=>{};i=!0;let{trackDocumentVisibility:t=!0}=e,n,r,a=0,o,s=!1;function c(){r&&=(clearTimeout(r),void 0)}return chrome.windows.getCurrent(e=>{if(s)return;if(e.id==null){console.error(`[side-panel-state-tracker] Could not get window ID`);return}let i=e.id;function l(){return t&&document.hidden?`hidden`:`visible`}function u(){if(s||r)return;n=void 0,console.info(`[side-panel-state-tracker] Connection to background lost, scheduling reconnect...`);let e=Math.min(250*2**a,5e3);a+=1,r=setTimeout(()=>{r=void 0,f(`reconnected`)},e)}function d(e){let t=n;if(!t){u();return}try{t.postMessage({...e,windowId:i,type:`side-panel-state-tracker`,timestamp:Date.now()})}catch(e){console.error(`[side-panel-state-tracker] Failed to send message:`,e),u()}}function f(e){if(!s){c();try{let t=chrome.runtime.connect({name:chrome.runtime.id});n=t,a=0,t.onDisconnect.addListener(()=>{n===t&&(console.info(`[side-panel-state-tracker] Background connection lost.`),u())}),t.onMessage.addListener(e=>{e.type===`close-side-panel`&&(s=!0,c(),window.close())}),d({state:e===`document-load`?`visible`:l(),reason:e})}catch(e){console.error(`[side-panel-state-tracker] Failed to connect:`,e),u()}}}t&&(o=()=>{d({state:l(),reason:`visibility-change`})},document.addEventListener(`visibilitychange`,o)),f(`document-load`)}),()=>{s=!0,c(),o&&document.removeEventListener(`visibilitychange`,o);let e=n;n=void 0,e?.disconnect(),i=!1}}const o=new Map,s=new Set;let c=!1;function l(){c||(c=!0,chrome.action.onClicked.addListener(e=>{let t=o.get(e.windowId);t&&t.state===`visible`?t.port&&(o.delete(e.windowId),t.port.postMessage({type:`close-side-panel`})):chrome.sidePanel.open({windowId:e.windowId}).catch(console.error)}),chrome.runtime.onConnect.addListener(e=>{e.name===chrome.runtime.id&&(e.onMessage.addListener(t=>{t.type===`side-panel-state-tracker`&&t.state&&f({port:e,state:t.state,reason:t.reason??`unknown`,windowId:t.windowId,type:t.type})}),e.onDisconnect.addListener(e=>{Array.from(o.entries()).forEach(([t,n])=>{n.port&&n.port===e&&f({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}))}function u(){c||=(l(),!0)}function d(e){if(e.type!==`side-panel-state-tracker`)return;let t={state:e.state,reason:e.reason,windowId:e.windowId};s.forEach(e=>{try{e(t)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function f(e){let{windowId:t,state:n}=e;if(n===`hidden`){o.delete(t),d(e);return}o.set(t,e),d(e)}function p(e){return u(),o.get(e)?.state}function m(e){return u(),p(e)===`visible`}function h(e){return u(),s.add(e),()=>{s.delete(e)}}export{p as getSidePanelStateForWindow,l as initSidePanelStateManager,a as initializeSidePanelStateTracker,m as isWindowSidePanelVisible,h as onSidePanelStateChange,r as onWindowClose};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pixpilot/chrome-lifecycle",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "0.8.0",
5
5
  "description": "Lifecycle management utilities for Chrome extensions.",
6
6
  "author": "m.doaie <m.doaie@hotmail.com>",
7
7
  "license": "MIT",
@@ -30,10 +30,10 @@
30
30
  "tsdown": "^0.15.9",
31
31
  "typescript": "^5.9.3",
32
32
  "@internal/eslint-config": "0.3.0",
33
- "@internal/tsdown-config": "0.1.0",
34
33
  "@internal/prettier-config": "0.1.0",
35
34
  "@internal/vitest-config": "0.1.0",
36
- "@internal/tsconfig": "0.1.0"
35
+ "@internal/tsconfig": "0.1.0",
36
+ "@internal/tsdown-config": "0.1.0"
37
37
  },
38
38
  "prettier": "@internal/prettier-config",
39
39
  "scripts": {