【JavaScript】Idle Detection APIとかいう悪用以外の使い道がないAPI

2024/09/10に[Idle Detection API](https://wicg.github.io/idle-detection/)というAPIが更新されていました。
ステータスは[Draft Community Group Report](https://www.w3.org/community/about/process/)です。
これはコミュニティによる提案であり、W3Cによる正式な勧告ではありません。
個人や団体レベルでも、とりあえずRFCを作ってみたり検討したりできる段階ということです。

以下は[この提案を管理しているGitHub](https://github.com/WICG/idle-detection)から、このRFCの意義を解説した[Readme](https://github.com/WICG/idle-detection/blob/main/README.md)の紹介です。


# User Idle Detection API

このAPIでは、開発者はユーザがアイドル状態になったとき(キーボードマウスを操作していない、スクリーンセーバーが起動した、画面がロックした等)のイベントリスナーを登録できます。
入力イベントの監視によるソリューションと異なり、この機能はサイトのコンテンツ領域を超えて機能します。
すなわち、ユーザが別のウィンドウやタブを操作している場合などにも機能します。

ネイティブアプリケーションやブラウザ拡張機能などでは、アイドル検出を利用して、ユーザがチャットに参加できない状態であることを他のチャットユーザに通知したり、メディアを一時停止して帯域を節約したり、ユーザが戻ってきたときにタイムリーなアラートを表示したりします。
このAPIでは、ユーザのアイドル状態を検出する方法と、スクリプトからポーリングすることなく状態の変更をイベントリスナーに通知する手段を提供します。

参考：[ディスカッションスレッド](https://discourse.wicg.io/t/idle-detection-api/2959/)・[Issues](https://github.com/WICG/idle-detection/issues)


## Use cases

ユースケース。

チャットアプリ：ユーザのステータスを他ユーザに通知する。ユーザがアクティブなデバイスにだけ通知を配信する。
タイムリーな通知：たとえばユーザがアクティブに戻るまでは通知を停止する。
サービスワーカー：保存されていない状態が残っていない場合にのみ、タブの再読み込みをトリガーして古いサービスワーカーを更新する。


## Relationship with other APIs

他APIとの関係。

[requestIdleCallback()](https://www.w3.org/TR/requestidlecallback/)：本APIはシステムがアイドル状態になったときに作業を非同期的にスケジュールする機能ではありません。

[Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API)：ページが表示されなくなったあとでもアイドル状態を検出できます。

## Polyfills

[idle.ts](https://github.com/dropbox/idle.ts)などは独自のコンテンツ領域の状態しか拾うことができません。
現在のスクリプトは、ユーザがコンテンツ領域外でアイドル状態になったかどうかを判断することはできません。


## Model

APIは、次の2つの次元で表されます。

・ユーザのアイドル状態

ユーザが、一定期間ユーザエージェントとやりとりしていない。

・画面のアイドル状態

システムが画面をロックしている。

アクティブとアイドル状態を区別するには、ユーザエージェントやOSによって異なる検出基準が必要となります。

本仕様では、アイドル状態の識別を意図的に定義していません。
この定義はユーザエージェントに委ねられています。

たとえば複数の仮想デスクトップを使ってシステムを操作していて、仮想デスクトップ1をアクティブに操作しているものの、仮想デスクトップ2のコンテンツは表示されていない場合があります。
この場合、仮想デスクトップ2はアイドル状態になる必要があります。


## API Design

本APIの設計は[Sensors API](https://w3c.github.io/sensors)から着想を得ています。

### WebIDL

```webidl
dictionary IdleOptions {
  [EnforceRange] unsigned long threshold;
  AbortSignal signal;
};

enum UserIdleState {
    "active",
    "idle"
};

enum ScreenIdleState {
    "locked",
    "unlocked"
};

[
  SecureContext,
  Exposed=(Window,DedicatedWorker)
] interface IdleDetector : EventTarget {
  constructor();
  readonly attribute UserIdleState? userState;
  readonly attribute ScreenIdleState? screenState;
  attribute EventHandler onchange;
  [Exposed=Window] static Promise<PermissionState> requestPermission();
  Promise<void> start(optional IdleOptions options = {});
};
```

### Example

使用例です。

```javascript
const main = async () => {
  // Feature detection.
  if (!('IdleDetector' in window)) {
    return console.log('IdleDetectorがない。');
  }
  // パーミッションチェック
  if ((await IdleDetector.requestPermission() !== 'granted') {
    return console.log('IdleDetectorが許可されていない。');
  }
  try {
    const controller = new AbortController();
    const signal = controller.signal;
    
    const idleDetector = new IdleDetector();
    idleDetector.addEventListener('change', () => {
      console.log(`Idle change: ${idleDetector.userState}, ${idleDetector.screenState}.`);
    });    
    await idleDetector.start({
      threshold: 60000,
      signal,
    });
    console.log('IdleDetectorが有効になった。');
    
    window.setTimeout(() => {
      controller.abort();
      console.log('IdleDetector終了。');
    }, 120000);
  } catch (err) {
    // 実行時エラー
    console.error(err.name, err.message);
  }
};

main();
```


## Platforms

全てのプラットフォーム(Linux・Windows・macOS・Android・iOS・ChromeOS)は何らかのアイドル検出をサポートしています。

デスクトップデバイスは、一定時間操作がないとスクリーンセーバーが起動します。
さらにスクリーンセーバーが一定時間アクティブだった場合は、ユーザに再認証を要求することもあります。
これらのイベントはエンジンが監視可能です。

モバイルデバイスでは、数秒間非アクティブだとバッテリー節約のため画面が暗くなります。
これはエンジンで監視することができません。
さらに時間が経つと、バッテリーをもっと節約するため最終的に画面がオフになります。
こちらはエンジンで監視可能です。


## Permissions

このAPIを利用するには、新しい権限`idle-detection`が必要です。


## Security and Privacy

アイドル状態はグローバルなシステムプロパティであるため、これをクロスオリジンや識別用として使用しないよう注意する必要があります。
これは[Generic Sensors](https://w3c.github.io/sensors/)や[Geolocation](https://w3c.github.io/geolocation-api/)など、システムイベントにアクセスするその他のAPIと同様です。

アイドル状態の反映までの時間が短くなると、複数のタブを用いることでユーザの動作を識別することができます。
閾値が非常に短い場合、アイドル状態の変更から入力のリズムを推測し、機密データの漏洩につながる可能性もあるかもしれません。
身体障害や認知障害のあるユーザは、コンテンツの認識や操作に非常に時間がかかる可能性があります。
APIはそのようなユーザを区別したり、アイドル状態によってコンテンツと対話する機能を制限してはなりません。

検出の閾値を制限する場合は、高速ポーリングなどによって閾値の制限を回避されないように実装する必要があります。

グローバル状態の漏出を軽減するため、APIは新たな権限`idle-detection`を必要とします。
権限を要求することにより、クロスオリジン識別の問題は完全に解決するわけではありませんが、可能性を減らすことができます。

ページが権限を要求した場合、ユーザに通知されます。

![21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/0433ea49-566a-12c1-025f-d012ca97ebcb.png)

既存の権限の拡張ではなく新たな権限として、そのサイトがどの追加機能にアクセスできるのかをユーザに明示します。

![22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/f0638758-462b-6ab3-e0b8-51586cf42627.png)

プライベートブラウジングを提供する実装は、この機能を有効にしないようにします。
また、自動不承認のタイミングをランダムに遅らせるなどして、Webサイトがプライバシーモードを検出できないように注意する必要があります。


## Alternative Permissions Models

代替として検討されたパーミッションモデル。
本APIでは導入されていません。

### Event Timing Fuzzing

現在のアイドル状態と、アイドル状態遷移のタイミングは、オリジン境界を越えてユーザ識別を行うために利用できるシグナルになります。
たとえば2つのサイトで、クライアントが同時にactiveからidleになった場合、そのクライアントは同一ユーザである可能性があります。

この攻撃に対処できる緩和策として、アイドル状態遷移のタイミングにランダムな遅延を追加する対応が検討されましたが、次の理由で却下されました。

・イベントを少しでも遅らせると、メッセージが正しいデバイスに確実に配信されることを期待するチャットアプリでの本APIの有用性が大幅に低下する。
・最大30秒まで大きく遅らせた場合でも、長期間にわたってデータを収集することでユーザの識別が可能である。


### Combined with Notification Permission

新しい`idle-detection`権限を追加するのではなく、既存の`notifications`権限の定義を拡張することで、本機能へのアクセスを許可します。
このAPIの最も説得力ある使用例はメッセージングアプリなので、`notifications`権限の使用は適切であると思われます。

新しい権限を追加しないことの利点は、同意疲れの回避です。
同意疲れとは、OS・ブラウザ・Webサイトがユーザに提示する選択肢が際限なく増え続けることです。
既存の`notifications`権限を使うことで、権限をユーザが制御しつつ、必要な決定の数を減らすことができます。

新しい権限を追加しないことの問題点は、ユーザが過去に下した権限決定の意味がかわってしまうことです。
この状態を監視するAPIは、ユーザが`notifications`権限を付与した際に考慮していなかった可能性があります。


## Prior Work

本提案の直接のインスピレーションとなった[chrome.idle](https://developer.chrome.com/apps/idle)。
このAPIには、グローバルな閾値が存在します。
このため、同一ページ上のふたつのコンポーネントで異なる閾値を設定することが困難です。

JavaScriptでの再現を試みた[Dropbox/idle.ts](https://github.com/dropbox/idle.ts)。


# 感想

完璧なクロスサイトトラッキング機能であり、非常に優秀なフィンガープリント手段となりえます。
さらに、いつ離席するかを継続して調査することにより、外出や食事などユーザの行動パターンまでをも読み取られる可能性があります。
あと、どう考えても席を外したらマイニングしますよね。

プロポーザルに挙げられているゴミみたいなメリットだけでは、莫大すぎるデメリットを打ち消すにはあまりに力不足です。
Appleは[論外すぎるからもう話すことはないわ](https://lists.webkit.org/pipermail/webkit-dev/2020-October/031565.html)と議論を打ち切り、またMozillaは[ユーザの同意は安全策ではない](https://mozilla.github.io/standards-positions/)と権限通知をセキュリティ対策と見なしていません。
ということで[Firefox](https://github.com/mozilla/standards-positions/issues/453#issuecomment-888225596)・[Safari](https://lists.webkit.org/pipermail/webkit-dev/2020-October/031565.html)ともに当然ながら完全否定であり、現在の仕様で他のブラウザにIdle Detection APIが実装される見込みは一切ありません。

結果として、Idle Detection APIは[Chrome94という相当古いバージョンから実装されている](https://caniuse.com/?search=Idle%20Detection)にも関わらず、誰にも使われない、そもそも誰も知らない謎機能となっています。

はい、この機能、実はChromeでは2021年から使用可能になっています。
でも実際に使ってみたとか活用しているとかの話を一切見たことがないですよね。
いったいどういうことなのでしょうか。

そしてもちろん言うまでもなくこのAPIはGoogleによる提案であり、そして提案責任者[Reilly Grant](https://github.com/reillyeon)は[Serial Terminal](https://github.com/GoogleChromeLabs/serial-terminal)・[Web Bluetooth](https://github.com/WebBluetoothCG/web-bluetooth)・[WebUSB](https://github.com/WICG/webusb)・[Isolated Web Apps](https://github.com/WICG/isolated-web-apps)といったセキュリティ・プライバシーの全てを投げ捨てたゴミAPIばかりを量産しているという、つまりはまあそういうアレです。


# 設定で拒否する

プライバシーとセキュリティ → サイトの設定 → その他の権限 → デバイスのアクティブ状態から拒否することができます。

![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/e635beff-fac1-85aa-5a17-ca691a219dd8.png)

Idle Detection APIを拒否することによる不利益は一切存在しないので、必ず拒否しましょう。

![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/26088/2f24fd8c-2865-1b77-bbeb-4e50d391918a.png)