このPCで開いている 任意のウィンドウを 1 つだけ 選び、URL を発行して、離れた第三者が ブラウザだけ・アカウント不要 で一時的に閲覧/操作できるアプリです。 デスクトップ全体は決して共有されません(キャプチャ元に画面全体を一切列挙しない設計)。
remote_app/
├─ host/ Electron 製ホストアプリ(このPCで動かす)
│ ├─ main.js ウィンドウ列挙・キャプチャ・入力注入の橋渡し・サーバ自動起動
│ ├─ input.js nut.js による入力注入(座標クランプ+危険キーのブロック)
│ ├─ preload.js レンダラへ最小APIを公開
│ └─ renderer/ ウィンドウ選択UI・WebRTC offer 側
└─ server/ シグナリング+中継+ビューア配信(Node)
├─ server.js
└─ public/ ブラウザ用ビューア(WebRTC answer 側・入力送出)
- 映像: WebRTC で配信 / 遠隔入力: DataChannel で送信
- アクセス制御(既定): URLトークン + 有効期限(30分) + ホスト承認
- Windows 10/11
- Node.js 18+(確認: v24 系)
# リポジトリ直下で(host と server の依存を両方入れます)
npm install入力注入(遠隔操作)について: 多くの場合
npm installだけで有効になります(Electron 42 + nut.js 4 で動作確認済み)。起動時にホスト画面へ「⚠ 入力注入が無効(閲覧のみ)」と出た場合のみ、 ネイティブモジュールを Electron 用に再ビルドしてください:npm run rebuild再ビルドには Windows の C++ ビルドツール(Visual Studio Build Tools 等)が必要なことがあります。 再ビルドしない場合でも起動はでき、その場合は 閲覧のみ(操作不可)になります。
npm start- ホストアプリが起動し、内部でシグナリングサーバ(既定
:8443)も自動起動します。 - ウィンドウを 1 つ選ぶと 共有URL が表示されます。
- 第三者はそのURLをブラウザで開くだけ。ホスト側に「許可しますか?」が出たら 許可 で開始。
- 同一LAN内ならこのまま動作します(URLは
http://<このPCのLAN IP>:8443/s/...)。
このPCが 公開IP を持ち、ルーターの UPnP が有効 なら、追加設定なしでインターネット公開できます。
npm start 時にアプリが UPnP で Web ポート(TCP 8443)を自動開放 し、公開IP付きの共有URL
(http://<公開IP>:8443/s/...)を発行します。第三者は ブラウザでそのURLを開くだけ(インストール不要)。
npm start
# 起動ログに「[host] インターネット公開: http://<公開IP>:8443 ...」が出れば成功- 公開したくない(同一LAN内だけ):
$env:PASSIST_PUBLIC="0"; npm start - UPnP が無効/多段NATなら、自動で 同一LAN内のみ にフォールバックします(ログに警告)。
- 映像/操作は WebRTC で P2P(STUN)。厳しい NAT の相手とは繋がらないことがあり、その場合は
TURN サーバ(coturn 等) を
host/renderer/renderer.js・server/public/viewer.jsのiceServersに追加してください(多人数運用なら中継サーバ集約型を推奨)。
http://<公開IP> のままでも 映像・操作は常に暗号化(WebRTC DTLS-SRTP)されます。ページ配信とシグナリング
(トークン/PIN/信頼secret)も暗号化したい場合は次のいずれか:
- Cloudflare トンネル(最も簡単・無料の正規証明書):
cloudflared tunnel --url http://localhost:8443 # 得られた https://xxxx.trycloudflare.com を、ホスト画面「外部公開URL」欄に入れて再発行 # (この場合は UPnP もポート開けも不要。$env:PASSIST_PUBLIC="0" でOK)
- 独自証明書:
TLS_CERT/TLS_KEYを渡すとサーバが https/wss で待ち受け (生IPの証明書は取りにくいので、独自ドメイン+Let's Encrypt 推奨):$env:TLS_CERT="C:\path\cert.pem"; $env:TLS_KEY="C:\path\key.pem"; npm start
| 要件 | 実装 |
|---|---|
| ウィンドウだけ・デスクトップ全体は出さない | キャプチャ元に screen を一切列挙せず window のみ。選択IDに固定。 |
| アカウント不要 | ビューアはURLのトークンのみで接続。ログインなし。 |
| 一時利用 | セッションに有効期限(既定30分)。期限切れで自動失効。 |
| 勝手に繋がせない | 既定でホスト承認制。ホストが「許可」するまで映像は流れない。 |
| 操作範囲をウィンドウ内に限定 | 入力座標を対象ウィンドウ矩形にクランプ。 |
| デスクトップへ脱出させない | Win / Alt+Tab / Alt+F4 / Ctrl+Esc / タスクマネージャ 等をブロック。 |
approve(既定): ホスト承認制pin: 6桁ワンタイムPINを併用(ホスト画面に表示、口頭等で共有)token: URLを知っていれば即接続(最も手軽・拡散注意)
$env:ACCESS_MODE = "pin"; npm startその他: PORT(既定8443) / SESSION_TTL_MS(既定1800000) / PUBLIC_BASE_URL / REMOTE_SERVER_URL。
「一度認証した相手」や「事前に許可した相手」は、ホストが毎回「許可」を押さなくても自動接続できます。
アカウントは不要で、端末ごとの信頼クレデンシャル(clientId+secret)で実現します。
- 方法A: 承認時に信頼する … 承認ダイアログの「この端末を信頼」にチェックして許可。 以後その端末(ブラウザ)は承認なしで接続できます。
- 方法B: 事前に信頼リンクを発行 … ホスト画面の「信頼リンクを発行(事前承認)」で、
クレデンシャル入りURL(
…/s/<token>#k=<clientId>.<secret>)を発行して相手に渡します。 相手は初回から承認不要で接続できます。
仕組み: 信頼クレデンシャルはホスト側(userData/passist-trust.json、secret はハッシュで保存)にのみ
永続化されます。ビューアは secret を localStorage に保存し、接続時に提示。ホストが照合して一致すれば
ダイアログを出さずに自動承認します。secret 平文はビューア↔ホスト間でシグナリング経由で渡るため、
インターネット越しで信頼機能を使うなら https/wss(Cloudflareトンネル等)を推奨です。信頼の取り消しはホスト画面の「すべて解除」から。
# 信頼ロジックの単体テスト(ネットワーク不要)
npm test
# シグナリング/中継の結合テスト(先に approve モードでサーバ起動が必要)
# ターミナル1:
$env:PORT="8990"; $env:ACCESS_MODE="approve"; node server/server.js
# ターミナル2:
$env:TEST_PORT="8990"; npm run test:signaling- 入力注入は OS レベルの実入力です(ホスト側の実カーソルが動きます)。対象ウィンドウが
他ウィンドウに隠れていると、重なった別ウィンドウに入力が当たる可能性があります。
対策として操作開始時に対象ウィンドウを自動で前面化します(nut.js の
focus())。 ただしホスト側で別ウィンドウを手前にすると入力がずれるため、操作中は触らない運用を推奨。 - ウィンドウの特定は HWND(ウィンドウハンドル)で厳密一致します(
desktopCapturerのwindow:<HWND>:0を利用)。同名ウィンドウが複数あっても取り違えず、タイトルの文字コードにも 依存しません(取得できない場合のみタイトル一致にフォールバック)。 - 同時接続は1セッション1ビューア。
- WebRTC のため厳しい NAT では TURN 必須。