@pixpilot/chrome-lifecycle 0.7.0 → 0.9.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 +71 -7
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +48 -3
- package/dist/index.d.ts +48 -3
- package/dist/index.js +1 -1
- package/package.json +4 -4
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
|
|
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.
|
|
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.
|
|
@@ -121,10 +127,68 @@ unsubscribe();
|
|
|
121
127
|
|
|
122
128
|
**Callback data:**
|
|
123
129
|
|
|
124
|
-
| Property
|
|
125
|
-
|
|
|
126
|
-
| `windowId`
|
|
127
|
-
| `state`
|
|
128
|
-
| `
|
|
130
|
+
| Property | Type | Description |
|
|
131
|
+
| --------------- | ---------------------------------------- | ----------------------------------------------- |
|
|
132
|
+
| `windowId` | `number` | Chrome window ID |
|
|
133
|
+
| `state` | `'visible'` \| `'hidden'` | Current side panel state |
|
|
134
|
+
| `previousState` | `'visible'` \| `'hidden'` \| `undefined` | State known before this one, `undefined` if new |
|
|
135
|
+
| `reason` | `string` | What triggered the change |
|
|
136
|
+
|
|
137
|
+
**Returns:** Unsubscribe function
|
|
138
|
+
|
|
139
|
+
This fires for **every** state report, including repeats of a state you already know
|
|
140
|
+
about — the tracker re-reports `visible` when it reconnects to a restarted service
|
|
141
|
+
worker, and can report `hidden` twice in a row (a visibility change followed by a
|
|
142
|
+
port disconnect). If you want transitions rather than reports, use
|
|
143
|
+
`onSidePanelShown` / `onSidePanelHidden` below instead of comparing states yourself.
|
|
144
|
+
|
|
145
|
+
##### `onSidePanelShown(listener)`
|
|
146
|
+
|
|
147
|
+
Fires only when a side panel _becomes_ visible. This is the event to use for "the
|
|
148
|
+
panel is back on screen, resync it".
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { onSidePanelShown } from '@pixpilot/chrome-lifecycle';
|
|
152
|
+
|
|
153
|
+
const unsubscribe = onSidePanelShown(({ windowId, reason }) => {
|
|
154
|
+
refreshPanelContents(windowId);
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
It covers three cases, distinguishable via `reason`:
|
|
159
|
+
|
|
160
|
+
| `reason` | When |
|
|
161
|
+
| ------------------- | ---------------------------------------------------------------------------------- |
|
|
162
|
+
| `document-load` | A freshly loaded panel document |
|
|
163
|
+
| `visibility-change` | A cached document Chrome is showing again — see the note on other extensions below |
|
|
164
|
+
| `reconnected` | First report after the service worker restarted, so your background state was lost |
|
|
165
|
+
|
|
166
|
+
**Callback data:** same as `onSidePanelStateChange`
|
|
167
|
+
|
|
168
|
+
**Returns:** Unsubscribe function
|
|
169
|
+
|
|
170
|
+
##### `onSidePanelHidden(listener)`
|
|
171
|
+
|
|
172
|
+
Fires only when a side panel stops being visible. Nothing fires for a window that
|
|
173
|
+
was never seen visible, so a service worker restart followed by a port disconnect
|
|
174
|
+
stays quiet instead of reporting a close the listener never saw open.
|
|
175
|
+
|
|
176
|
+
**Callback data:** same as `onSidePanelStateChange`
|
|
129
177
|
|
|
130
178
|
**Returns:** Unsubscribe function
|
|
179
|
+
|
|
180
|
+
#### When another extension's side panel takes over
|
|
181
|
+
|
|
182
|
+
Chrome gives all extensions one side panel slot per window. When the user opens a
|
|
183
|
+
different extension's panel, yours does **not** get torn down — Chrome keeps the
|
|
184
|
+
document alive and hides it, so it comes back with all of its state intact,
|
|
185
|
+
including anything stale it was showing before the switch.
|
|
186
|
+
|
|
187
|
+
That surfaces here as `visibility-change`, never `document-load`. A listener that
|
|
188
|
+
only refreshes on `document-load` will look correct until a user has two side panel
|
|
189
|
+
extensions installed, and then silently serve stale content. `onSidePanelShown`
|
|
190
|
+
handles both.
|
|
191
|
+
|
|
192
|
+
There is no Chrome API to ask whether your panel is the one currently on screen, so
|
|
193
|
+
`document.hidden` in the panel document is the only available signal. Leave
|
|
194
|
+
`trackDocumentVisibility` enabled if you rely on this.
|
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=
|
|
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 Map,d=new Set;let f=!1;function p(){f||(f=!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&&g({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&&g({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}),chrome.windows.onRemoved.addListener(e=>{l.delete(e),u.delete(e)}))}function m(){f||=(p(),!0)}function h(e,t){if(e.type!==`side-panel-state-tracker`)return;let n={state:e.state,reason:e.reason,windowId:e.windowId,previousState:t};d.forEach(e=>{try{e(n)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function g(e){let{windowId:t,state:n}=e,r=u.get(t);if(e.type===`side-panel-state-tracker`&&u.set(t,n),n===`hidden`){l.delete(t),h(e,r);return}l.set(t,e),h(e,r)}function _(e){return m(),l.get(e)?.state}function v(e){return m(),_(e)===`visible`}function y(e){return m(),d.add(e),()=>{d.delete(e)}}function b(e){return y(t=>{t.state===`visible`&&t.previousState!==`visible`&&e(t)})}function x(e){return y(t=>{t.state===`hidden`&&t.previousState===`visible`&&e(t)})}exports.getSidePanelStateForWindow=_,exports.initSidePanelStateManager=p,exports.initializeSidePanelStateTracker=c,exports.isWindowSidePanelVisible=v,exports.onSidePanelHidden=x,exports.onSidePanelShown=b,exports.onSidePanelStateChange=y,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';
|
|
@@ -34,7 +41,19 @@ interface SidePanelStateData extends BaseSidePanelMessage {
|
|
|
34
41
|
state: SidePanelState;
|
|
35
42
|
reason: string;
|
|
36
43
|
}
|
|
37
|
-
|
|
44
|
+
interface SidePanelStateChangeData extends Omit<SidePanelStateData, 'timestamp' | 'type'> {
|
|
45
|
+
/**
|
|
46
|
+
* The state recorded for this window before this change.
|
|
47
|
+
*
|
|
48
|
+
* `undefined` means nothing was recorded yet: the first event for a window, or
|
|
49
|
+
* the first event after the service worker restarted and lost its state. Use it
|
|
50
|
+
* to tell a real transition apart from a repeat of the state you already knew —
|
|
51
|
+
* the tracker reports the same state more than once (a reconnect while still
|
|
52
|
+
* visible, consecutive visibility changes), so plain state events are not
|
|
53
|
+
* transitions.
|
|
54
|
+
*/
|
|
55
|
+
previousState?: SidePanelState;
|
|
56
|
+
}
|
|
38
57
|
interface SidePanelClientMessage {
|
|
39
58
|
type: 'close-side-panel';
|
|
40
59
|
}
|
|
@@ -59,5 +78,31 @@ declare function isWindowSidePanelVisible(windowId: number): boolean;
|
|
|
59
78
|
* @returns Unsubscribe function to remove the listener
|
|
60
79
|
*/
|
|
61
80
|
declare function onSidePanelStateChange(listener: SidePanelStateListener): () => void;
|
|
81
|
+
/**
|
|
82
|
+
* Adds a listener that fires only when a side panel *becomes* visible, skipping
|
|
83
|
+
* repeats of a state you already knew about.
|
|
84
|
+
*
|
|
85
|
+
* This is the event to use for "the panel is back on screen, resync it". It covers
|
|
86
|
+
* a freshly loaded document (`reason: 'document-load'`), a panel Chrome had cached
|
|
87
|
+
* while another extension's side panel took over the slot
|
|
88
|
+
* (`reason: 'visibility-change'`), and the first report after a service worker
|
|
89
|
+
* restart (`reason: 'reconnected'`).
|
|
90
|
+
*
|
|
91
|
+
* @param listener - Callback function that receives state change data
|
|
92
|
+
* @returns Unsubscribe function to remove the listener
|
|
93
|
+
*/
|
|
94
|
+
declare function onSidePanelShown(listener: SidePanelStateListener): () => void;
|
|
95
|
+
/**
|
|
96
|
+
* Adds a listener that fires only when a side panel *stops* being visible, skipping
|
|
97
|
+
* repeats of a state you already knew about.
|
|
98
|
+
*
|
|
99
|
+
* Nothing fires for a window that was never seen visible, so a service worker
|
|
100
|
+
* restart followed by a port disconnect stays quiet instead of reporting a close
|
|
101
|
+
* that the listener never saw open.
|
|
102
|
+
*
|
|
103
|
+
* @param listener - Callback function that receives state change data
|
|
104
|
+
* @returns Unsubscribe function to remove the listener
|
|
105
|
+
*/
|
|
106
|
+
declare function onSidePanelHidden(listener: SidePanelStateListener): () => void;
|
|
62
107
|
//#endregion
|
|
63
|
-
export { SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelStateChange, onWindowClose };
|
|
108
|
+
export { InitializeSidePanelStateTrackerOptions, SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, SidePanelStateListener, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelHidden, onSidePanelShown, 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';
|
|
@@ -34,7 +41,19 @@ interface SidePanelStateData extends BaseSidePanelMessage {
|
|
|
34
41
|
state: SidePanelState;
|
|
35
42
|
reason: string;
|
|
36
43
|
}
|
|
37
|
-
|
|
44
|
+
interface SidePanelStateChangeData extends Omit<SidePanelStateData, 'timestamp' | 'type'> {
|
|
45
|
+
/**
|
|
46
|
+
* The state recorded for this window before this change.
|
|
47
|
+
*
|
|
48
|
+
* `undefined` means nothing was recorded yet: the first event for a window, or
|
|
49
|
+
* the first event after the service worker restarted and lost its state. Use it
|
|
50
|
+
* to tell a real transition apart from a repeat of the state you already knew —
|
|
51
|
+
* the tracker reports the same state more than once (a reconnect while still
|
|
52
|
+
* visible, consecutive visibility changes), so plain state events are not
|
|
53
|
+
* transitions.
|
|
54
|
+
*/
|
|
55
|
+
previousState?: SidePanelState;
|
|
56
|
+
}
|
|
38
57
|
interface SidePanelClientMessage {
|
|
39
58
|
type: 'close-side-panel';
|
|
40
59
|
}
|
|
@@ -59,5 +78,31 @@ declare function isWindowSidePanelVisible(windowId: number): boolean;
|
|
|
59
78
|
* @returns Unsubscribe function to remove the listener
|
|
60
79
|
*/
|
|
61
80
|
declare function onSidePanelStateChange(listener: SidePanelStateListener): () => void;
|
|
81
|
+
/**
|
|
82
|
+
* Adds a listener that fires only when a side panel *becomes* visible, skipping
|
|
83
|
+
* repeats of a state you already knew about.
|
|
84
|
+
*
|
|
85
|
+
* This is the event to use for "the panel is back on screen, resync it". It covers
|
|
86
|
+
* a freshly loaded document (`reason: 'document-load'`), a panel Chrome had cached
|
|
87
|
+
* while another extension's side panel took over the slot
|
|
88
|
+
* (`reason: 'visibility-change'`), and the first report after a service worker
|
|
89
|
+
* restart (`reason: 'reconnected'`).
|
|
90
|
+
*
|
|
91
|
+
* @param listener - Callback function that receives state change data
|
|
92
|
+
* @returns Unsubscribe function to remove the listener
|
|
93
|
+
*/
|
|
94
|
+
declare function onSidePanelShown(listener: SidePanelStateListener): () => void;
|
|
95
|
+
/**
|
|
96
|
+
* Adds a listener that fires only when a side panel *stops* being visible, skipping
|
|
97
|
+
* repeats of a state you already knew about.
|
|
98
|
+
*
|
|
99
|
+
* Nothing fires for a window that was never seen visible, so a service worker
|
|
100
|
+
* restart followed by a port disconnect stays quiet instead of reporting a close
|
|
101
|
+
* that the listener never saw open.
|
|
102
|
+
*
|
|
103
|
+
* @param listener - Callback function that receives state change data
|
|
104
|
+
* @returns Unsubscribe function to remove the listener
|
|
105
|
+
*/
|
|
106
|
+
declare function onSidePanelHidden(listener: SidePanelStateListener): () => void;
|
|
62
107
|
//#endregion
|
|
63
|
-
export { SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelStateChange, onWindowClose };
|
|
108
|
+
export { InitializeSidePanelStateTrackerOptions, SidePanelClientMessage, SidePanelState, SidePanelStateChangeData, SidePanelStateData, SidePanelStateListener, getSidePanelStateForWindow, initSidePanelStateManager, initializeSidePanelStateTracker, isWindowSidePanelVisible, onSidePanelHidden, onSidePanelShown, 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
|
|
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 Map,c=new Set;let l=!1;function u(){l||(l=!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&&p({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&&p({port:void 0,state:`hidden`,reason:`port-disconnected`,windowId:t,type:`side-panel-state-tracker`})})}))}),chrome.windows.onRemoved.addListener(e=>{o.delete(e),s.delete(e)}))}function d(){l||=(u(),!0)}function f(e,t){if(e.type!==`side-panel-state-tracker`)return;let n={state:e.state,reason:e.reason,windowId:e.windowId,previousState:t};c.forEach(e=>{try{e(n)}catch(e){console.error(`Error in side panel state listener:`,e)}})}function p(e){let{windowId:t,state:n}=e,r=s.get(t);if(e.type===`side-panel-state-tracker`&&s.set(t,n),n===`hidden`){o.delete(t),f(e,r);return}o.set(t,e),f(e,r)}function m(e){return d(),o.get(e)?.state}function h(e){return d(),m(e)===`visible`}function g(e){return d(),c.add(e),()=>{c.delete(e)}}function _(e){return g(t=>{t.state===`visible`&&t.previousState!==`visible`&&e(t)})}function v(e){return g(t=>{t.state===`hidden`&&t.previousState===`visible`&&e(t)})}export{m as getSidePanelStateForWindow,u as initSidePanelStateManager,a as initializeSidePanelStateTracker,h as isWindowSidePanelVisible,v as onSidePanelHidden,_ as onSidePanelShown,g 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.
|
|
4
|
+
"version": "0.9.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/prettier-config": "0.1.0",
|
|
34
|
-
"@internal/tsdown-config": "0.1.0",
|
|
35
33
|
"@internal/tsconfig": "0.1.0",
|
|
36
|
-
"@internal/
|
|
34
|
+
"@internal/prettier-config": "0.1.0",
|
|
35
|
+
"@internal/vitest-config": "0.1.0",
|
|
36
|
+
"@internal/tsdown-config": "0.1.0"
|
|
37
37
|
},
|
|
38
38
|
"prettier": "@internal/prettier-config",
|
|
39
39
|
"scripts": {
|