Stealth X は、X(Twitter)で自分のアカウント情報をローカルでマスクする Chrome 拡張機能です。
職場や共有スペースで X を開くときに、肩越しに見られても自分のアカウントが特定されにくいようにします。
特徴:
- ローカル表示のマスキングのみ(投稿・ログイン・API 通信に影響なし)
- 外部送信・データ収集なし
- 権限最小化(storage と x.com/twitter.com の content script のみ)
- 認証情報・Cookie を扱わない設計
バージョン: 1.1.1 ライセンス: MIT
strict: trueで厳格な型チェックnoImplicitAny: true- インターフェース名は
Iプリフィックスなし(例:MaskSettings)
- 関数・変数:
camelCase - 定数・マジック値:
SCREAMING_SNAKE_CASE - DOM セレクタ定数:
{TARGET}_SELECTORS(例:AVATAR_CONTAINER_SELECTORS) - プライベート関数:
_camelCaseプリフィックス
- 「何をしているか」ではなく「なぜ」を書く
- 複雑な正規表現には説明コメント必須
- 1行以内に収める
| 要素 | 技術 | 備考 |
|---|---|---|
| フレームワーク | Chrome Manifest V3 | 最新の Chrome 拡張仕様 |
| 言語 | TypeScript 5.8.3 | strict mode, ES2022 target |
| バンドラー | esbuild 0.25.4 | 高速ビルド、watch mode サポート |
| テスト | Vitest 3.1.3 + jsdom 26.1.0 | DOM テスト環境 |
| 型チェック | tsc --noEmit | ビルド前の検証 |
stealth-x/
├── src/
│ ├── content/ # Content script(DOM マスキング実装)
│ │ ├── index.ts # entry point: MutationObserver, 初期化
│ │ ├── masking.ts # マスキング関数群(1190行の大規模ロジック)
│ │ └── masking.test.ts
│ ├── popup/ # Popup UI(ON/OFF トグル、簡易設定)
│ │ └── index.ts
│ ├── options/ # Options ページ(細かい設定)
│ │ └── index.ts
│ └── shared/ # 共有ロジック
│ ├── settings.ts # 設定型定義、Chrome Storage API ラッパー
│ └── settings.test.ts
├── static/ # 静的ファイル(manifest.json, HTML, 画像)
│ ├── manifest.json # Chrome 拡張マニフェスト
│ ├── popup.html
│ ├── options.html
│ └── avatar-placeholder.png
├── scripts/
│ └── build.mjs # esbuild ビルドスクリプト(watch mode 対応)
├── dist/ # ビルド出力(.gitignore)
└── package.json, tsconfig.json, etc.
interface MaskSettings {
enabled: boolean;
mode: "alias";
maskDisplayName: boolean;
maskUsername: boolean;
maskAvatar: boolean;
maskBanner: boolean;
maskBio: boolean;
maskStats: boolean;
maskLocation: boolean;
maskJoinDate: boolean;
maskPostCount: boolean;
}- Chrome Storage API(sync)で複数デバイス間に同期
DEFAULT_SETTINGSで全機能有効がデフォルトnormalizeSettings()で部分更新と型統一- content script と popup が同じ型を共有
セレクタベースの DOM マスキング:
- テキスト置換: 元の値を
data-stealth-x-original-textに保存し、表示を「非表示」に置換 - ブロック隠蔽:
data-stealth-x-blockattribute で::after疑似要素の灰色バーを表示 - アバター・メディア隠蔽: 専用 attribute(
data-stealth-x-avatar,data-stealth-x-media)で画像を置換
詳細は masking.ts の定数セクション(*_SELECTORS, createStyles())を参照。
resolveCurrentAccount() で複数の方法でハンドルを検出:左下の SideNav_AccountSwitcher_Button から抽出 → @username パターンマッチング → avatar container の data-testid → リンク href。IMPORTANT: マスキング適用前に必ず elementBelongsToCurrentUser() で確認。
requestAnimationFrame + Set でバッチ処理:DOM 変更を MutationObserver で拾い、pendingRoots に追加。フレーム毎に一度だけ applyMasking() を実行し、複数更新をまとめる。
npm install
npm run check # 型チェック(CI で実行)
npm test # テスト実行
npm run build # 本番ビルド(dist/ 出力)
npm run dev # watch mode(開発時)IMPORTANT: Chrome は dist/ を読み込むため、ソースを編集しただけでは拡張に反映されない。以下に該当する変更をしたら、作業完了前に必ず npm run build を実行して dist/ を更新する:
src/content/**(content script の DOM マスキング)src/popup/**/src/options/**(UI ロジック)src/shared/**(設定・共有型)static/**(manifest.json、HTML、画像)
例外(ビルド不要):
*.test.tsのみの変更(テストはnpm testで検証)CLAUDE.md/README.mdなどのドキュメントscripts/,tsconfig.json,package.jsonなどビルド成果物に含まれない設定類
npm run dev を起動中であれば watch mode が自動再ビルドするため npm run build は不要。それ以外のタイミングでは、検証(型チェック・テスト)の後に npm run build を実行し、Chrome の拡張管理画面で再読み込みを促す。
dist/を削除(初期化)- esbuild で 3 つの entry point をバンドル:
content.js- content scriptpopup.js- popup UIoptions.js- options UI(entry point 確認)
static/をdist/にコピー(manifest.json, HTML など)- watch mode: ソース変更時に自動再構築
esbuild 設定:
- Format: IIFE(ブラウザ環境)
- Target: chrome120
- Platform: browser
- Bundle: true(依存関係を埋め込み)
- Minify: false(読みやすさ重視)
npm run build- Chrome:
chrome://extensions/を開く - 「デベロッパーモード」有効化
- 「パッケージ化されていない拡張機能を読み込む」→
dist/を選択 - popup または options ページで設定変更
- X.com でマスキング確認
マスキング対象は MaskSettings の各フラグで制御(maskDisplayName, maskUsername, maskAvatar, maskBanner, maskBio, maskStats, maskLocation, maskJoinDate, maskPostCount)。詳細なセレクタと処理は masking.ts の関数群(applyIdentityMask(), applyAvatarMask() 等)を参照。
applyCurrentSettings() で enabled: false の場合、または content script 無効化時:
export function restoreMasking(root: ParentNode = document) {
// 元のテキスト復元
// 属性・クラス削除
// document title 復元
}すべての変更は reversible(非破壊)。
- 外部送信なし - DOM 情報や設定を外部サーバーに送信しない
- 認証情報を扱わない - Cookie, localStorage, JWT token にアクセスしない
- 権限最小化 -
storagepermission とx.com/twitter.comcontent script のみ - ローカル処理のみ - すべてのマスキングは browser 上で実行
- 設定の同期のみ - Chrome Storage API で sync(user 認証下)
- X の DOM 変更に脆弱: data-testid や DOM 構造が変わると selector が壊れる可能性 → 複数化・fallback で緩和
- 画面共有・スクリーンショット対策なし - ローカル表示隠蔽のみ(browser 拡張の能力範囲外)
- document.title のマスキング - ブラウザタブには表示(プロフィールページ時のみマスク)
- 非同期初期化 - content script と popup の初期化タイミング →
chrome.storage.onChangedlistener で常に最新状態を保つ
npm test # Vitest で全テスト実行(jsdom 環境)
npm run check # TypeScript 型チェック実装済みテスト: src/content/masking.test.ts, src/shared/settings.test.ts
マスキング対象追加:
masking.tsにapply{Feature}Mask()関数実装applyMasking()の末尾に呼び出し追加masking.test.tsに単体テスト(jsdom + mock account)settings.tsに設定フラグ追加、DEFAULT_SETTINGS更新popup/index.tsに UI チェックボックス追加- 実機テスト(X.com で動作確認)
例: プロフィールの「Web サイト」情報をマスク
-
セレクタ定義 (masking.ts 冒頭)
const WEBSITE_SELECTORS = ['[data-testid="UserWebsite"]'];
-
マスキング関数実装
function applyWebsiteMask( root: ParentNode, settings: MaskSettings, account: CurrentAccount ) { if (!settings.maskWebsite) return; for (const element of collectMatches<HTMLElement>(root, WEBSITE_SELECTORS)) { if (!elementBelongsToCurrentUser(element, account)) continue; concealBlock(element, "ウェブサイト非表示"); } }
-
設定に追加 (shared/settings.ts)
interface MaskSettings { // ... maskWebsite: boolean; } const DEFAULT_SETTINGS: MaskSettings = { // ... maskWebsite: true, };
-
popup UI に追加
<input type="checkbox" id="maskWebsite" /> <label for="maskWebsite">ウェブサイトをマスク</label>
-
テスト追加 (masking.test.ts)
-
applyMasking() に呼び出し追加
X の DOM が変わった場合:
- Chrome DevTools で該当要素を inspect
data-testidや class を確認SELECTOR_NAME配列に新しい selector を追加- 既存 selector コメントアウト(後で削除)
- テスト実行・動作確認
観察: マスキング処理が重い、X の操作が遅くなった
- Chrome DevTools > Performance タブで record
requestAnimationFrameのコールスタック確認pendingRootsの size が大きくないか確認collectMatches()で selector の絞り込み検討getTextNodes()の TreeWalker が過度に走っていないか確認
- ローカルで
npm run devで watch 開始 - 修正実装 →
npm test→npm run checkで検証 - Chrome で拡張を reload → X.com で動作確認
- commit & main への push
本拡張機能はプライバシー保護目的の正当な用途を想定しています。
- ✅ 職場での閲覧時のプライバシー保護
- ✅ 共有スペースでの利用
- ✅ 肩越し盗み見対策
悪用を想定した機能追加(例: 他人の認証情報窃取、トラッキング)は追加しません。