@wcstack/permission 1.13.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 +143 -0
- package/README.md +143 -0
- package/dist/auto.js +3 -0
- package/dist/auto.min.js +1 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.esm.js +347 -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,143 @@
|
|
|
1
|
+
# @wcstack/permission
|
|
2
|
+
|
|
3
|
+
`@wcstack/permission` は wcstack エコシステム向けのヘッドレスなパーミッション状態コンポーネントです。
|
|
4
|
+
|
|
5
|
+
視覚的な UI ウィジェットではありません。
|
|
6
|
+
`@wcstack/geolocation` が端末の位置をリアクティブな state に変えるのと同じように、ブラウザのパーミッション許可状態をリアクティブな state に変える **非同期プリミティブノード** です。
|
|
7
|
+
|
|
8
|
+
`@wcstack/state` と組み合わせると、`<wcs-permission>` はパス契約で直接バインドできます:
|
|
9
|
+
|
|
10
|
+
- **入力サーフェス**: `name`、`user-visible-only`、`sysex`
|
|
11
|
+
- **出力 state サーフェス**: `state`、`granted`、`denied`、`prompt`、`unsupported`
|
|
12
|
+
|
|
13
|
+
これにより、パーミッションに応じた UI(バナー・ゲート・機能ヒント)を、UI 層で `navigator.permissions.query()` や `change` リスナーの配線を書かずに、HTML 上で宣言的に表現できます。
|
|
14
|
+
|
|
15
|
+
`@wcstack/permission` は [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md)(Core / Shell / Binding Contract)アーキテクチャに従います:
|
|
16
|
+
|
|
17
|
+
- **Core**(`PermissionCore`)が query・4 値 state・live `change` 追従を担当
|
|
18
|
+
- **Shell**(`<wcs-permission>`)がその state を DOM 属性とライフサイクルに接続
|
|
19
|
+
- **Binding Contract**(`static wcBindable`)が観測可能な `properties` を宣言(そして意図的に **コマンドを持たない**)
|
|
20
|
+
|
|
21
|
+
## なぜ存在するか — read-only でコマンドの無いノード
|
|
22
|
+
|
|
23
|
+
他の wcstack IO ノード(`<wcs-geo>`、`<wcs-ws>`、`<wcs-clipboard>` …)はいずれも「何かを実行し」つつ state を報告します。Permissions API はそれらと違い **read-only** です。`query()` はあっても標準の `request()` がありません。この API から許可を求めることはできず、許可要求は機能そのものの呼び出し(`getCurrentPosition()`、`Notification.requestPermission()` …)の副作用として起こります。
|
|
24
|
+
|
|
25
|
+
したがって `<wcs-permission>` は純粋な **要素 → state** プロデューサです。*監視する* だけで、*求めない*。**コマンドを一切持たない初の wcstack ノード** であり、command-token は適用されず event-token のみが成立します。許可を取りに行くのは機能ノード(`<wcs-geo>` など)の責務で、本ノードは現在の許可状態を live なバインド可能 state として反映するだけです。
|
|
26
|
+
|
|
27
|
+
パーミッションの変化は `change` リスナーの購読ではなく **状態遷移** になります。
|
|
28
|
+
|
|
29
|
+
> **secure context 必須。** Permissions API は secure context(HTTPS、または `localhost`)でのみ動作します。API が存在しない場合や、要求した権限名をブラウザが拒否する場合(対応はブラウザ差が大きい: Firefox は `clipboard-read` 非対応、Safari は複数の名前を欠く)、`<wcs-permission>` は例外を投げず `state = "unsupported"` を報告します。
|
|
30
|
+
|
|
31
|
+
## インストール
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @wcstack/permission
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## クイックスタート
|
|
38
|
+
|
|
39
|
+
### 1. 許可状態を監視して UI をゲートする
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
43
|
+
<script type="module" src="https://esm.run/@wcstack/permission/auto"></script>
|
|
44
|
+
|
|
45
|
+
<wcs-state>
|
|
46
|
+
<script type="module">
|
|
47
|
+
export default { granted: false };
|
|
48
|
+
</script>
|
|
49
|
+
</wcs-state>
|
|
50
|
+
|
|
51
|
+
<wcs-permission name="geolocation" data-wcs="granted: granted"></wcs-permission>
|
|
52
|
+
|
|
53
|
+
<!-- 監視結果のブール 1 つ: 許可されるまで表示。 -->
|
|
54
|
+
<div data-wcs="hidden: granted">続行するには位置情報を許可してください。</div>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. 4 値の state
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<wcs-permission name="camera"
|
|
61
|
+
data-wcs="state: camState"></wcs-permission>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`state` は `"prompt"` / `"granted"` / `"denied"` / `"unsupported"` で、ユーザーがブラウザ設定で許可を変えると live に更新されます。
|
|
65
|
+
|
|
66
|
+
### 3. 追加メンバーを取る descriptor
|
|
67
|
+
|
|
68
|
+
一部の権限は名前だけでは足りません。対応するブール属性を使います:
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<!-- push: query({ name: "push", userVisibleOnly: true }) -->
|
|
72
|
+
<wcs-permission name="push" user-visible-only data-wcs="state: pushPerm"></wcs-permission>
|
|
73
|
+
|
|
74
|
+
<!-- midi: query({ name: "midi", sysex: true }) -->
|
|
75
|
+
<wcs-permission name="midi" sysex data-wcs="state: midiPerm"></wcs-permission>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. 監視役と取得役を並置する
|
|
79
|
+
|
|
80
|
+
`<wcs-permission>` が監視し、`<wcs-geo>` が要求する。ボタンが駆動するのは機能ノードで、permission ノードではありません。
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<wcs-permission name="geolocation" data-wcs="granted: granted; denied: denied"></wcs-permission>
|
|
84
|
+
<wcs-geo manual data-wcs="command.getCurrentPosition: $command.locate; latitude: lat"></wcs-geo>
|
|
85
|
+
|
|
86
|
+
<button data-wcs="onclick: locate; disabled: denied">現在地を取得</button>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
完全なデモは `examples/state-permission-banner` を参照。
|
|
90
|
+
|
|
91
|
+
## 属性 / 入力
|
|
92
|
+
|
|
93
|
+
| 属性 | 型 | 既定値 | 説明 |
|
|
94
|
+
| ------------------- | ------- | ------ | -------------------------------------------------------------------- |
|
|
95
|
+
| `name` | string | `""` | query する権限名(例: `geolocation`、`notifications`、`camera`)。必須 — 空の `name` は query せず `state = "unsupported"` に倒れます。 |
|
|
96
|
+
| `user-visible-only` | boolean | `false`| descriptor に `userVisibleOnly: true` を追加(`push` 権限用)。 |
|
|
97
|
+
| `sysex` | boolean | `false`| descriptor に `sysex: true` を追加(`midi` 権限用)。 |
|
|
98
|
+
|
|
99
|
+
## 観測可能プロパティ(出力)
|
|
100
|
+
|
|
101
|
+
| プロパティ | イベント | 説明 |
|
|
102
|
+
| ------------- | ---------------------- | ------------------------------------------------------------------- |
|
|
103
|
+
| `state` | `wcs-permission:change`| `"prompt"` / `"granted"` / `"denied"` / `"unsupported"`、live 追従。 |
|
|
104
|
+
| `granted` | `wcs-permission:change`| `state === "granted"` のとき `true`。`hidden@granted` 等に便利。 |
|
|
105
|
+
| `denied` | `wcs-permission:change`| `state === "denied"` のとき `true`。 |
|
|
106
|
+
| `prompt` | `wcs-permission:change`| `state === "prompt"` のとき `true`。 |
|
|
107
|
+
| `unsupported` | `wcs-permission:change`| この環境で query できないとき `true`。 |
|
|
108
|
+
|
|
109
|
+
5 つすべては単一の `wcs-permission:change` イベントから派生します(ブール群は `state` と連動)。
|
|
110
|
+
|
|
111
|
+
## コマンド
|
|
112
|
+
|
|
113
|
+
**無し。** Permissions API は read-only で、呼ぶべき `request()` がありません。許可の取得は機能ノードの責務(例: `<wcs-geo>` の `getCurrentPosition`)です。`<wcs-permission>` は純粋なモニタです。
|
|
114
|
+
|
|
115
|
+
## 注意・制限
|
|
116
|
+
|
|
117
|
+
- **属性は接続時に読み取り、監視はしない。** `<wcs-permission>` は `observedAttributes` / `attributeChangedCallback` を実装しません。descriptor(`name` +追加メンバー)は接続時に固定され、接続後に `name` を命令的に変えても再 query しません。別の権限を監視するには別要素を使う(または再接続する)。
|
|
118
|
+
- **再接続で再 query。** 要素を取り外して再挿入すると `connectedCallback` が再実行され、query を再発行して `change` を再購読します(切断時に購読を解除するのと対称)。切断時にまだ解決していない in-flight な query は無効化され、その後に解決しても `state` を更新せず `change` リスナーも張りません。したがって素早い 切断→再接続 でも古い購読が残ることはありません。
|
|
119
|
+
- **SSR(`@wcstack/server`)。** `static hasConnectedCallbackPromise = true` を宣言し `connectedCallbackPromise` を公開するため、サーバレンダラは接続時 query の settle を待ってからスナップショットします。
|
|
120
|
+
- **サイレント失敗処理(zero-log)。** wcstack のゼロ依存方針に沿い、`<wcs-permission>` はログも例外も出しません。Permissions API が無い場合、権限名が拒否される場合、`name` 属性が未指定/空の場合はいずれも静かに `state = "unsupported"` に解決します。`unsupported`(または `state`)をバインドして反応してください。
|
|
121
|
+
|
|
122
|
+
## ヘッドレス利用(`PermissionCore`)
|
|
123
|
+
|
|
124
|
+
Core は DOM 非依存で、`@wc-bindable/core` の `bind()` と直接使えます:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { PermissionCore } from "@wcstack/permission";
|
|
128
|
+
|
|
129
|
+
const perm = new PermissionCore({ name: "geolocation" });
|
|
130
|
+
perm.addEventListener("wcs-permission:change", (e) => {
|
|
131
|
+
console.log((e as CustomEvent).detail); // "prompt" | "granted" | "denied" | "unsupported"
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await perm.ready; // 初回 query が settle 済み
|
|
135
|
+
console.log(perm.granted);
|
|
136
|
+
|
|
137
|
+
// 後始末:
|
|
138
|
+
perm.dispose(); // live な `change` リスナーを外す
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## ライセンス
|
|
142
|
+
|
|
143
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @wcstack/permission
|
|
2
|
+
|
|
3
|
+
`@wcstack/permission` is a headless permission-state component for the wcstack ecosystem.
|
|
4
|
+
|
|
5
|
+
It is not a visual UI widget.
|
|
6
|
+
It is an **async primitive node** that turns a browser permission grant into reactive state — the same way `@wcstack/geolocation` turns the device's location into reactive state.
|
|
7
|
+
|
|
8
|
+
With `@wcstack/state`, `<wcs-permission>` can be bound directly through path contracts:
|
|
9
|
+
|
|
10
|
+
- **input surface**: `name`, `user-visible-only`, `sysex`
|
|
11
|
+
- **output state surface**: `state`, `granted`, `denied`, `prompt`, `unsupported`
|
|
12
|
+
|
|
13
|
+
This means permission-aware UI — banners, gates, capability hints — can be expressed declaratively in HTML, without writing `navigator.permissions.query()` or `change`-listener glue in your UI layer.
|
|
14
|
+
|
|
15
|
+
`@wcstack/permission` follows the [CSBC](https://github.com/csbc-dev/arch/blob/main/README.md) (Core / Shell / Binding Contract) architecture:
|
|
16
|
+
|
|
17
|
+
- **Core** (`PermissionCore`) handles the query, the four-value state, and live `change` tracking
|
|
18
|
+
- **Shell** (`<wcs-permission>`) connects that state to DOM attributes and lifecycle
|
|
19
|
+
- **Binding Contract** (`static wcBindable`) declares observable `properties` (and, deliberately, **no commands**)
|
|
20
|
+
|
|
21
|
+
## Why this exists — a read-only, command-less node
|
|
22
|
+
|
|
23
|
+
Every other wcstack IO node (`<wcs-geo>`, `<wcs-ws>`, `<wcs-clipboard>`, …) both *does* something and reports state. The Permissions API is different: it is **read-only**. It has `query()` but no standard `request()`. You cannot ask for a grant through it — asking happens as a side effect of calling the feature itself (`getCurrentPosition()`, `Notification.requestPermission()`, …).
|
|
24
|
+
|
|
25
|
+
So `<wcs-permission>` is a pure **element → state** producer: it *watches*, it never *asks*. It is the first wcstack node with **no commands at all** — command-token does not apply, only event-token. Acquiring a grant is the job of the feature node (`<wcs-geo>` etc.); this node just reflects the current grant as bindable state, live.
|
|
26
|
+
|
|
27
|
+
A permission change becomes a **state transition**, not a `change`-listener subscription.
|
|
28
|
+
|
|
29
|
+
> **Secure context required.** The Permissions API only works in a secure context (HTTPS, or `localhost`). Where it is absent — or the browser rejects the requested permission name (support varies widely: Firefox has no `clipboard-read`, Safari omits several names) — `<wcs-permission>` reports `state = "unsupported"` instead of throwing.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @wcstack/permission
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### 1. Watch a grant and gate the UI
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<script type="module" src="https://esm.run/@wcstack/state/auto"></script>
|
|
43
|
+
<script type="module" src="https://esm.run/@wcstack/permission/auto"></script>
|
|
44
|
+
|
|
45
|
+
<wcs-state>
|
|
46
|
+
<script type="module">
|
|
47
|
+
export default { granted: false };
|
|
48
|
+
</script>
|
|
49
|
+
</wcs-state>
|
|
50
|
+
|
|
51
|
+
<wcs-permission name="geolocation" data-wcs="granted: granted"></wcs-permission>
|
|
52
|
+
|
|
53
|
+
<!-- One boolean, straight from the watcher: shown until granted. -->
|
|
54
|
+
<div data-wcs="hidden: granted">Please allow location to continue.</div>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 2. The four-value state
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<wcs-permission name="camera"
|
|
61
|
+
data-wcs="state: camState"></wcs-permission>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`state` is `"prompt"` / `"granted"` / `"denied"` / `"unsupported"`, and updates live when the user changes the grant in browser settings.
|
|
65
|
+
|
|
66
|
+
### 3. Descriptors that take extra members
|
|
67
|
+
|
|
68
|
+
Some permissions need more than a name. Use the matching boolean attribute:
|
|
69
|
+
|
|
70
|
+
```html
|
|
71
|
+
<!-- push: query({ name: "push", userVisibleOnly: true }) -->
|
|
72
|
+
<wcs-permission name="push" user-visible-only data-wcs="state: pushPerm"></wcs-permission>
|
|
73
|
+
|
|
74
|
+
<!-- midi: query({ name: "midi", sysex: true }) -->
|
|
75
|
+
<wcs-permission name="midi" sysex data-wcs="state: midiPerm"></wcs-permission>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. Watcher + acquirer, side by side
|
|
79
|
+
|
|
80
|
+
`<wcs-permission>` watches; `<wcs-geo>` asks. The button drives the feature node, not the permission node.
|
|
81
|
+
|
|
82
|
+
```html
|
|
83
|
+
<wcs-permission name="geolocation" data-wcs="granted: granted; denied: denied"></wcs-permission>
|
|
84
|
+
<wcs-geo manual data-wcs="command.getCurrentPosition: $command.locate; latitude: lat"></wcs-geo>
|
|
85
|
+
|
|
86
|
+
<button data-wcs="onclick: locate; disabled: denied">Locate me</button>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
See `examples/state-permission-banner` for the full demo.
|
|
90
|
+
|
|
91
|
+
## Attributes / Inputs
|
|
92
|
+
|
|
93
|
+
| Attribute | Type | Default | Description |
|
|
94
|
+
| ------------------- | ------- | ------- | --------------------------------------------------------------------------- |
|
|
95
|
+
| `name` | string | `""` | The permission name to query (e.g. `geolocation`, `notifications`, `camera`). Required — an empty `name` short-circuits to `state = "unsupported"` without querying. |
|
|
96
|
+
| `user-visible-only` | boolean | `false` | Adds `userVisibleOnly: true` to the descriptor (for the `push` permission). |
|
|
97
|
+
| `sysex` | boolean | `false` | Adds `sysex: true` to the descriptor (for the `midi` permission). |
|
|
98
|
+
|
|
99
|
+
## Observable Properties (outputs)
|
|
100
|
+
|
|
101
|
+
| Property | Event | Description |
|
|
102
|
+
| ------------- | ---------------------- | ----------------------------------------------------------------------- |
|
|
103
|
+
| `state` | `wcs-permission:change`| `"prompt"` / `"granted"` / `"denied"` / `"unsupported"`, tracked live. |
|
|
104
|
+
| `granted` | `wcs-permission:change`| `true` when `state === "granted"`. Convenience for `hidden@granted` etc. |
|
|
105
|
+
| `denied` | `wcs-permission:change`| `true` when `state === "denied"`. |
|
|
106
|
+
| `prompt` | `wcs-permission:change`| `true` when `state === "prompt"`. |
|
|
107
|
+
| `unsupported` | `wcs-permission:change`| `true` when the permission cannot be queried in this environment. |
|
|
108
|
+
|
|
109
|
+
All five derive from the single `wcs-permission:change` event (the booleans change in lockstep with `state`).
|
|
110
|
+
|
|
111
|
+
## Commands
|
|
112
|
+
|
|
113
|
+
**None.** The Permissions API is read-only — there is no `request()` to call. Acquiring a grant is the feature node's responsibility (e.g. `<wcs-geo>`'s `getCurrentPosition`). `<wcs-permission>` is a pure monitor.
|
|
114
|
+
|
|
115
|
+
## Notes & limitations
|
|
116
|
+
|
|
117
|
+
- **Attributes are read at connect time, not observed.** `<wcs-permission>` does not implement `observedAttributes` / `attributeChangedCallback`. The descriptor (`name` + extras) is fixed when the element connects; changing `name` imperatively after connect does not re-query. To watch a different permission, use a separate element (or re-connect).
|
|
118
|
+
- **Reconnect re-queries.** Removing and re-inserting the element runs `connectedCallback` again, re-issuing the query and re-subscribing to `change` (matching how it tears the subscription down on disconnect). A query still in flight when the element disconnects is invalidated: if it resolves afterwards it neither updates `state` nor attaches a `change` listener, so a rapid disconnect→reconnect cannot leak a stale subscription.
|
|
119
|
+
- **SSR (`@wcstack/server`).** Declares `static hasConnectedCallbackPromise = true` and exposes `connectedCallbackPromise`, so the server renderer waits for the connect-time query to settle before snapshotting.
|
|
120
|
+
- **Silent failure handling (zero-log).** Consistent with the rest of wcstack's zero-dependency philosophy, `<wcs-permission>` never logs or throws. A missing Permissions API, a browser that rejects the requested permission name, or a missing/empty `name` attribute all silently resolve to `state = "unsupported"`. Bind `unsupported` (or `state`) to react.
|
|
121
|
+
|
|
122
|
+
## Headless usage (`PermissionCore`)
|
|
123
|
+
|
|
124
|
+
The Core has no DOM dependency and can be used directly with `bind()` from `@wc-bindable/core`:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { PermissionCore } from "@wcstack/permission";
|
|
128
|
+
|
|
129
|
+
const perm = new PermissionCore({ name: "geolocation" });
|
|
130
|
+
perm.addEventListener("wcs-permission:change", (e) => {
|
|
131
|
+
console.log((e as CustomEvent).detail); // "prompt" | "granted" | "denied" | "unsupported"
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await perm.ready; // first query has settled
|
|
135
|
+
console.log(perm.granted);
|
|
136
|
+
|
|
137
|
+
// later, when done:
|
|
138
|
+
perm.dispose(); // detach the live `change` listener
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT
|
package/dist/auto.js
ADDED
package/dist/auto.min.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bootstrapPermission}from"./index.esm.min.js";bootstrapPermission();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
interface ITagNames {
|
|
2
|
+
readonly permission: string;
|
|
3
|
+
}
|
|
4
|
+
interface IWritableTagNames {
|
|
5
|
+
permission?: 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
|
+
* Permission state mirroring the Permissions API `PermissionState`
|
|
35
|
+
* (`"prompt"` / `"granted"` / `"denied"`) plus `"unsupported"` for environments
|
|
36
|
+
* without `navigator.permissions`, or where the requested permission name cannot
|
|
37
|
+
* be queried (the browser rejects the descriptor). This is the same four-value
|
|
38
|
+
* surface used by `@wcstack/geolocation` and `@wcstack/clipboard`.
|
|
39
|
+
*/
|
|
40
|
+
type PermissionStateOrUnsupported = "prompt" | "granted" | "denied" | "unsupported";
|
|
41
|
+
/**
|
|
42
|
+
* Descriptor passed to `navigator.permissions.query()`. `name` is the permission
|
|
43
|
+
* name (e.g. `"geolocation"`, `"notifications"`, `"camera"`). The optional fields
|
|
44
|
+
* cover the descriptors that take extra members:
|
|
45
|
+
* - `userVisibleOnly` — required by the `"push"` permission.
|
|
46
|
+
* - `sysex` — used by the `"midi"` permission.
|
|
47
|
+
*
|
|
48
|
+
* Other members defined by future descriptors are allowed via the index
|
|
49
|
+
* signature so the Shell can forward unknown attributes without a type change.
|
|
50
|
+
*/
|
|
51
|
+
interface WcsPermissionDescriptor {
|
|
52
|
+
name: string;
|
|
53
|
+
userVisibleOnly?: boolean;
|
|
54
|
+
sysex?: boolean;
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Value types for PermissionCore (headless) — the observable state properties.
|
|
59
|
+
* Use with `bind()` from `@wc-bindable/core` for compile-time type checking.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const core = new PermissionCore({ name: "geolocation" });
|
|
64
|
+
* bind(core, (name: keyof WcsPermissionCoreValues, value) => { ... });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
interface WcsPermissionCoreValues {
|
|
68
|
+
state: PermissionStateOrUnsupported;
|
|
69
|
+
granted: boolean;
|
|
70
|
+
denied: boolean;
|
|
71
|
+
prompt: boolean;
|
|
72
|
+
unsupported: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Value types for the Shell (`<wcs-permission>`) — identical observable surface
|
|
76
|
+
* to the Core. The Shell adds no command-property: the Permissions API is
|
|
77
|
+
* read-only, so this element is a pure element → state monitor.
|
|
78
|
+
*/
|
|
79
|
+
type WcsPermissionValues = WcsPermissionCoreValues;
|
|
80
|
+
/**
|
|
81
|
+
* Settable input surface for the Shell (`<wcs-permission>`) — the descriptor
|
|
82
|
+
* members exposed as attributes (`name`, `user-visible-only`, `sysex`). Mirrors
|
|
83
|
+
* the `inputs` entries of the wc-bindable manifest; use it for compile-time typing
|
|
84
|
+
* when a binding system or tooling writes these declaratively.
|
|
85
|
+
*/
|
|
86
|
+
interface WcsPermissionInputs {
|
|
87
|
+
name: string;
|
|
88
|
+
userVisibleOnly: boolean;
|
|
89
|
+
sysex: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
declare function bootstrapPermission(userConfig?: IWritableConfig): void;
|
|
93
|
+
|
|
94
|
+
declare function getConfig(): IConfig;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Headless permission-state primitive. A thin, framework-agnostic wrapper around
|
|
98
|
+
* the Permissions API exposed through the wc-bindable protocol.
|
|
99
|
+
*
|
|
100
|
+
* Unlike the other @wcstack IO nodes (geolocation / clipboard / sse / …), the
|
|
101
|
+
* Permissions API is **read-only**: it has `query()` but no standard `request()`.
|
|
102
|
+
* Asking the user for a grant is the job of the feature node (`<wcs-geo>` etc.);
|
|
103
|
+
* this node only *observes*. It is therefore a pure element → state monitor with
|
|
104
|
+
* **no commands** — command-token does not apply, only event-token.
|
|
105
|
+
*
|
|
106
|
+
* The single observable is `state` (`navigator.permissions.query(descriptor)`'s
|
|
107
|
+
* `PermissionState`, or `"unsupported"`), published via the `wcs-permission:change`
|
|
108
|
+
* event. `granted` / `denied` / `prompt` / `unsupported` are convenience booleans
|
|
109
|
+
* derived from that one event (mirroring how GeolocationCore exposes latitude/…
|
|
110
|
+
* from one `wcs-geo:position` event), so a binding like `hidden@granted` works
|
|
111
|
+
* directly. The live `change` event of the PermissionStatus is tracked so a grant
|
|
112
|
+
* flipping in browser settings flows into the declarative state.
|
|
113
|
+
*/
|
|
114
|
+
declare class PermissionCore extends EventTarget {
|
|
115
|
+
static wcBindable: IWcBindable;
|
|
116
|
+
private _target;
|
|
117
|
+
private _descriptor;
|
|
118
|
+
private _state;
|
|
119
|
+
private _permissionStatus;
|
|
120
|
+
private _permissionSubscribed;
|
|
121
|
+
private _permGen;
|
|
122
|
+
private _ready;
|
|
123
|
+
constructor(descriptor?: WcsPermissionDescriptor | null, target?: EventTarget);
|
|
124
|
+
get state(): PermissionStateOrUnsupported;
|
|
125
|
+
get granted(): boolean;
|
|
126
|
+
get denied(): boolean;
|
|
127
|
+
get prompt(): boolean;
|
|
128
|
+
get unsupported(): boolean;
|
|
129
|
+
/** Resolves once the current (or initial) query settles. */
|
|
130
|
+
get ready(): Promise<void>;
|
|
131
|
+
private _setState;
|
|
132
|
+
/**
|
|
133
|
+
* Start observing `descriptor` (e.g. `{ name: "geolocation" }`). Idempotent
|
|
134
|
+
* while already subscribed — calling it again only updates the stored descriptor
|
|
135
|
+
* for a *future* re-subscription; it does **not** re-query, even when called with
|
|
136
|
+
* a different descriptor (the Shell binds at a fixed connect-time descriptor and
|
|
137
|
+
* does not re-query on a `name` change in v1). To switch permission mid-life,
|
|
138
|
+
* dispose() first, then observe() the new descriptor. On the first call, or after
|
|
139
|
+
* a dispose(), it issues the query and subscribes to the live `change` event.
|
|
140
|
+
* Returns a promise that resolves once that query settles, for SSR.
|
|
141
|
+
*/
|
|
142
|
+
observe(descriptor: WcsPermissionDescriptor): Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* Detach the live permission `change` listener. Call from the Shell's
|
|
145
|
+
* `disconnectedCallback` so a removed element does not leak the subscription.
|
|
146
|
+
* A later reconnect can re-subscribe via observe().
|
|
147
|
+
*
|
|
148
|
+
* Headless callers (using PermissionCore directly, without the Shell) own this
|
|
149
|
+
* lifecycle themselves: call dispose() when the observer is no longer needed,
|
|
150
|
+
* otherwise the live PermissionStatus `change` listener keeps this instance
|
|
151
|
+
* reachable for as long as the status is alive. dispose() is safe to call when
|
|
152
|
+
* never subscribed and may be paired with a later observe() to resume.
|
|
153
|
+
*/
|
|
154
|
+
dispose(): void;
|
|
155
|
+
private _initPermission;
|
|
156
|
+
private _onPermissionChange;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
declare class WcsPermission extends HTMLElement {
|
|
160
|
+
static hasConnectedCallbackPromise: boolean;
|
|
161
|
+
static wcBindable: IWcBindable;
|
|
162
|
+
private _core;
|
|
163
|
+
private _connectedCallbackPromise;
|
|
164
|
+
constructor();
|
|
165
|
+
get name(): string;
|
|
166
|
+
set name(value: string);
|
|
167
|
+
get userVisibleOnly(): boolean;
|
|
168
|
+
set userVisibleOnly(value: boolean);
|
|
169
|
+
get sysex(): boolean;
|
|
170
|
+
set sysex(value: boolean);
|
|
171
|
+
get state(): PermissionStateOrUnsupported;
|
|
172
|
+
get granted(): boolean;
|
|
173
|
+
get denied(): boolean;
|
|
174
|
+
get prompt(): boolean;
|
|
175
|
+
get unsupported(): boolean;
|
|
176
|
+
get connectedCallbackPromise(): Promise<void>;
|
|
177
|
+
private _descriptor;
|
|
178
|
+
connectedCallback(): void;
|
|
179
|
+
disconnectedCallback(): void;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { PermissionCore, WcsPermission, bootstrapPermission, getConfig };
|
|
183
|
+
export type { IWritableConfig, IWritableTagNames, PermissionStateOrUnsupported, WcsPermissionCoreValues, WcsPermissionDescriptor, WcsPermissionInputs, WcsPermissionValues };
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
const _config = {
|
|
2
|
+
tagNames: {
|
|
3
|
+
permission: "wcs-permission",
|
|
4
|
+
},
|
|
5
|
+
};
|
|
6
|
+
function deepFreeze(obj) {
|
|
7
|
+
if (obj === null || typeof obj !== "object")
|
|
8
|
+
return obj;
|
|
9
|
+
Object.freeze(obj);
|
|
10
|
+
for (const key of Object.keys(obj)) {
|
|
11
|
+
deepFreeze(obj[key]);
|
|
12
|
+
}
|
|
13
|
+
return obj;
|
|
14
|
+
}
|
|
15
|
+
function deepClone(obj) {
|
|
16
|
+
if (obj === null || typeof obj !== "object")
|
|
17
|
+
return obj;
|
|
18
|
+
const clone = {};
|
|
19
|
+
for (const key of Object.keys(obj)) {
|
|
20
|
+
clone[key] = deepClone(obj[key]);
|
|
21
|
+
}
|
|
22
|
+
return clone;
|
|
23
|
+
}
|
|
24
|
+
let frozenConfig = null;
|
|
25
|
+
const config = _config;
|
|
26
|
+
function getConfig() {
|
|
27
|
+
if (!frozenConfig) {
|
|
28
|
+
frozenConfig = deepFreeze(deepClone(_config));
|
|
29
|
+
}
|
|
30
|
+
return frozenConfig;
|
|
31
|
+
}
|
|
32
|
+
function setConfig(partialConfig) {
|
|
33
|
+
if (partialConfig.tagNames) {
|
|
34
|
+
Object.assign(_config.tagNames, partialConfig.tagNames);
|
|
35
|
+
}
|
|
36
|
+
frozenConfig = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Headless permission-state primitive. A thin, framework-agnostic wrapper around
|
|
41
|
+
* the Permissions API exposed through the wc-bindable protocol.
|
|
42
|
+
*
|
|
43
|
+
* Unlike the other @wcstack IO nodes (geolocation / clipboard / sse / …), the
|
|
44
|
+
* Permissions API is **read-only**: it has `query()` but no standard `request()`.
|
|
45
|
+
* Asking the user for a grant is the job of the feature node (`<wcs-geo>` etc.);
|
|
46
|
+
* this node only *observes*. It is therefore a pure element → state monitor with
|
|
47
|
+
* **no commands** — command-token does not apply, only event-token.
|
|
48
|
+
*
|
|
49
|
+
* The single observable is `state` (`navigator.permissions.query(descriptor)`'s
|
|
50
|
+
* `PermissionState`, or `"unsupported"`), published via the `wcs-permission:change`
|
|
51
|
+
* event. `granted` / `denied` / `prompt` / `unsupported` are convenience booleans
|
|
52
|
+
* derived from that one event (mirroring how GeolocationCore exposes latitude/…
|
|
53
|
+
* from one `wcs-geo:position` event), so a binding like `hidden@granted` works
|
|
54
|
+
* directly. The live `change` event of the PermissionStatus is tracked so a grant
|
|
55
|
+
* flipping in browser settings flows into the declarative state.
|
|
56
|
+
*/
|
|
57
|
+
class PermissionCore extends EventTarget {
|
|
58
|
+
static wcBindable = {
|
|
59
|
+
protocol: "wc-bindable",
|
|
60
|
+
version: 1,
|
|
61
|
+
properties: [
|
|
62
|
+
{ name: "state", event: "wcs-permission:change" },
|
|
63
|
+
{ name: "granted", event: "wcs-permission:change", getter: (e) => e.detail === "granted" },
|
|
64
|
+
{ name: "denied", event: "wcs-permission:change", getter: (e) => e.detail === "denied" },
|
|
65
|
+
{ name: "prompt", event: "wcs-permission:change", getter: (e) => e.detail === "prompt" },
|
|
66
|
+
{ name: "unsupported", event: "wcs-permission:change", getter: (e) => e.detail === "unsupported" },
|
|
67
|
+
],
|
|
68
|
+
// No commands: the Permissions API is read-only (query-only). See class docs.
|
|
69
|
+
commands: [],
|
|
70
|
+
};
|
|
71
|
+
_target;
|
|
72
|
+
_descriptor = null;
|
|
73
|
+
_state = "prompt";
|
|
74
|
+
// Live PermissionStatus handle (when the Permissions API is available), kept so
|
|
75
|
+
// the `change` listener can be removed on dispose().
|
|
76
|
+
_permissionStatus = null;
|
|
77
|
+
// True once a permission subscription has been (or is being) established, and
|
|
78
|
+
// reset by dispose(). Guards observe() so a reconnect after dispose() re-queries
|
|
79
|
+
// while a redundant observe() on an already-live subscription does not.
|
|
80
|
+
_permissionSubscribed = false;
|
|
81
|
+
// Monotonic id of the current permission query. Bumped by every _initPermission()
|
|
82
|
+
// and by dispose(). Each in-flight query captures its id and, on resolve, bails
|
|
83
|
+
// unless it is still current — so a query superseded by a rapid (synchronous)
|
|
84
|
+
// disconnect→reconnect, or one that resolves after dispose(), never attaches a
|
|
85
|
+
// listener. A plain boolean cannot cover this: dispose()→observe() flips it
|
|
86
|
+
// false→true again, reopening the window for the stale query to slip through.
|
|
87
|
+
_permGen = 0;
|
|
88
|
+
// Resolves once the most recent query settles (or immediately when the API is
|
|
89
|
+
// unsupported). The Shell exposes this as connectedCallbackPromise so SSR can
|
|
90
|
+
// await the first probe before snapshotting the HTML.
|
|
91
|
+
_ready = Promise.resolve();
|
|
92
|
+
constructor(descriptor, target) {
|
|
93
|
+
super();
|
|
94
|
+
this._target = target ?? this;
|
|
95
|
+
// Headless ergonomics: when a descriptor is supplied up front, probe the
|
|
96
|
+
// permission state immediately so observers see the real value before the
|
|
97
|
+
// first read. The Shell passes nothing and drives the first query from
|
|
98
|
+
// connectedCallback via observe(), once the element's attributes resolve.
|
|
99
|
+
if (descriptor) {
|
|
100
|
+
this._descriptor = descriptor;
|
|
101
|
+
this._ready = this._initPermission();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
get state() {
|
|
105
|
+
return this._state;
|
|
106
|
+
}
|
|
107
|
+
get granted() {
|
|
108
|
+
return this._state === "granted";
|
|
109
|
+
}
|
|
110
|
+
get denied() {
|
|
111
|
+
return this._state === "denied";
|
|
112
|
+
}
|
|
113
|
+
get prompt() {
|
|
114
|
+
return this._state === "prompt";
|
|
115
|
+
}
|
|
116
|
+
get unsupported() {
|
|
117
|
+
return this._state === "unsupported";
|
|
118
|
+
}
|
|
119
|
+
/** Resolves once the current (or initial) query settles. */
|
|
120
|
+
get ready() {
|
|
121
|
+
return this._ready;
|
|
122
|
+
}
|
|
123
|
+
// --- State setter with event dispatch ---
|
|
124
|
+
_setState(state) {
|
|
125
|
+
// Same-value guard: `state` is the only stored value and the derived booleans
|
|
126
|
+
// change in lockstep with it, so suppressing identical re-dispatches is safe.
|
|
127
|
+
if (this._state === state)
|
|
128
|
+
return;
|
|
129
|
+
this._state = state;
|
|
130
|
+
this._target.dispatchEvent(new CustomEvent("wcs-permission:change", {
|
|
131
|
+
detail: state,
|
|
132
|
+
bubbles: true,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
// --- Public API ---
|
|
136
|
+
/**
|
|
137
|
+
* Start observing `descriptor` (e.g. `{ name: "geolocation" }`). Idempotent
|
|
138
|
+
* while already subscribed — calling it again only updates the stored descriptor
|
|
139
|
+
* for a *future* re-subscription; it does **not** re-query, even when called with
|
|
140
|
+
* a different descriptor (the Shell binds at a fixed connect-time descriptor and
|
|
141
|
+
* does not re-query on a `name` change in v1). To switch permission mid-life,
|
|
142
|
+
* dispose() first, then observe() the new descriptor. On the first call, or after
|
|
143
|
+
* a dispose(), it issues the query and subscribes to the live `change` event.
|
|
144
|
+
* Returns a promise that resolves once that query settles, for SSR.
|
|
145
|
+
*/
|
|
146
|
+
observe(descriptor) {
|
|
147
|
+
this._descriptor = descriptor;
|
|
148
|
+
if (!this._permissionSubscribed) {
|
|
149
|
+
this._ready = this._initPermission();
|
|
150
|
+
}
|
|
151
|
+
return this._ready;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Detach the live permission `change` listener. Call from the Shell's
|
|
155
|
+
* `disconnectedCallback` so a removed element does not leak the subscription.
|
|
156
|
+
* A later reconnect can re-subscribe via observe().
|
|
157
|
+
*
|
|
158
|
+
* Headless callers (using PermissionCore directly, without the Shell) own this
|
|
159
|
+
* lifecycle themselves: call dispose() when the observer is no longer needed,
|
|
160
|
+
* otherwise the live PermissionStatus `change` listener keeps this instance
|
|
161
|
+
* reachable for as long as the status is alive. dispose() is safe to call when
|
|
162
|
+
* never subscribed and may be paired with a later observe() to resume.
|
|
163
|
+
*/
|
|
164
|
+
dispose() {
|
|
165
|
+
this._permissionSubscribed = false;
|
|
166
|
+
// Invalidate any in-flight query so its .then() bails instead of attaching a
|
|
167
|
+
// listener after teardown.
|
|
168
|
+
this._permGen++;
|
|
169
|
+
if (this._permissionStatus) {
|
|
170
|
+
this._permissionStatus.removeEventListener("change", this._onPermissionChange);
|
|
171
|
+
this._permissionStatus = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// --- Internal ---
|
|
175
|
+
_initPermission() {
|
|
176
|
+
// Guard a missing/empty permission name (e.g. a `<wcs-permission>` with no
|
|
177
|
+
// `name` attribute). Such a descriptor would only ever reject at query() and
|
|
178
|
+
// silently fall back to "unsupported", which is hard to diagnose. Short-circuit
|
|
179
|
+
// to "unsupported" without issuing a doomed query so the misconfiguration
|
|
180
|
+
// surfaces deterministically and no listener is attached.
|
|
181
|
+
if (!this._descriptor || !this._descriptor.name) {
|
|
182
|
+
this._setState("unsupported");
|
|
183
|
+
return Promise.resolve();
|
|
184
|
+
}
|
|
185
|
+
// The Permissions API is optional. When absent (or it rejects, e.g. the
|
|
186
|
+
// browser does not accept the requested permission name), report "unsupported"
|
|
187
|
+
// and leave it at that — there is nothing to retry.
|
|
188
|
+
if (typeof navigator === "undefined" || !navigator.permissions || typeof navigator.permissions.query !== "function") {
|
|
189
|
+
// Route through _setState (not a bare assignment) so observers stay in sync
|
|
190
|
+
// with the public state. The same-value guard means no redundant dispatch
|
|
191
|
+
// when the state does not actually change.
|
|
192
|
+
this._setState("unsupported");
|
|
193
|
+
// Intentionally does NOT set _permissionSubscribed: there is no listener to
|
|
194
|
+
// tear down, so a reconnect simply re-probes (idempotent — the same-value
|
|
195
|
+
// guard suppresses any dispatch and no listener is ever attached). Mirrors
|
|
196
|
+
// GeolocationCore's reinitPermission behavior in unsupported environments.
|
|
197
|
+
return Promise.resolve();
|
|
198
|
+
}
|
|
199
|
+
this._permissionSubscribed = true;
|
|
200
|
+
const gen = ++this._permGen;
|
|
201
|
+
// Cast: WcsPermissionDescriptor widens `name` to string (and allows extra
|
|
202
|
+
// descriptor members like userVisibleOnly / sysex) where the lib DOM type
|
|
203
|
+
// expects the PermissionName union.
|
|
204
|
+
return navigator.permissions.query(this._descriptor).then((status) => {
|
|
205
|
+
// Stale resolution: this query was superseded (rapid reconnect) or the
|
|
206
|
+
// element was disposed while it was in flight. Drop it so only the current
|
|
207
|
+
// subscription attaches a listener.
|
|
208
|
+
if (gen !== this._permGen)
|
|
209
|
+
return;
|
|
210
|
+
this._permissionStatus = status;
|
|
211
|
+
this._setState(status.state);
|
|
212
|
+
status.addEventListener("change", this._onPermissionChange);
|
|
213
|
+
}, () => {
|
|
214
|
+
if (gen !== this._permGen)
|
|
215
|
+
return;
|
|
216
|
+
this._setState("unsupported");
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
_onPermissionChange = (event) => {
|
|
220
|
+
const status = event.target;
|
|
221
|
+
this._setState(status.state);
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Named WcsPermission (not `Permission`) so the class does not shadow any global,
|
|
226
|
+
// and to match the <wcs-geo> / <wcs-ws> convention (WcsGeolocation /
|
|
227
|
+
// WcsWebSocket). The public export keeps the `WcsPermission` name unchanged.
|
|
228
|
+
class WcsPermission extends HTMLElement {
|
|
229
|
+
static hasConnectedCallbackPromise = true;
|
|
230
|
+
static wcBindable = {
|
|
231
|
+
...PermissionCore.wcBindable,
|
|
232
|
+
// Shell-level settable surface. `name` is the permission name; the descriptor
|
|
233
|
+
// extras `user-visible-only` (push) and `sysex` (midi) are boolean flags that
|
|
234
|
+
// reflect idempotently, so a binding system that writes through
|
|
235
|
+
// inputs[].attribute is safe.
|
|
236
|
+
inputs: [
|
|
237
|
+
{ name: "name", attribute: "name" },
|
|
238
|
+
{ name: "userVisibleOnly", attribute: "user-visible-only" },
|
|
239
|
+
{ name: "sysex", attribute: "sysex" },
|
|
240
|
+
],
|
|
241
|
+
// No commands: read-only monitor (see PermissionCore). The Permissions API has
|
|
242
|
+
// no request() — acquiring a grant is the feature node's job.
|
|
243
|
+
commands: [],
|
|
244
|
+
};
|
|
245
|
+
// Created in the Shell constructor with no descriptor so the delegated getters
|
|
246
|
+
// work before connect (returning the default "prompt" / false). The actual
|
|
247
|
+
// query is driven from connectedCallback once the element's attributes resolve
|
|
248
|
+
// (programmatically-created elements have no attributes at construction time).
|
|
249
|
+
_core;
|
|
250
|
+
_connectedCallbackPromise = Promise.resolve();
|
|
251
|
+
constructor() {
|
|
252
|
+
super();
|
|
253
|
+
this._core = new PermissionCore(null, this);
|
|
254
|
+
}
|
|
255
|
+
// --- Attribute accessors ---
|
|
256
|
+
get name() {
|
|
257
|
+
return this.getAttribute("name") ?? "";
|
|
258
|
+
}
|
|
259
|
+
set name(value) {
|
|
260
|
+
this.setAttribute("name", value);
|
|
261
|
+
}
|
|
262
|
+
get userVisibleOnly() {
|
|
263
|
+
return this.hasAttribute("user-visible-only");
|
|
264
|
+
}
|
|
265
|
+
set userVisibleOnly(value) {
|
|
266
|
+
if (value) {
|
|
267
|
+
this.setAttribute("user-visible-only", "");
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
this.removeAttribute("user-visible-only");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
get sysex() {
|
|
274
|
+
return this.hasAttribute("sysex");
|
|
275
|
+
}
|
|
276
|
+
set sysex(value) {
|
|
277
|
+
if (value) {
|
|
278
|
+
this.setAttribute("sysex", "");
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
this.removeAttribute("sysex");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// --- Core delegated getters ---
|
|
285
|
+
get state() {
|
|
286
|
+
return this._core.state;
|
|
287
|
+
}
|
|
288
|
+
get granted() {
|
|
289
|
+
return this._core.granted;
|
|
290
|
+
}
|
|
291
|
+
get denied() {
|
|
292
|
+
return this._core.denied;
|
|
293
|
+
}
|
|
294
|
+
get prompt() {
|
|
295
|
+
return this._core.prompt;
|
|
296
|
+
}
|
|
297
|
+
get unsupported() {
|
|
298
|
+
return this._core.unsupported;
|
|
299
|
+
}
|
|
300
|
+
// wc-bindable connectedCallbackPromise protocol: resolves once the connect-time
|
|
301
|
+
// query settles, so SSR (@wcstack/server render.ts) waits for the first probe
|
|
302
|
+
// before snapshotting the HTML. Mirrors WcsGeolocation.connectedCallbackPromise.
|
|
303
|
+
get connectedCallbackPromise() {
|
|
304
|
+
return this._connectedCallbackPromise;
|
|
305
|
+
}
|
|
306
|
+
// --- Internal ---
|
|
307
|
+
// Build the query descriptor from the current attributes. Only present extras
|
|
308
|
+
// are included so a bare `{ name }` is passed for permissions that take no
|
|
309
|
+
// additional members. The descriptor is fixed at connect time (v1 does not
|
|
310
|
+
// re-query on a `name` change — see README).
|
|
311
|
+
_descriptor() {
|
|
312
|
+
const descriptor = { name: this.name };
|
|
313
|
+
if (this.userVisibleOnly)
|
|
314
|
+
descriptor.userVisibleOnly = true;
|
|
315
|
+
if (this.sysex)
|
|
316
|
+
descriptor.sysex = true;
|
|
317
|
+
return descriptor;
|
|
318
|
+
}
|
|
319
|
+
// --- Lifecycle ---
|
|
320
|
+
connectedCallback() {
|
|
321
|
+
this.style.display = "none";
|
|
322
|
+
// Begin observing (or revive the subscription after a reconnect). The
|
|
323
|
+
// returned promise is held as connectedCallbackPromise for SSR. query() never
|
|
324
|
+
// rejects in a way that escapes — failures surface as the `unsupported`
|
|
325
|
+
// state — so no .catch() is needed.
|
|
326
|
+
this._connectedCallbackPromise = this._core.observe(this._descriptor());
|
|
327
|
+
}
|
|
328
|
+
disconnectedCallback() {
|
|
329
|
+
this._core.dispose();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function registerComponents() {
|
|
334
|
+
if (!customElements.get(config.tagNames.permission)) {
|
|
335
|
+
customElements.define(config.tagNames.permission, WcsPermission);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function bootstrapPermission(userConfig) {
|
|
340
|
+
if (userConfig) {
|
|
341
|
+
setConfig(userConfig);
|
|
342
|
+
}
|
|
343
|
+
registerComponents();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export { PermissionCore, WcsPermission, bootstrapPermission, getConfig };
|
|
347
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/config.ts","../src/core/PermissionCore.ts","../src/components/Permission.ts","../src/registerComponents.ts","../src/bootstrapPermission.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n tagNames: {\n permission: string;\n };\n}\n\nconst _config: IInternalConfig = {\n tagNames: {\n permission: \"wcs-permission\",\n },\n};\n\nfunction deepFreeze<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n Object.freeze(obj);\n for (const key of Object.keys(obj)) {\n deepFreeze((obj as Record<string, unknown>)[key]);\n }\n return obj;\n}\n\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n clone[key] = deepClone((obj as Record<string, unknown>)[key]);\n }\n return clone as T;\n}\n\nlet frozenConfig: IConfig | null = null;\n\nexport const config: IConfig = _config as IConfig;\n\nexport function getConfig(): IConfig {\n if (!frozenConfig) {\n frozenConfig = deepFreeze(deepClone(_config));\n }\n return frozenConfig;\n}\n\nexport function setConfig(partialConfig: IWritableConfig): void {\n if (partialConfig.tagNames) {\n Object.assign(_config.tagNames, partialConfig.tagNames);\n }\n frozenConfig = null;\n}\n","import {\n IWcBindable, PermissionStateOrUnsupported, WcsPermissionDescriptor,\n} from \"../types.js\";\n\n/**\n * Headless permission-state primitive. A thin, framework-agnostic wrapper around\n * the Permissions API exposed through the wc-bindable protocol.\n *\n * Unlike the other @wcstack IO nodes (geolocation / clipboard / sse / …), the\n * Permissions API is **read-only**: it has `query()` but no standard `request()`.\n * Asking the user for a grant is the job of the feature node (`<wcs-geo>` etc.);\n * this node only *observes*. It is therefore a pure element → state monitor with\n * **no commands** — command-token does not apply, only event-token.\n *\n * The single observable is `state` (`navigator.permissions.query(descriptor)`'s\n * `PermissionState`, or `\"unsupported\"`), published via the `wcs-permission:change`\n * event. `granted` / `denied` / `prompt` / `unsupported` are convenience booleans\n * derived from that one event (mirroring how GeolocationCore exposes latitude/…\n * from one `wcs-geo:position` event), so a binding like `hidden@granted` works\n * directly. The live `change` event of the PermissionStatus is tracked so a grant\n * flipping in browser settings flows into the declarative state.\n */\nexport class PermissionCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"state\", event: \"wcs-permission:change\" },\n { name: \"granted\", event: \"wcs-permission:change\", getter: (e: Event) => (e as CustomEvent).detail === \"granted\" },\n { name: \"denied\", event: \"wcs-permission:change\", getter: (e: Event) => (e as CustomEvent).detail === \"denied\" },\n { name: \"prompt\", event: \"wcs-permission:change\", getter: (e: Event) => (e as CustomEvent).detail === \"prompt\" },\n { name: \"unsupported\", event: \"wcs-permission:change\", getter: (e: Event) => (e as CustomEvent).detail === \"unsupported\" },\n ],\n // No commands: the Permissions API is read-only (query-only). See class docs.\n commands: [],\n };\n\n private _target: EventTarget;\n private _descriptor: WcsPermissionDescriptor | null = null;\n\n private _state: PermissionStateOrUnsupported = \"prompt\";\n\n // Live PermissionStatus handle (when the Permissions API is available), kept so\n // the `change` listener can be removed on dispose().\n private _permissionStatus: PermissionStatus | null = null;\n\n // True once a permission subscription has been (or is being) established, and\n // reset by dispose(). Guards observe() so a reconnect after dispose() re-queries\n // while a redundant observe() on an already-live subscription does not.\n private _permissionSubscribed: boolean = false;\n\n // Monotonic id of the current permission query. Bumped by every _initPermission()\n // and by dispose(). Each in-flight query captures its id and, on resolve, bails\n // unless it is still current — so a query superseded by a rapid (synchronous)\n // disconnect→reconnect, or one that resolves after dispose(), never attaches a\n // listener. A plain boolean cannot cover this: dispose()→observe() flips it\n // false→true again, reopening the window for the stale query to slip through.\n private _permGen: number = 0;\n\n // Resolves once the most recent query settles (or immediately when the API is\n // unsupported). The Shell exposes this as connectedCallbackPromise so SSR can\n // await the first probe before snapshotting the HTML.\n private _ready: Promise<void> = Promise.resolve();\n\n constructor(descriptor?: WcsPermissionDescriptor | null, target?: EventTarget) {\n super();\n this._target = target ?? this;\n // Headless ergonomics: when a descriptor is supplied up front, probe the\n // permission state immediately so observers see the real value before the\n // first read. The Shell passes nothing and drives the first query from\n // connectedCallback via observe(), once the element's attributes resolve.\n if (descriptor) {\n this._descriptor = descriptor;\n this._ready = this._initPermission();\n }\n }\n\n get state(): PermissionStateOrUnsupported {\n return this._state;\n }\n\n get granted(): boolean {\n return this._state === \"granted\";\n }\n\n get denied(): boolean {\n return this._state === \"denied\";\n }\n\n get prompt(): boolean {\n return this._state === \"prompt\";\n }\n\n get unsupported(): boolean {\n return this._state === \"unsupported\";\n }\n\n /** Resolves once the current (or initial) query settles. */\n get ready(): Promise<void> {\n return this._ready;\n }\n\n // --- State setter with event dispatch ---\n\n private _setState(state: PermissionStateOrUnsupported): void {\n // Same-value guard: `state` is the only stored value and the derived booleans\n // change in lockstep with it, so suppressing identical re-dispatches is safe.\n if (this._state === state) return;\n this._state = state;\n this._target.dispatchEvent(new CustomEvent(\"wcs-permission:change\", {\n detail: state,\n bubbles: true,\n }));\n }\n\n // --- Public API ---\n\n /**\n * Start observing `descriptor` (e.g. `{ name: \"geolocation\" }`). Idempotent\n * while already subscribed — calling it again only updates the stored descriptor\n * for a *future* re-subscription; it does **not** re-query, even when called with\n * a different descriptor (the Shell binds at a fixed connect-time descriptor and\n * does not re-query on a `name` change in v1). To switch permission mid-life,\n * dispose() first, then observe() the new descriptor. On the first call, or after\n * a dispose(), it issues the query and subscribes to the live `change` event.\n * Returns a promise that resolves once that query settles, for SSR.\n */\n observe(descriptor: WcsPermissionDescriptor): Promise<void> {\n this._descriptor = descriptor;\n if (!this._permissionSubscribed) {\n this._ready = this._initPermission();\n }\n return this._ready;\n }\n\n /**\n * Detach the live permission `change` listener. Call from the Shell's\n * `disconnectedCallback` so a removed element does not leak the subscription.\n * A later reconnect can re-subscribe via observe().\n *\n * Headless callers (using PermissionCore directly, without the Shell) own this\n * lifecycle themselves: call dispose() when the observer is no longer needed,\n * otherwise the live PermissionStatus `change` listener keeps this instance\n * reachable for as long as the status is alive. dispose() is safe to call when\n * never subscribed and may be paired with a later observe() to resume.\n */\n dispose(): void {\n this._permissionSubscribed = false;\n // Invalidate any in-flight query so its .then() bails instead of attaching a\n // listener after teardown.\n this._permGen++;\n if (this._permissionStatus) {\n this._permissionStatus.removeEventListener(\"change\", this._onPermissionChange);\n this._permissionStatus = null;\n }\n }\n\n // --- Internal ---\n\n private _initPermission(): Promise<void> {\n // Guard a missing/empty permission name (e.g. a `<wcs-permission>` with no\n // `name` attribute). Such a descriptor would only ever reject at query() and\n // silently fall back to \"unsupported\", which is hard to diagnose. Short-circuit\n // to \"unsupported\" without issuing a doomed query so the misconfiguration\n // surfaces deterministically and no listener is attached.\n if (!this._descriptor || !this._descriptor.name) {\n this._setState(\"unsupported\");\n return Promise.resolve();\n }\n // The Permissions API is optional. When absent (or it rejects, e.g. the\n // browser does not accept the requested permission name), report \"unsupported\"\n // and leave it at that — there is nothing to retry.\n if (typeof navigator === \"undefined\" || !navigator.permissions || typeof navigator.permissions.query !== \"function\") {\n // Route through _setState (not a bare assignment) so observers stay in sync\n // with the public state. The same-value guard means no redundant dispatch\n // when the state does not actually change.\n this._setState(\"unsupported\");\n // Intentionally does NOT set _permissionSubscribed: there is no listener to\n // tear down, so a reconnect simply re-probes (idempotent — the same-value\n // guard suppresses any dispatch and no listener is ever attached). Mirrors\n // GeolocationCore's reinitPermission behavior in unsupported environments.\n return Promise.resolve();\n }\n this._permissionSubscribed = true;\n const gen = ++this._permGen;\n // Cast: WcsPermissionDescriptor widens `name` to string (and allows extra\n // descriptor members like userVisibleOnly / sysex) where the lib DOM type\n // expects the PermissionName union.\n return navigator.permissions.query(this._descriptor as unknown as PermissionDescriptor).then(\n (status) => {\n // Stale resolution: this query was superseded (rapid reconnect) or the\n // element was disposed while it was in flight. Drop it so only the current\n // subscription attaches a listener.\n if (gen !== this._permGen) return;\n this._permissionStatus = status;\n this._setState(status.state as PermissionStateOrUnsupported);\n status.addEventListener(\"change\", this._onPermissionChange);\n },\n () => {\n if (gen !== this._permGen) return;\n this._setState(\"unsupported\");\n },\n );\n }\n\n private _onPermissionChange = (event: Event): void => {\n const status = event.target as PermissionStatus;\n this._setState(status.state as PermissionStateOrUnsupported);\n };\n}\n","import { IWcBindable, PermissionStateOrUnsupported, WcsPermissionDescriptor } from \"../types.js\";\nimport { PermissionCore } from \"../core/PermissionCore.js\";\n\n// Named WcsPermission (not `Permission`) so the class does not shadow any global,\n// and to match the <wcs-geo> / <wcs-ws> convention (WcsGeolocation /\n// WcsWebSocket). The public export keeps the `WcsPermission` name unchanged.\nexport class WcsPermission extends HTMLElement {\n static hasConnectedCallbackPromise = true;\n static wcBindable: IWcBindable = {\n ...PermissionCore.wcBindable,\n // Shell-level settable surface. `name` is the permission name; the descriptor\n // extras `user-visible-only` (push) and `sysex` (midi) are boolean flags that\n // reflect idempotently, so a binding system that writes through\n // inputs[].attribute is safe.\n inputs: [\n { name: \"name\", attribute: \"name\" },\n { name: \"userVisibleOnly\", attribute: \"user-visible-only\" },\n { name: \"sysex\", attribute: \"sysex\" },\n ],\n // No commands: read-only monitor (see PermissionCore). The Permissions API has\n // no request() — acquiring a grant is the feature node's job.\n commands: [],\n };\n\n // Created in the Shell constructor with no descriptor so the delegated getters\n // work before connect (returning the default \"prompt\" / false). The actual\n // query is driven from connectedCallback once the element's attributes resolve\n // (programmatically-created elements have no attributes at construction time).\n private _core: PermissionCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n\n constructor() {\n super();\n this._core = new PermissionCore(null, this);\n }\n\n // --- Attribute accessors ---\n\n get name(): string {\n return this.getAttribute(\"name\") ?? \"\";\n }\n\n set name(value: string) {\n this.setAttribute(\"name\", value);\n }\n\n get userVisibleOnly(): boolean {\n return this.hasAttribute(\"user-visible-only\");\n }\n\n set userVisibleOnly(value: boolean) {\n if (value) {\n this.setAttribute(\"user-visible-only\", \"\");\n } else {\n this.removeAttribute(\"user-visible-only\");\n }\n }\n\n get sysex(): boolean {\n return this.hasAttribute(\"sysex\");\n }\n\n set sysex(value: boolean) {\n if (value) {\n this.setAttribute(\"sysex\", \"\");\n } else {\n this.removeAttribute(\"sysex\");\n }\n }\n\n // --- Core delegated getters ---\n\n get state(): PermissionStateOrUnsupported {\n return this._core.state;\n }\n\n get granted(): boolean {\n return this._core.granted;\n }\n\n get denied(): boolean {\n return this._core.denied;\n }\n\n get prompt(): boolean {\n return this._core.prompt;\n }\n\n get unsupported(): boolean {\n return this._core.unsupported;\n }\n\n // wc-bindable connectedCallbackPromise protocol: resolves once the connect-time\n // query settles, so SSR (@wcstack/server render.ts) waits for the first probe\n // before snapshotting the HTML. Mirrors WcsGeolocation.connectedCallbackPromise.\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Internal ---\n\n // Build the query descriptor from the current attributes. Only present extras\n // are included so a bare `{ name }` is passed for permissions that take no\n // additional members. The descriptor is fixed at connect time (v1 does not\n // re-query on a `name` change — see README).\n private _descriptor(): WcsPermissionDescriptor {\n const descriptor: WcsPermissionDescriptor = { name: this.name };\n if (this.userVisibleOnly) descriptor.userVisibleOnly = true;\n if (this.sysex) descriptor.sysex = true;\n return descriptor;\n }\n\n // --- Lifecycle ---\n\n connectedCallback(): void {\n this.style.display = \"none\";\n // Begin observing (or revive the subscription after a reconnect). The\n // returned promise is held as connectedCallbackPromise for SSR. query() never\n // rejects in a way that escapes — failures surface as the `unsupported`\n // state — so no .catch() is needed.\n this._connectedCallbackPromise = this._core.observe(this._descriptor());\n }\n\n disconnectedCallback(): void {\n this._core.dispose();\n }\n}\n","import { WcsPermission } from \"./components/Permission.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.permission)) {\n customElements.define(config.tagNames.permission, WcsPermission);\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapPermission(userConfig?: IWritableConfig): void {\n if (userConfig) {\n setConfig(userConfig);\n }\n registerComponents();\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e={tagNames:{permission:"wcs-permission"}};function t(e){if(null===e||"object"!=typeof e)return e;Object.freeze(e);for(const s of Object.keys(e))t(e[s]);return e}function s(e){if(null===e||"object"!=typeof e)return e;const t={};for(const i of Object.keys(e))t[i]=s(e[i]);return t}let i=null;const r=e;function n(){return i||(i=t(s(e))),i}class o extends EventTarget{static wcBindable={protocol:"wc-bindable",version:1,properties:[{name:"state",event:"wcs-permission:change"},{name:"granted",event:"wcs-permission:change",getter:e=>"granted"===e.detail},{name:"denied",event:"wcs-permission:change",getter:e=>"denied"===e.detail},{name:"prompt",event:"wcs-permission:change",getter:e=>"prompt"===e.detail},{name:"unsupported",event:"wcs-permission:change",getter:e=>"unsupported"===e.detail}],commands:[]};_target;_descriptor=null;_state="prompt";_permissionStatus=null;_permissionSubscribed=!1;_permGen=0;_ready=Promise.resolve();constructor(e,t){super(),this._target=t??this,e&&(this._descriptor=e,this._ready=this._initPermission())}get state(){return this._state}get granted(){return"granted"===this._state}get denied(){return"denied"===this._state}get prompt(){return"prompt"===this._state}get unsupported(){return"unsupported"===this._state}get ready(){return this._ready}_setState(e){this._state!==e&&(this._state=e,this._target.dispatchEvent(new CustomEvent("wcs-permission:change",{detail:e,bubbles:!0})))}observe(e){return this._descriptor=e,this._permissionSubscribed||(this._ready=this._initPermission()),this._ready}dispose(){this._permissionSubscribed=!1,this._permGen++,this._permissionStatus&&(this._permissionStatus.removeEventListener("change",this._onPermissionChange),this._permissionStatus=null)}_initPermission(){if(!this._descriptor||!this._descriptor.name)return this._setState("unsupported"),Promise.resolve();if("undefined"==typeof navigator||!navigator.permissions||"function"!=typeof navigator.permissions.query)return this._setState("unsupported"),Promise.resolve();this._permissionSubscribed=!0;const e=++this._permGen;return navigator.permissions.query(this._descriptor).then(t=>{e===this._permGen&&(this._permissionStatus=t,this._setState(t.state),t.addEventListener("change",this._onPermissionChange))},()=>{e===this._permGen&&this._setState("unsupported")})}_onPermissionChange=e=>{const t=e.target;this._setState(t.state)}}class a extends HTMLElement{static hasConnectedCallbackPromise=!0;static wcBindable={...o.wcBindable,inputs:[{name:"name",attribute:"name"},{name:"userVisibleOnly",attribute:"user-visible-only"},{name:"sysex",attribute:"sysex"}],commands:[]};_core;_connectedCallbackPromise=Promise.resolve();constructor(){super(),this._core=new o(null,this)}get name(){return this.getAttribute("name")??""}set name(e){this.setAttribute("name",e)}get userVisibleOnly(){return this.hasAttribute("user-visible-only")}set userVisibleOnly(e){e?this.setAttribute("user-visible-only",""):this.removeAttribute("user-visible-only")}get sysex(){return this.hasAttribute("sysex")}set sysex(e){e?this.setAttribute("sysex",""):this.removeAttribute("sysex")}get state(){return this._core.state}get granted(){return this._core.granted}get denied(){return this._core.denied}get prompt(){return this._core.prompt}get unsupported(){return this._core.unsupported}get connectedCallbackPromise(){return this._connectedCallbackPromise}_descriptor(){const e={name:this.name};return this.userVisibleOnly&&(e.userVisibleOnly=!0),this.sysex&&(e.sysex=!0),e}connectedCallback(){this.style.display="none",this._connectedCallbackPromise=this._core.observe(this._descriptor())}disconnectedCallback(){this._core.dispose()}}function u(t){var s;t&&((s=t).tagNames&&Object.assign(e.tagNames,s.tagNames),i=null),customElements.get(r.tagNames.permission)||customElements.define(r.tagNames.permission,a)}export{o as PermissionCore,a as WcsPermission,u as bootstrapPermission,n as getConfig};
|
|
2
|
+
//# sourceMappingURL=index.esm.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.min.js","sources":["../src/config.ts","../src/core/PermissionCore.ts","../src/components/Permission.ts","../src/bootstrapPermission.ts","../src/registerComponents.ts"],"sourcesContent":["import { IConfig, IWritableConfig } from \"./types.js\";\n\ninterface IInternalConfig extends IConfig {\n tagNames: {\n permission: string;\n };\n}\n\nconst _config: IInternalConfig = {\n tagNames: {\n permission: \"wcs-permission\",\n },\n};\n\nfunction deepFreeze<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n Object.freeze(obj);\n for (const key of Object.keys(obj)) {\n deepFreeze((obj as Record<string, unknown>)[key]);\n }\n return obj;\n}\n\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const clone: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n clone[key] = deepClone((obj as Record<string, unknown>)[key]);\n }\n return clone as T;\n}\n\nlet frozenConfig: IConfig | null = null;\n\nexport const config: IConfig = _config as IConfig;\n\nexport function getConfig(): IConfig {\n if (!frozenConfig) {\n frozenConfig = deepFreeze(deepClone(_config));\n }\n return frozenConfig;\n}\n\nexport function setConfig(partialConfig: IWritableConfig): void {\n if (partialConfig.tagNames) {\n Object.assign(_config.tagNames, partialConfig.tagNames);\n }\n frozenConfig = null;\n}\n","import {\n IWcBindable, PermissionStateOrUnsupported, WcsPermissionDescriptor,\n} from \"../types.js\";\n\n/**\n * Headless permission-state primitive. A thin, framework-agnostic wrapper around\n * the Permissions API exposed through the wc-bindable protocol.\n *\n * Unlike the other @wcstack IO nodes (geolocation / clipboard / sse / …), the\n * Permissions API is **read-only**: it has `query()` but no standard `request()`.\n * Asking the user for a grant is the job of the feature node (`<wcs-geo>` etc.);\n * this node only *observes*. It is therefore a pure element → state monitor with\n * **no commands** — command-token does not apply, only event-token.\n *\n * The single observable is `state` (`navigator.permissions.query(descriptor)`'s\n * `PermissionState`, or `\"unsupported\"`), published via the `wcs-permission:change`\n * event. `granted` / `denied` / `prompt` / `unsupported` are convenience booleans\n * derived from that one event (mirroring how GeolocationCore exposes latitude/…\n * from one `wcs-geo:position` event), so a binding like `hidden@granted` works\n * directly. The live `change` event of the PermissionStatus is tracked so a grant\n * flipping in browser settings flows into the declarative state.\n */\nexport class PermissionCore extends EventTarget {\n static wcBindable: IWcBindable = {\n protocol: \"wc-bindable\",\n version: 1,\n properties: [\n { name: \"state\", event: \"wcs-permission:change\" },\n { name: \"granted\", event: \"wcs-permission:change\", getter: (e: Event) => (e as CustomEvent).detail === \"granted\" },\n { name: \"denied\", event: \"wcs-permission:change\", getter: (e: Event) => (e as CustomEvent).detail === \"denied\" },\n { name: \"prompt\", event: \"wcs-permission:change\", getter: (e: Event) => (e as CustomEvent).detail === \"prompt\" },\n { name: \"unsupported\", event: \"wcs-permission:change\", getter: (e: Event) => (e as CustomEvent).detail === \"unsupported\" },\n ],\n // No commands: the Permissions API is read-only (query-only). See class docs.\n commands: [],\n };\n\n private _target: EventTarget;\n private _descriptor: WcsPermissionDescriptor | null = null;\n\n private _state: PermissionStateOrUnsupported = \"prompt\";\n\n // Live PermissionStatus handle (when the Permissions API is available), kept so\n // the `change` listener can be removed on dispose().\n private _permissionStatus: PermissionStatus | null = null;\n\n // True once a permission subscription has been (or is being) established, and\n // reset by dispose(). Guards observe() so a reconnect after dispose() re-queries\n // while a redundant observe() on an already-live subscription does not.\n private _permissionSubscribed: boolean = false;\n\n // Monotonic id of the current permission query. Bumped by every _initPermission()\n // and by dispose(). Each in-flight query captures its id and, on resolve, bails\n // unless it is still current — so a query superseded by a rapid (synchronous)\n // disconnect→reconnect, or one that resolves after dispose(), never attaches a\n // listener. A plain boolean cannot cover this: dispose()→observe() flips it\n // false→true again, reopening the window for the stale query to slip through.\n private _permGen: number = 0;\n\n // Resolves once the most recent query settles (or immediately when the API is\n // unsupported). The Shell exposes this as connectedCallbackPromise so SSR can\n // await the first probe before snapshotting the HTML.\n private _ready: Promise<void> = Promise.resolve();\n\n constructor(descriptor?: WcsPermissionDescriptor | null, target?: EventTarget) {\n super();\n this._target = target ?? this;\n // Headless ergonomics: when a descriptor is supplied up front, probe the\n // permission state immediately so observers see the real value before the\n // first read. The Shell passes nothing and drives the first query from\n // connectedCallback via observe(), once the element's attributes resolve.\n if (descriptor) {\n this._descriptor = descriptor;\n this._ready = this._initPermission();\n }\n }\n\n get state(): PermissionStateOrUnsupported {\n return this._state;\n }\n\n get granted(): boolean {\n return this._state === \"granted\";\n }\n\n get denied(): boolean {\n return this._state === \"denied\";\n }\n\n get prompt(): boolean {\n return this._state === \"prompt\";\n }\n\n get unsupported(): boolean {\n return this._state === \"unsupported\";\n }\n\n /** Resolves once the current (or initial) query settles. */\n get ready(): Promise<void> {\n return this._ready;\n }\n\n // --- State setter with event dispatch ---\n\n private _setState(state: PermissionStateOrUnsupported): void {\n // Same-value guard: `state` is the only stored value and the derived booleans\n // change in lockstep with it, so suppressing identical re-dispatches is safe.\n if (this._state === state) return;\n this._state = state;\n this._target.dispatchEvent(new CustomEvent(\"wcs-permission:change\", {\n detail: state,\n bubbles: true,\n }));\n }\n\n // --- Public API ---\n\n /**\n * Start observing `descriptor` (e.g. `{ name: \"geolocation\" }`). Idempotent\n * while already subscribed — calling it again only updates the stored descriptor\n * for a *future* re-subscription; it does **not** re-query, even when called with\n * a different descriptor (the Shell binds at a fixed connect-time descriptor and\n * does not re-query on a `name` change in v1). To switch permission mid-life,\n * dispose() first, then observe() the new descriptor. On the first call, or after\n * a dispose(), it issues the query and subscribes to the live `change` event.\n * Returns a promise that resolves once that query settles, for SSR.\n */\n observe(descriptor: WcsPermissionDescriptor): Promise<void> {\n this._descriptor = descriptor;\n if (!this._permissionSubscribed) {\n this._ready = this._initPermission();\n }\n return this._ready;\n }\n\n /**\n * Detach the live permission `change` listener. Call from the Shell's\n * `disconnectedCallback` so a removed element does not leak the subscription.\n * A later reconnect can re-subscribe via observe().\n *\n * Headless callers (using PermissionCore directly, without the Shell) own this\n * lifecycle themselves: call dispose() when the observer is no longer needed,\n * otherwise the live PermissionStatus `change` listener keeps this instance\n * reachable for as long as the status is alive. dispose() is safe to call when\n * never subscribed and may be paired with a later observe() to resume.\n */\n dispose(): void {\n this._permissionSubscribed = false;\n // Invalidate any in-flight query so its .then() bails instead of attaching a\n // listener after teardown.\n this._permGen++;\n if (this._permissionStatus) {\n this._permissionStatus.removeEventListener(\"change\", this._onPermissionChange);\n this._permissionStatus = null;\n }\n }\n\n // --- Internal ---\n\n private _initPermission(): Promise<void> {\n // Guard a missing/empty permission name (e.g. a `<wcs-permission>` with no\n // `name` attribute). Such a descriptor would only ever reject at query() and\n // silently fall back to \"unsupported\", which is hard to diagnose. Short-circuit\n // to \"unsupported\" without issuing a doomed query so the misconfiguration\n // surfaces deterministically and no listener is attached.\n if (!this._descriptor || !this._descriptor.name) {\n this._setState(\"unsupported\");\n return Promise.resolve();\n }\n // The Permissions API is optional. When absent (or it rejects, e.g. the\n // browser does not accept the requested permission name), report \"unsupported\"\n // and leave it at that — there is nothing to retry.\n if (typeof navigator === \"undefined\" || !navigator.permissions || typeof navigator.permissions.query !== \"function\") {\n // Route through _setState (not a bare assignment) so observers stay in sync\n // with the public state. The same-value guard means no redundant dispatch\n // when the state does not actually change.\n this._setState(\"unsupported\");\n // Intentionally does NOT set _permissionSubscribed: there is no listener to\n // tear down, so a reconnect simply re-probes (idempotent — the same-value\n // guard suppresses any dispatch and no listener is ever attached). Mirrors\n // GeolocationCore's reinitPermission behavior in unsupported environments.\n return Promise.resolve();\n }\n this._permissionSubscribed = true;\n const gen = ++this._permGen;\n // Cast: WcsPermissionDescriptor widens `name` to string (and allows extra\n // descriptor members like userVisibleOnly / sysex) where the lib DOM type\n // expects the PermissionName union.\n return navigator.permissions.query(this._descriptor as unknown as PermissionDescriptor).then(\n (status) => {\n // Stale resolution: this query was superseded (rapid reconnect) or the\n // element was disposed while it was in flight. Drop it so only the current\n // subscription attaches a listener.\n if (gen !== this._permGen) return;\n this._permissionStatus = status;\n this._setState(status.state as PermissionStateOrUnsupported);\n status.addEventListener(\"change\", this._onPermissionChange);\n },\n () => {\n if (gen !== this._permGen) return;\n this._setState(\"unsupported\");\n },\n );\n }\n\n private _onPermissionChange = (event: Event): void => {\n const status = event.target as PermissionStatus;\n this._setState(status.state as PermissionStateOrUnsupported);\n };\n}\n","import { IWcBindable, PermissionStateOrUnsupported, WcsPermissionDescriptor } from \"../types.js\";\nimport { PermissionCore } from \"../core/PermissionCore.js\";\n\n// Named WcsPermission (not `Permission`) so the class does not shadow any global,\n// and to match the <wcs-geo> / <wcs-ws> convention (WcsGeolocation /\n// WcsWebSocket). The public export keeps the `WcsPermission` name unchanged.\nexport class WcsPermission extends HTMLElement {\n static hasConnectedCallbackPromise = true;\n static wcBindable: IWcBindable = {\n ...PermissionCore.wcBindable,\n // Shell-level settable surface. `name` is the permission name; the descriptor\n // extras `user-visible-only` (push) and `sysex` (midi) are boolean flags that\n // reflect idempotently, so a binding system that writes through\n // inputs[].attribute is safe.\n inputs: [\n { name: \"name\", attribute: \"name\" },\n { name: \"userVisibleOnly\", attribute: \"user-visible-only\" },\n { name: \"sysex\", attribute: \"sysex\" },\n ],\n // No commands: read-only monitor (see PermissionCore). The Permissions API has\n // no request() — acquiring a grant is the feature node's job.\n commands: [],\n };\n\n // Created in the Shell constructor with no descriptor so the delegated getters\n // work before connect (returning the default \"prompt\" / false). The actual\n // query is driven from connectedCallback once the element's attributes resolve\n // (programmatically-created elements have no attributes at construction time).\n private _core: PermissionCore;\n private _connectedCallbackPromise: Promise<void> = Promise.resolve();\n\n constructor() {\n super();\n this._core = new PermissionCore(null, this);\n }\n\n // --- Attribute accessors ---\n\n get name(): string {\n return this.getAttribute(\"name\") ?? \"\";\n }\n\n set name(value: string) {\n this.setAttribute(\"name\", value);\n }\n\n get userVisibleOnly(): boolean {\n return this.hasAttribute(\"user-visible-only\");\n }\n\n set userVisibleOnly(value: boolean) {\n if (value) {\n this.setAttribute(\"user-visible-only\", \"\");\n } else {\n this.removeAttribute(\"user-visible-only\");\n }\n }\n\n get sysex(): boolean {\n return this.hasAttribute(\"sysex\");\n }\n\n set sysex(value: boolean) {\n if (value) {\n this.setAttribute(\"sysex\", \"\");\n } else {\n this.removeAttribute(\"sysex\");\n }\n }\n\n // --- Core delegated getters ---\n\n get state(): PermissionStateOrUnsupported {\n return this._core.state;\n }\n\n get granted(): boolean {\n return this._core.granted;\n }\n\n get denied(): boolean {\n return this._core.denied;\n }\n\n get prompt(): boolean {\n return this._core.prompt;\n }\n\n get unsupported(): boolean {\n return this._core.unsupported;\n }\n\n // wc-bindable connectedCallbackPromise protocol: resolves once the connect-time\n // query settles, so SSR (@wcstack/server render.ts) waits for the first probe\n // before snapshotting the HTML. Mirrors WcsGeolocation.connectedCallbackPromise.\n get connectedCallbackPromise(): Promise<void> {\n return this._connectedCallbackPromise;\n }\n\n // --- Internal ---\n\n // Build the query descriptor from the current attributes. Only present extras\n // are included so a bare `{ name }` is passed for permissions that take no\n // additional members. The descriptor is fixed at connect time (v1 does not\n // re-query on a `name` change — see README).\n private _descriptor(): WcsPermissionDescriptor {\n const descriptor: WcsPermissionDescriptor = { name: this.name };\n if (this.userVisibleOnly) descriptor.userVisibleOnly = true;\n if (this.sysex) descriptor.sysex = true;\n return descriptor;\n }\n\n // --- Lifecycle ---\n\n connectedCallback(): void {\n this.style.display = \"none\";\n // Begin observing (or revive the subscription after a reconnect). The\n // returned promise is held as connectedCallbackPromise for SSR. query() never\n // rejects in a way that escapes — failures surface as the `unsupported`\n // state — so no .catch() is needed.\n this._connectedCallbackPromise = this._core.observe(this._descriptor());\n }\n\n disconnectedCallback(): void {\n this._core.dispose();\n }\n}\n","import { setConfig } from \"./config.js\";\nimport { registerComponents } from \"./registerComponents.js\";\nimport { IWritableConfig } from \"./types.js\";\n\nexport function bootstrapPermission(userConfig?: IWritableConfig): void {\n if (userConfig) {\n setConfig(userConfig);\n }\n registerComponents();\n}\n","import { WcsPermission } from \"./components/Permission.js\";\nimport { config } from \"./config.js\";\n\nexport function registerComponents(): void {\n if (!customElements.get(config.tagNames.permission)) {\n customElements.define(config.tagNames.permission, WcsPermission);\n }\n}\n"],"names":["_config","tagNames","permission","deepFreeze","obj","Object","freeze","key","keys","deepClone","clone","frozenConfig","config","getConfig","PermissionCore","EventTarget","static","protocol","version","properties","name","event","getter","e","detail","commands","_target","_descriptor","_state","_permissionStatus","_permissionSubscribed","_permGen","_ready","Promise","resolve","constructor","descriptor","target","super","this","_initPermission","state","granted","denied","prompt","unsupported","ready","_setState","dispatchEvent","CustomEvent","bubbles","observe","dispose","removeEventListener","_onPermissionChange","navigator","permissions","query","gen","then","status","addEventListener","WcsPermission","HTMLElement","wcBindable","inputs","attribute","_core","_connectedCallbackPromise","getAttribute","value","setAttribute","userVisibleOnly","hasAttribute","removeAttribute","sysex","connectedCallbackPromise","connectedCallback","style","display","disconnectedCallback","bootstrapPermission","userConfig","partialConfig","assign","customElements","get","define"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wcstack/permission",
|
|
3
|
+
"version": "1.13.1",
|
|
4
|
+
"description": "Declarative permission-state component for Web Components. Framework-agnostic Permissions API monitor via wc-bindable-protocol.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.esm.js",
|
|
7
|
+
"module": "./dist/index.esm.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.esm.js"
|
|
13
|
+
},
|
|
14
|
+
"./auto": "./dist/auto.min.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"clean": "rimraf dist .tsc-out",
|
|
21
|
+
"build": "rimraf dist .tsc-out && tsc && rollup -c",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
25
|
+
"lint": "eslint src",
|
|
26
|
+
"version:patch": "npm version patch",
|
|
27
|
+
"version:minor": "npm version minor",
|
|
28
|
+
"version:major": "npm version major",
|
|
29
|
+
"prepublishOnly": "npm run build && npm run test:coverage"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"web-components",
|
|
33
|
+
"permissions",
|
|
34
|
+
"permission",
|
|
35
|
+
"permissions-api",
|
|
36
|
+
"custom-elements",
|
|
37
|
+
"wc-bindable",
|
|
38
|
+
"declarative",
|
|
39
|
+
"zero-dependencies",
|
|
40
|
+
"framework-agnostic"
|
|
41
|
+
],
|
|
42
|
+
"author": "mogera551",
|
|
43
|
+
"homepage": "https://wcstack.github.io",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/wcstack/wcstack.git",
|
|
47
|
+
"directory": "packages/permission"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/wcstack/wcstack/issues"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@eslint/js": "^9.39.1",
|
|
55
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
56
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
57
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
58
|
+
"@vitest/ui": "^4.0.15",
|
|
59
|
+
"eslint": "^9.39.1",
|
|
60
|
+
"globals": "^16.5.0",
|
|
61
|
+
"happy-dom": "^20.0.11",
|
|
62
|
+
"rimraf": "^6.0.1",
|
|
63
|
+
"rollup": "^4.22.4",
|
|
64
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
65
|
+
"rollup-plugin-copy": "^3.5.0",
|
|
66
|
+
"tslib": "^2.8.1",
|
|
67
|
+
"typescript": "^5.9.3",
|
|
68
|
+
"typescript-eslint": "^8.49.0",
|
|
69
|
+
"vitest": "^4.0.15"
|
|
70
|
+
}
|
|
71
|
+
}
|