@wcstack/wakelock 1.12.1
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.ja.md +173 -0
- package/README.md +175 -0
- package/dist/auto.js +3 -0
- package/dist/auto.min.js +1 -0
- package/dist/index.d.ts +250 -0
- package/dist/index.esm.js +524 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.esm.min.js +2 -0
- package/dist/index.esm.min.js.map +1 -0
- package/package.json +71 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# @wcstack/wakelock
|
|
2
|
+
|
|
3
|
+
`@wcstack/wakelock` は wcstack エコシステムのためのヘッドレスな Screen Wake Lock コンポーネントです。
|
|
4
|
+
|
|
5
|
+
視覚的な UI ウィジェットではありません。
|
|
6
|
+
**非同期プリミティブノード** ですが、他のすべての @wcstack センサーとは *逆向き* に動作します。`@wcstack/geolocation` や `@wcstack/intersection` などは **プロデューサー**(`element → state`)で、デバイスのシグナルをリアクティブな状態に変換します。`@wcstack/wakelock` は **純粋なシンク**(`state → element`)で、バインドされた真偽値が画面を起こし続けるかどうかを駆動します。
|
|
7
|
+
|
|
8
|
+
`@wcstack/state` と組み合わせると、`<wcs-wakelock>` は宣言的な1行として読めます:
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
<wcs-wakelock data-wcs="active: isPlaying"></wcs-wakelock>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
*「`isPlaying` が true である **間** 画面を起こし続ける」*。`navigator.wakeLock.request()` も、`visibilitychange` での再取得のグルーコードも、後始末も不要です。
|
|
15
|
+
|
|
16
|
+
- **入力サーフェス**: `active`, `type`, `manual`
|
|
17
|
+
- **出力ステートサーフェス**: `held`, `error`
|
|
18
|
+
- **コマンド**: `request()`, `release()`
|
|
19
|
+
|
|
20
|
+
`@wcstack/wakelock` は [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md)(Core / Shell / Binding Contract)アーキテクチャに従います:
|
|
21
|
+
|
|
22
|
+
- **Core** (`WakeLockCore`) が sentinel、desired/actual の分離、自動解放後の再取得ループ、never-throw な失敗処理を所有
|
|
23
|
+
- **Shell** (`<wcs-wakelock>`) が `active` 属性を `request()` / `release()` にマップし、ライフサイクルを管理
|
|
24
|
+
- **Binding Contract** (`static wcBindable`) が観測可能な `properties`、書き込み可能な `inputs`、呼び出し可能な `commands` を宣言
|
|
25
|
+
|
|
26
|
+
## なぜ存在するのか
|
|
27
|
+
|
|
28
|
+
Screen Wake Lock API には扱いにくい性質があります。ページが可視でなくなった瞬間(タブが非表示、ウィンドウが最小化)に、OS が **自動的にロックを解放** してしまうのです。実際に「再生中は画面を起こし続ける」を実現するには、望み(desired intent)を自分で保持し、ページが再び可視になるたびにロックを再取得しなければなりません。
|
|
29
|
+
|
|
30
|
+
`@wcstack/wakelock` はそのリース管理をコンポーネントの内側に閉じ込めます。真偽値をバインドすれば、その値が true である限り、コンポーネントが可視性変化をまたいでロックを生かし続けます。
|
|
31
|
+
|
|
32
|
+
## 望み(`active`)と実状態(`held`)
|
|
33
|
+
|
|
34
|
+
自動解放があるため、*望んでいること* と *いま実際に保持していること* は乖離します。そこで両者は別々のサーフェスになっています:
|
|
35
|
+
|
|
36
|
+
| サーフェス | 向き | 意味 |
|
|
37
|
+
|----------|---------|------|
|
|
38
|
+
| `active` | 入力 | **望み(desired intent)。** `true` の間ロックを保持。OS の自動解放を越えて維持される。 |
|
|
39
|
+
| `held` | 出力 | **実状態。** *いま* sentinel を保持しているか。自動解放で `false` になり、ページが再び可視になると `true` に戻る。 |
|
|
40
|
+
|
|
41
|
+
`active` は意図的に観測可能な出力 **ではありません**。OS がロックを落としても変化せず、変化するのは `held` だけだからです。UI でライブな「画面が起き続けている」状態を反映したい場合は `held` をバインドしてください。
|
|
42
|
+
|
|
43
|
+
## インストール
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @wcstack/wakelock
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## クイックスタート
|
|
50
|
+
|
|
51
|
+
### 動画再生中に画面を起こし続ける
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
55
|
+
<script type="module" src="https://esm.run/@wcstack/wakelock/auto"></script>
|
|
56
|
+
|
|
57
|
+
<wcs-state>
|
|
58
|
+
<script type="module">
|
|
59
|
+
export default {
|
|
60
|
+
playing: false,
|
|
61
|
+
startPlaying() { this.playing = true; },
|
|
62
|
+
stopPlaying() { this.playing = false; },
|
|
63
|
+
};
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<video
|
|
67
|
+
src="/movie.mp4"
|
|
68
|
+
data-wcs="onplay: startPlaying; onpause: stopPlaying"
|
|
69
|
+
></video>
|
|
70
|
+
|
|
71
|
+
<!-- ロックは `playing` が true の間だけ保持され、タブ切り替えを越えて生き残る。 -->
|
|
72
|
+
<wcs-wakelock data-wcs="active: playing"></wcs-wakelock>
|
|
73
|
+
</wcs-state>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 実際のロック状態を UI に反映する
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<wcs-wakelock data-wcs="active: keepAwake; held: screenLocked"></wcs-wakelock>
|
|
80
|
+
|
|
81
|
+
<span data-wcs="textContent: screenLocked"></span>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### コマンド駆動(命令的)
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<wcs-wakelock data-wcs="command.request: $command.stayAwake"></wcs-wakelock>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **コマンドは `active` 属性をミラーしません。** `request` / `release` コマンドは
|
|
91
|
+
> `active` 属性に触れずに Core の望み(desired intent)を直接切り替えるため、コマンド後は
|
|
92
|
+
> 要素の `active` プロパティ(属性のミラー)が `false` のまま `held` が `true`(またはその逆)
|
|
93
|
+
> になりえます。同一要素でコマンド駆動と `active` 属性バインドを混在させないでください
|
|
94
|
+
> — どちらか一方を選ぶこと。単一情報源には `active: ...` でバインドします。
|
|
95
|
+
|
|
96
|
+
## 属性
|
|
97
|
+
|
|
98
|
+
| 属性 | 型 | 既定値 | 説明 |
|
|
99
|
+
|-----------|---------|----------|------|
|
|
100
|
+
| `active` | boolean | `false` | 望み: 存在する間、画面を起こし続ける。看板バインディング(`active: isPlaying`)。 |
|
|
101
|
+
| `type` | string | `screen` | ロックの種類。標準化されているのは `screen` のみ。属性は将来互換のために存在。 |
|
|
102
|
+
| `manual` | boolean | `false` | `active` が付いていても接続時に自動取得しない。代わりに `request()` / `release()` で駆動する。 |
|
|
103
|
+
|
|
104
|
+
> **`manual` は接続時のポリシーであり、ライブなスイッチではありません。** 接続 *後* に `manual` 属性を外しても自動取得はしません — `active` をトグルするか `request()` を呼んでください。(ライブな `active` のトグルは `manual` に関係なく常に request/release を駆動します。)
|
|
105
|
+
|
|
106
|
+
## 出力ステート
|
|
107
|
+
|
|
108
|
+
| プロパティ | 型 | 説明 |
|
|
109
|
+
|----------|------------------|------|
|
|
110
|
+
| `held` | `boolean` | wake lock の sentinel をいま保持しているか。OS の自動解放と再取得を反映する。 |
|
|
111
|
+
| `error` | `Error \| null` | 直近のリクエスト失敗(拒否、未対応など)、または `null`。 |
|
|
112
|
+
|
|
113
|
+
> **`wcs-wakelock:error` は単なる失敗シグナルではなく、プロパティ変更通知**(wc-bindable モデル)です: 失敗時には `detail` = エラーで発火し、その後のリクエストが成功して `error` プロパティがクリアされると `detail = null` で再度発火します。`error == null` は「過去に一度もエラーがなかった」ではなく「いまエラーがない」と解釈してください。
|
|
114
|
+
|
|
115
|
+
## コマンド
|
|
116
|
+
|
|
117
|
+
| コマンド | 説明 |
|
|
118
|
+
|-------------|------|
|
|
119
|
+
| `request()` | ロックを望み状態にして取得する(可視かつ対応環境なら)。reject しない — `error` を参照。 |
|
|
120
|
+
| `release()` | ロックを望まない状態にして、保持中の sentinel を解放する。 |
|
|
121
|
+
|
|
122
|
+
## Binding Contract(`wcBindable`)
|
|
123
|
+
|
|
124
|
+
Core も Shell も [wc-bindable](https://github.com/csbc-dev) プロトコルを宣言します。
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
// WakeLockCore (ヘッドレス)
|
|
128
|
+
WakeLockCore.wcBindable = {
|
|
129
|
+
protocol: "wc-bindable",
|
|
130
|
+
version: 1,
|
|
131
|
+
properties: [
|
|
132
|
+
{ name: "held", event: "wcs-wakelock:held-changed" },
|
|
133
|
+
{ name: "error", event: "wcs-wakelock:error" },
|
|
134
|
+
],
|
|
135
|
+
commands: [
|
|
136
|
+
{ name: "request", async: true }, { name: "release" },
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Shell(`<wcs-wakelock>`)は Core の `properties` / `commands` を継承し、DOM 駆動の `inputs`(`active`, `type`, `manual`)を宣言します。
|
|
142
|
+
|
|
143
|
+
## Core を単体で使う
|
|
144
|
+
|
|
145
|
+
`WakeLockCore` はフレームワーク非依存で、カスタム要素なしでも使えます:
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
import { WakeLockCore } from "@wcstack/wakelock";
|
|
149
|
+
|
|
150
|
+
const core = new WakeLockCore();
|
|
151
|
+
core.addEventListener("wcs-wakelock:held-changed", (e) => {
|
|
152
|
+
console.log("screen awake:", e.detail);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await core.request(); // 取得(そして可視性変化をまたいで維持)
|
|
156
|
+
// 後で
|
|
157
|
+
core.release(); // 起こし続けるのを停止
|
|
158
|
+
core.dispose(); // visibilitychange リスナを除去
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Core を直接駆動する場合は、終わったら `dispose()` を呼んで `visibilitychange` リスナを除去してください。
|
|
162
|
+
|
|
163
|
+
## ノートと制限
|
|
164
|
+
|
|
165
|
+
- **プロデューサーではなくシンク。** 他の @wcstack センサーとは違い、wake lock は状態に *よって* 駆動されます(`state → element`)。それが返すのは `held` だけです。
|
|
166
|
+
- **自動解放は肩代わりされます。** OS は複数の理由でロックを落とします — ページの非表示だけでなく、ページが可視のままでもバッテリー低下や省電力モードで落ちることがあります。コンポーネントはどちらの場合もリースを更新します: *可視*中の解放は即座に再取得し、*非表示*中の解放は次に可視へ戻ったときに再取得します(`active` が立っている限り)。このバインディングは「一度取得する」ではなく「active の **間** 起こし続ける」を意味します。再取得が**拒否**された場合はループせず、`error` で surface して静止します(支配的なケース: 仕様上バッテリー低下/省電力下の再リクエストは拒否されます)。再取得が**許可**された場合はリースを更新するため、OS が許可と解放を繰り返す限りコンポーネントも更新を続けます(解放1回につき request 1回、OS 駆動であり同期的なスピンではありません)。
|
|
167
|
+
- **セキュアコンテキスト(HTTPS)。** Screen Wake Lock API はセキュアコンテキスト(HTTPS、または `localhost`)でのみ動作します。
|
|
168
|
+
- **決して throw しない。** 未対応環境はサイレントな no-op(`held` は `false` のまま)。拒否されたリクエストは throw せず `error` プロパティで surface します。`request()` は決して reject しません。
|
|
169
|
+
- **パーミッションゲートなし。** 別個のパーミッションプロンプトはありません(ページが可視でない場合はリクエストが拒否されることがあり、それは `error` として surface します)。
|
|
170
|
+
|
|
171
|
+
## ライセンス
|
|
172
|
+
|
|
173
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# @wcstack/wakelock
|
|
2
|
+
|
|
3
|
+
`@wcstack/wakelock` is a headless Screen Wake Lock component for the wcstack ecosystem.
|
|
4
|
+
|
|
5
|
+
It is not a visual UI widget.
|
|
6
|
+
It is an **async primitive node** — but, unlike every other @wcstack sensor, it runs *the other way*. `@wcstack/geolocation`, `@wcstack/intersection`, and friends are **producers** (`element → state`): they turn a device signal into reactive state. `@wcstack/wakelock` is a **pure sink** (`state → element`): a bound boolean drives whether the screen is kept awake.
|
|
7
|
+
|
|
8
|
+
With `@wcstack/state`, `<wcs-wakelock>` reads as one declarative line:
|
|
9
|
+
|
|
10
|
+
```html
|
|
11
|
+
<wcs-wakelock data-wcs="active: isPlaying"></wcs-wakelock>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
*"Keep the screen awake **while** `isPlaying` is true."* No `navigator.wakeLock.request()`, no `visibilitychange` re-acquire glue, no teardown.
|
|
15
|
+
|
|
16
|
+
- **input surface**: `active`, `type`, `manual`
|
|
17
|
+
- **output state surface**: `held`, `error`
|
|
18
|
+
- **commands**: `request()`, `release()`
|
|
19
|
+
|
|
20
|
+
`@wcstack/wakelock` follows the [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md) (Core / Shell / Binding Contract) architecture:
|
|
21
|
+
|
|
22
|
+
- **Core** (`WakeLockCore`) owns the sentinel, the desired/actual split, the auto-release re-acquire loop, and never-throw failure handling
|
|
23
|
+
- **Shell** (`<wcs-wakelock>`) maps the `active` attribute to `request()` / `release()` and manages lifecycle
|
|
24
|
+
- **Binding Contract** (`static wcBindable`) declares observable `properties`, writable `inputs`, and callable `commands`
|
|
25
|
+
|
|
26
|
+
## Why this exists
|
|
27
|
+
|
|
28
|
+
The Screen Wake Lock API has an awkward edge: the OS **automatically releases** the lock the moment the page stops being visible (tab hidden, window minimized). To actually "keep the screen awake while playing", you must hold the desired intent yourself and re-acquire the lock every time the page becomes visible again.
|
|
29
|
+
|
|
30
|
+
`@wcstack/wakelock` moves that lease management into the component. You bind a boolean; the component keeps the lock alive across visibility changes for as long as the boolean is true.
|
|
31
|
+
|
|
32
|
+
## Desired (`active`) vs actual (`held`)
|
|
33
|
+
|
|
34
|
+
Because of auto-release, *what you want* and *what is currently held* diverge — so they are two separate surfaces:
|
|
35
|
+
|
|
36
|
+
| Surface | Direction | Meaning |
|
|
37
|
+
|----------|------------------|---------|
|
|
38
|
+
| `active` | input | **Desired intent.** Hold the lock while `true`. Survives an OS auto-release. |
|
|
39
|
+
| `held` | output | **Actual state.** Whether a sentinel is held *right now*. Flips to `false` on auto-release, back to `true` when the page is visible again. |
|
|
40
|
+
|
|
41
|
+
`active` is deliberately **not** an observable output: it does not change when the OS drops the lock — only `held` does. Bind `held` if your UI needs to reflect the live "screen is being kept awake" state.
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @wcstack/wakelock
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Keep the screen awake during video playback
|
|
52
|
+
|
|
53
|
+
```html
|
|
54
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
55
|
+
<script type="module" src="https://esm.run/@wcstack/wakelock/auto"></script>
|
|
56
|
+
|
|
57
|
+
<wcs-state>
|
|
58
|
+
<script type="module">
|
|
59
|
+
export default {
|
|
60
|
+
playing: false,
|
|
61
|
+
startPlaying() { this.playing = true; },
|
|
62
|
+
stopPlaying() { this.playing = false; },
|
|
63
|
+
};
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<video
|
|
67
|
+
src="/movie.mp4"
|
|
68
|
+
data-wcs="onplay: startPlaying; onpause: stopPlaying"
|
|
69
|
+
></video>
|
|
70
|
+
|
|
71
|
+
<!-- Lock is held only while `playing` is true, and survives tab switches. -->
|
|
72
|
+
<wcs-wakelock data-wcs="active: playing"></wcs-wakelock>
|
|
73
|
+
</wcs-state>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Reflect the actual lock state in the UI
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<wcs-wakelock data-wcs="active: keepAwake; held: screenLocked"></wcs-wakelock>
|
|
80
|
+
|
|
81
|
+
<span data-wcs="textContent: screenLocked"></span>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Command-driven (imperative)
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<wcs-wakelock data-wcs="command.request: $command.stayAwake"></wcs-wakelock>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> **Commands do not mirror the `active` attribute.** The `request` / `release`
|
|
91
|
+
> commands flip the desired intent on the Core directly without touching the
|
|
92
|
+
> `active` attribute, so the element's `active` property (an attribute mirror) can
|
|
93
|
+
> read `false` while `held` is `true` (or vice versa) after a command. Don't mix
|
|
94
|
+
> command-driven control with `active` attribute binding on the same element —
|
|
95
|
+
> pick one. Bind via `active: ...` for a single source of truth.
|
|
96
|
+
|
|
97
|
+
## Attributes
|
|
98
|
+
|
|
99
|
+
| Attribute | Type | Default | Description |
|
|
100
|
+
|-----------|---------|----------|-------------|
|
|
101
|
+
| `active` | boolean | `false` | Desired intent: hold the screen awake while present. The headline binding (`active: isPlaying`). |
|
|
102
|
+
| `type` | string | `screen` | Lock type. Only `screen` is standardized; the attribute exists for forward compatibility. |
|
|
103
|
+
| `manual` | boolean | `false` | Do not auto-acquire on connect even if `active` is present; drive via `request()` / `release()` instead. |
|
|
104
|
+
|
|
105
|
+
> **`manual` is a connect-time policy, not a live switch.** Removing the `manual` attribute *after* connect does not auto-acquire — toggle `active` or call `request()`. (A live `active` toggle always drives request/release regardless of `manual`.)
|
|
106
|
+
|
|
107
|
+
## Output state
|
|
108
|
+
|
|
109
|
+
| Property | Type | Description |
|
|
110
|
+
|----------|------------------|-------------|
|
|
111
|
+
| `held` | `boolean` | Whether a wake lock sentinel is currently held. Reflects OS auto-release and re-acquisition. |
|
|
112
|
+
| `error` | `Error \| null` | The last request failure (e.g. denied, unsupported), or `null`. |
|
|
113
|
+
|
|
114
|
+
> **`wcs-wakelock:error` is a property-change notification** (wc-bindable model), not just a failure signal: it fires with `detail` = the Error on a failed request, and fires again with `detail = null` when a later request succeeds and the `error` property is cleared. Read `error == null` as "no error *right now*", not "an error never happened".
|
|
115
|
+
|
|
116
|
+
## Commands
|
|
117
|
+
|
|
118
|
+
| Command | Description |
|
|
119
|
+
|-------------|-------------|
|
|
120
|
+
| `request()` | Mark the lock desired and acquire it (if visible & supported). Never rejects — see `error`. |
|
|
121
|
+
| `release()` | Mark the lock no longer desired and release any held sentinel. |
|
|
122
|
+
|
|
123
|
+
## Binding Contract (`wcBindable`)
|
|
124
|
+
|
|
125
|
+
Both the Core and the Shell declare the [wc-bindable](https://github.com/csbc-dev) protocol.
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
// WakeLockCore (headless)
|
|
129
|
+
WakeLockCore.wcBindable = {
|
|
130
|
+
protocol: "wc-bindable",
|
|
131
|
+
version: 1,
|
|
132
|
+
properties: [
|
|
133
|
+
{ name: "held", event: "wcs-wakelock:held-changed" },
|
|
134
|
+
{ name: "error", event: "wcs-wakelock:error" },
|
|
135
|
+
],
|
|
136
|
+
commands: [
|
|
137
|
+
{ name: "request", async: true }, { name: "release" },
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The Shell (`<wcs-wakelock>`) inherits the Core's `properties` / `commands` and declares the DOM-driven `inputs` (`active`, `type`, `manual`).
|
|
143
|
+
|
|
144
|
+
## Using the Core standalone
|
|
145
|
+
|
|
146
|
+
`WakeLockCore` is framework-agnostic and can be used without the custom element:
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
import { WakeLockCore } from "@wcstack/wakelock";
|
|
150
|
+
|
|
151
|
+
const core = new WakeLockCore();
|
|
152
|
+
core.addEventListener("wcs-wakelock:held-changed", (e) => {
|
|
153
|
+
console.log("screen awake:", e.detail);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
await core.request(); // acquire (and keep across visibility changes)
|
|
157
|
+
// later
|
|
158
|
+
core.release(); // stop keeping awake
|
|
159
|
+
core.dispose(); // remove the visibilitychange listener
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
When you drive the Core directly, call `dispose()` when you are done so its
|
|
163
|
+
`visibilitychange` listener is removed.
|
|
164
|
+
|
|
165
|
+
## Notes & limitations
|
|
166
|
+
|
|
167
|
+
- **Sink, not producer.** Unlike the other @wcstack sensors, the wake lock is driven *by* state (`state → element`). `held` is the only thing it produces back.
|
|
168
|
+
- **Auto-release is handled for you.** The OS may drop the lock for several reasons — the page being hidden, but also battery-low or power-saver mode while the page stays visible. The component renews the lease in both cases: a release while *visible* is re-acquired immediately, and a release while *hidden* is re-acquired on the next return to visibility — as long as `active` is still set. The binding means "keep awake *while* active", not "acquire once". A **denied** re-acquire does not loop — it surfaces via `error` and stays quiet (the dominant case: the spec rejects re-requests under battery-low / power-saver). A **granted** re-acquire renews the lease, so if the OS keeps granting and releasing, the component keeps renewing (one request per release, driven by the OS, not a synchronous spin).
|
|
169
|
+
- **Secure context (HTTPS).** The Screen Wake Lock API only works in a secure context (HTTPS, or `localhost`).
|
|
170
|
+
- **Never throws.** An unsupported environment is a silent no-op (`held` stays `false`); a rejected request surfaces via the `error` property rather than throwing. `request()` never rejects.
|
|
171
|
+
- **No permission gate.** There is no separate permission prompt (a request may still be denied if the page is not visible — that surfaces as `error`).
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT
|
package/dist/auto.js
ADDED
package/dist/auto.min.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bootstrapWakeLock as o}from"./index.esm.min.js";o();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
interface ITagNames {
|
|
2
|
+
readonly wakelock: string;
|
|
3
|
+
}
|
|
4
|
+
interface IWritableTagNames {
|
|
5
|
+
wakelock?: string;
|
|
6
|
+
}
|
|
7
|
+
interface IConfig {
|
|
8
|
+
readonly tagNames: ITagNames;
|
|
9
|
+
}
|
|
10
|
+
interface IWritableConfig {
|
|
11
|
+
tagNames?: IWritableTagNames;
|
|
12
|
+
}
|
|
13
|
+
interface IWcBindableProperty {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly event: string;
|
|
16
|
+
readonly getter?: (event: Event) => any;
|
|
17
|
+
}
|
|
18
|
+
interface IWcBindableInput {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly attribute?: string;
|
|
21
|
+
}
|
|
22
|
+
interface IWcBindableCommand {
|
|
23
|
+
readonly name: string;
|
|
24
|
+
readonly async?: boolean;
|
|
25
|
+
}
|
|
26
|
+
interface IWcBindable {
|
|
27
|
+
readonly protocol: "wc-bindable";
|
|
28
|
+
readonly version: number;
|
|
29
|
+
readonly properties: IWcBindableProperty[];
|
|
30
|
+
readonly inputs?: IWcBindableInput[];
|
|
31
|
+
readonly commands?: IWcBindableCommand[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Wake lock type. The spec currently standardizes only `"screen"`; the field
|
|
35
|
+
* exists for forward compatibility with future lock types.
|
|
36
|
+
*/
|
|
37
|
+
type WakeLockKind = "screen";
|
|
38
|
+
/**
|
|
39
|
+
* Value types for WakeLockCore (headless) — the observable state properties.
|
|
40
|
+
* Use with `bind()` from `@wc-bindable/core` for compile-time type checking.
|
|
41
|
+
*
|
|
42
|
+
* Unlike the @wcstack sensor tags (geolocation / intersection), the wake lock is
|
|
43
|
+
* a pure *sink*: a bound state drives whether the lock is held (`active`, an
|
|
44
|
+
* input), and the only outputs are `held` — whether a sentinel is actually held
|
|
45
|
+
* right now — and `error`. `active` (the desired intent) is deliberately not an
|
|
46
|
+
* observable output: it does not change when the OS auto-releases the lock, only
|
|
47
|
+
* `held` does.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const core = new WakeLockCore();
|
|
52
|
+
* bind(core, (name: keyof WcsWakeLockCoreValues, value) => { ... });
|
|
53
|
+
* await core.request();
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
interface WcsWakeLockCoreValues {
|
|
57
|
+
/** Whether a wake lock sentinel is currently held (actual state). */
|
|
58
|
+
held: boolean;
|
|
59
|
+
/** The last request failure, or `null` while none. */
|
|
60
|
+
error: Error | null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Value types for the Shell (`<wcs-wakelock>`) — identical observable surface to
|
|
64
|
+
* the Core (`held` / `error`).
|
|
65
|
+
*/
|
|
66
|
+
type WcsWakeLockValues = WcsWakeLockCoreValues;
|
|
67
|
+
interface WcsWakeLockInputs {
|
|
68
|
+
/**
|
|
69
|
+
* Desired intent: hold the screen awake while `true`. The headline declarative
|
|
70
|
+
* binding (`active@isPlaying`). Mirrored to the `active` boolean attribute.
|
|
71
|
+
* Setting it `false` releases the lock. It stays `true` across an OS auto-release
|
|
72
|
+
* (tab hidden) so the lock is re-acquired when the page becomes visible again —
|
|
73
|
+
* read `held` for the actual current state.
|
|
74
|
+
*/
|
|
75
|
+
active: boolean;
|
|
76
|
+
/** Lock type. Only `"screen"` is standardized; defaults to `"screen"`. */
|
|
77
|
+
type: WakeLockKind;
|
|
78
|
+
/** Do not auto-acquire on connect even if `active` is present; drive via commands. */
|
|
79
|
+
manual: boolean;
|
|
80
|
+
}
|
|
81
|
+
interface WcsWakeLockCoreCommands {
|
|
82
|
+
/**
|
|
83
|
+
* Mark the lock as desired and acquire it (if the page is visible and the API
|
|
84
|
+
* is supported). Never rejects — a failure surfaces via the `error` property.
|
|
85
|
+
*/
|
|
86
|
+
request(): Promise<void>;
|
|
87
|
+
/** Mark the lock as no longer desired and release any held sentinel. */
|
|
88
|
+
release(): void;
|
|
89
|
+
}
|
|
90
|
+
interface WcsWakeLockCommands {
|
|
91
|
+
request(): Promise<void>;
|
|
92
|
+
release(): void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
declare function bootstrapWakeLock(userConfig?: IWritableConfig): void;
|
|
96
|
+
|
|
97
|
+
declare function getConfig(): IConfig;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Headless screen-wake-lock primitive — a thin, framework-agnostic wrapper around
|
|
101
|
+
* the Screen Wake Lock API exposed through the wc-bindable protocol.
|
|
102
|
+
*
|
|
103
|
+
* Unlike the other @wcstack sensors (geolocation / intersection), the wake lock is
|
|
104
|
+
* a pure *sink*: nothing is read from the device. A bound state drives the desired
|
|
105
|
+
* intent (`request()` / `release()`), and the only observable outputs are `held`
|
|
106
|
+
* (whether a sentinel is actually held) and `error`.
|
|
107
|
+
*
|
|
108
|
+
* The OS releases the lock whenever the page stops being visible (tab hidden,
|
|
109
|
+
* window minimized). To honor the declarative intent ("keep awake *while* active"),
|
|
110
|
+
* the Core keeps the desired flag (`_active`) and re-acquires the lock on the next
|
|
111
|
+
* `visibilitychange` back to visible. So `_active` (desired) and `held` (actual)
|
|
112
|
+
* diverge across an auto-release — and only `held` is published, because desired
|
|
113
|
+
* does not change when the OS drops the lock.
|
|
114
|
+
*
|
|
115
|
+
* Never-throw: `request()` never rejects (a failure surfaces via `error`), and an
|
|
116
|
+
* unsupported environment is a silent no-op (`held` stays false), consistent with
|
|
117
|
+
* the other @wcstack sensors.
|
|
118
|
+
*/
|
|
119
|
+
declare class WakeLockCore extends EventTarget {
|
|
120
|
+
static wcBindable: IWcBindable;
|
|
121
|
+
private _target;
|
|
122
|
+
private _type;
|
|
123
|
+
private _active;
|
|
124
|
+
private _held;
|
|
125
|
+
private _error;
|
|
126
|
+
private _sentinel;
|
|
127
|
+
private _gen;
|
|
128
|
+
private _acquiring;
|
|
129
|
+
private _visibilityBound;
|
|
130
|
+
constructor(target?: EventTarget, type?: WakeLockKind);
|
|
131
|
+
get held(): boolean;
|
|
132
|
+
get error(): Error | null;
|
|
133
|
+
/** The desired intent. Read-only reflection; not a wc-bindable property (it does
|
|
134
|
+
* not change on an OS auto-release, so there is nothing to observe). */
|
|
135
|
+
get active(): boolean;
|
|
136
|
+
get type(): WakeLockKind;
|
|
137
|
+
set type(value: WakeLockKind);
|
|
138
|
+
private _setHeld;
|
|
139
|
+
private _setError;
|
|
140
|
+
private _sameError;
|
|
141
|
+
/**
|
|
142
|
+
* Mark the lock as desired and acquire it. Idempotent while already held. If the
|
|
143
|
+
* API is unavailable or the page is currently hidden, the desired flag is still
|
|
144
|
+
* set (so the lock is acquired on the next return to visibility) but nothing is
|
|
145
|
+
* acquired now. Never rejects — a request failure surfaces via `error`.
|
|
146
|
+
*/
|
|
147
|
+
request(): Promise<void>;
|
|
148
|
+
/** Mark the lock as no longer desired and release any held sentinel. */
|
|
149
|
+
release(): void;
|
|
150
|
+
/**
|
|
151
|
+
* Full teardown: remove the visibility listener and release any held sentinel.
|
|
152
|
+
* Call from the Shell's `disconnectedCallback`.
|
|
153
|
+
*
|
|
154
|
+
* Semantics: this is a terminal teardown, not a pause. After `dispose()` the Core
|
|
155
|
+
* is meant to be discarded — there is no re-arm step, and the visibility listener
|
|
156
|
+
* is gone, so an OS auto-release will no longer be followed by a re-acquire. A
|
|
157
|
+
* later `request()` would still work in isolation (it re-attaches the listener via
|
|
158
|
+
* `_ensureVisibilityListener`), but reusing a disposed Core is not an intended path;
|
|
159
|
+
* the Shell always constructs a fresh Core per element instead.
|
|
160
|
+
*/
|
|
161
|
+
dispose(): void;
|
|
162
|
+
private _acquire;
|
|
163
|
+
/**
|
|
164
|
+
* Re-attempt an acquire after an in-flight one was *superseded* (its generation no
|
|
165
|
+
* longer matches), but only if the lock is still desired, not already held, and the
|
|
166
|
+
* page is visible. This recovers a request() that was coalesced away by the
|
|
167
|
+
* in-flight `_acquiring` guard: during a release()→request() overlap, the second
|
|
168
|
+
* request() bumps `_gen` and is a no-op at the guard, so without this retry its
|
|
169
|
+
* still-live intent would be lost until the next visibilitychange or manual call.
|
|
170
|
+
*
|
|
171
|
+
* Bounded — cannot loop forever: a retry runs ONLY on supersession, and a
|
|
172
|
+
* supersession requires an external release()/request() to bump `_gen` mid-flight.
|
|
173
|
+
* A retry's own `_acquire()`, if it is itself not superseded, terminates by either
|
|
174
|
+
* acquiring (held=true) or recording the live failure (held=false, error set) —
|
|
175
|
+
* neither path retries. So a denied environment that keeps rejecting does not
|
|
176
|
+
* recurse; the retry chain length is bounded by the number of external overlaps.
|
|
177
|
+
*/
|
|
178
|
+
private _retryIfStillDesired;
|
|
179
|
+
private _onRelease;
|
|
180
|
+
/**
|
|
181
|
+
* Lease renewal after an OS release while the page is still visible. Honors the
|
|
182
|
+
* "keep awake *while* active" promise for releases that do NOT coincide with a
|
|
183
|
+
* visibility change (battery-low / power-saver), which otherwise leave the lock
|
|
184
|
+
* stuck at desired=true / held=false until the next hide→show cycle.
|
|
185
|
+
*
|
|
186
|
+
* Bounded on failure: this only runs from `_onRelease`, which only fires when a
|
|
187
|
+
* sentinel was genuinely acquired and then released. A re-acquire that FAILS takes
|
|
188
|
+
* `_acquire()`'s live-failure path (error recorded, held=false) and attaches no
|
|
189
|
+
* listener, so it cannot re-enter `_onRelease` — a denied environment records the
|
|
190
|
+
* error once and stops. This is the dominant real path: per the Wake Lock spec a
|
|
191
|
+
* re-request under battery-low / power-saver is rejected (`NotAllowedError`), so the
|
|
192
|
+
* renewal terminates there.
|
|
193
|
+
*
|
|
194
|
+
* The one path NOT bounded by a counter is a pathological host that keeps GRANTING
|
|
195
|
+
* the re-request and then immediately auto-releasing it (grant→release reflux). Each
|
|
196
|
+
* iteration yields to the event loop and consumes a real OS grant, so it is not a
|
|
197
|
+
* tight/synchronous loop, but it would churn request() calls. We deliberately do NOT
|
|
198
|
+
* add a debounce or renewal cap: that reflux is not documented browser behavior
|
|
199
|
+
* (real browsers reject, not grant-then-revoke), and the extra timing state would
|
|
200
|
+
* complicate the pure-sink design to defend a case that does not occur in practice.
|
|
201
|
+
*
|
|
202
|
+
* The `_isVisible()` / `!_acquiring` guards (doubled by `_acquire()`'s own in-flight
|
|
203
|
+
* and held guards) prevent re-entry during an in-flight acquire and while hidden.
|
|
204
|
+
*/
|
|
205
|
+
private _reacquireAfterRelease;
|
|
206
|
+
private _onVisibilityChange;
|
|
207
|
+
private _ensureVisibilityListener;
|
|
208
|
+
private _wakeLock;
|
|
209
|
+
private _isVisible;
|
|
210
|
+
private _normalizeError;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* `<wcs-wakelock>` — declarative Screen Wake Lock.
|
|
215
|
+
*
|
|
216
|
+
* The first @wcstack tag that is a pure *sink*: every other sensor is an
|
|
217
|
+
* element→state producer, but the wake lock is state→element. The headline
|
|
218
|
+
* binding is `active@isPlaying` — hold the screen awake while a bound boolean is
|
|
219
|
+
* true. `active` is the single input knob (a mirrored attribute); `held` and
|
|
220
|
+
* `error` are the observable outputs.
|
|
221
|
+
*
|
|
222
|
+
* The OS auto-releases the lock when the page is hidden; the Core re-acquires it
|
|
223
|
+
* on the next return to visibility while `active` is still set, so the binding
|
|
224
|
+
* means "keep awake *while* active", not just "acquire once".
|
|
225
|
+
*/
|
|
226
|
+
declare class WcsWakeLock extends HTMLElement {
|
|
227
|
+
static hasConnectedCallbackPromise: boolean;
|
|
228
|
+
static observedAttributes: string[];
|
|
229
|
+
static wcBindable: IWcBindable;
|
|
230
|
+
private _core;
|
|
231
|
+
constructor();
|
|
232
|
+
get active(): boolean;
|
|
233
|
+
set active(value: boolean);
|
|
234
|
+
get type(): WakeLockKind;
|
|
235
|
+
set type(value: WakeLockKind);
|
|
236
|
+
get manual(): boolean;
|
|
237
|
+
set manual(value: boolean);
|
|
238
|
+
get held(): boolean;
|
|
239
|
+
get error(): Error | null;
|
|
240
|
+
/** Acquire (and keep) the wake lock. Never rejects — see the `error` property. */
|
|
241
|
+
request(): Promise<void>;
|
|
242
|
+
/** Release the wake lock and stop re-acquiring it. */
|
|
243
|
+
release(): void;
|
|
244
|
+
connectedCallback(): void;
|
|
245
|
+
disconnectedCallback(): void;
|
|
246
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export { WakeLockCore, WcsWakeLock, bootstrapWakeLock, getConfig };
|
|
250
|
+
export type { IWritableConfig, IWritableTagNames, WakeLockKind, WcsWakeLockCommands, WcsWakeLockCoreCommands, WcsWakeLockCoreValues, WcsWakeLockInputs, WcsWakeLockValues };
|