Skip to content

Latest commit

 

History

History
358 lines (260 loc) · 11.5 KB

File metadata and controls

358 lines (260 loc) · 11.5 KB

X Stream Saver — 仕様書(Markdown / リポジトリ格納用)

文書番号:XSS-SPEC-001
版:1.0
更新日:2025-12-12(JST)
対象:Google Chrome(Manifest V3)


0. 概要

X(旧Twitter)のタイムラインは SPA(無限スクロール)であり、意図しない更新・リロード・スクロールにより「さっき見ていたツイート/画像」が流れて見失われることがある。

本拡張は、X 上でユーザーが 実際に viewport に表示(視認)したツイート/画像(将来:動画)を短期ログとしてローカルに保存し、Side Panel / Feed UI で「自分専用の履歴フィード」として再表示する。


1. 目的

  • 直近 TTL=10分 の範囲で、視認済みのツイート/メディアのURL・メタ情報を保存する
  • 拡張UIで 新しい順 にカード表示し、フィルタ(All/Tweets/Media)を提供する
  • データは ローカルのみ(外部送信なし)
  • DOM変化に強いセレクタ戦略(data-testid)と、パフォーマンス劣化しない収集方式(バッチ送信)を採用する

2. スコープ

2.1 MVP(本仕様の対象)

  • 収集対象:tweet / image / video(video は当面「存在判定+tweetUrl」のみでも可)
  • ストレージ:chrome.storage.local
  • UI:Side Panel(推奨)+ feed.html(同一UIを再利用可能)

2.2 Out of Scope(将来拡張)

  • 動画の実体URL・サムネ抽出とプレビュー
  • ピン留め(TTLを超えた永続保存領域)
  • JSON/CSVエクスポート
  • 仮想スクロール(500件を超える運用)
  • ツイート内複数画像の「グルーピング表示」

3. 対象環境・前提

  • Chrome 拡張:Manifest V3
  • 対象ドメイン:https://x.com/*, https://twitter.com/*
  • X は SPA / DOM差し替えが頻繁(クラス名依存は避ける)
  • 本拡張はユーザーの閲覧体験を阻害しない(軽量動作)

4. コンポーネント構成

4.1 Content Script(src/content.js

責務:

  • DOM 監視(MutationObserver)で新規ツイート要素を検出
  • 視認判定(IntersectionObserver)で「見えた」タイミングのみ抽出
  • data-testid を起点にツイートURL・本文・画像URL・動画存在を抽出
  • 収集結果をバッチ化して Background に送信

4.2 Background / Service Worker(src/background.js

責務:

  • Content Script から LOG_BATCH を受信
  • 書き込み競合防止(Promiseキュー / Mutex)
  • 既存データとマージ(重複排除、seenAt更新、フィールド単位で情報劣化防止)
  • TTL期限切れ削除・上限500件のカット
  • chrome.alarms による定期クリーンアップ(再起動耐性あり)
  • Side Panel 起動制御(推奨:openPanelOnActionClick)

4.3 Feed UI(feed/feed.html, feed/feed.js

責務:

  • chrome.storage.localfeed_log を読み込み、カード形式で表示
  • フィルタ(All/Tweets/Media)
  • chrome.storage.onChanged によるリアルタイム反映
  • DOM API による安全なレンダリング(innerHTML 排除)

5. データフロー

  1. Content Script が DOM 変化を監視し、ツイート要素を監視対象に追加
  2. IntersectionObserver が閾値を満たした要素を「視認」と判定
  3. Content Script が LogItem を生成し、内部キューに蓄積
  4. 一定周期/件数で LOG_BATCH として Background に送信
  5. Background が feed_log を取得 → マージ → TTL/上限適用 → 保存
  6. Feed UI が storage.onChanged を受けて再描画(または手動Reload)

6. 画面・遷移(テキスト図)

6.1 画面構成(Side Panel / Feed 共通)

  • Header
    • Filter: All / Tweets / Media
    • Reload(任意)
  • Feed List
    • Card(tweet/image/video)
  • Empty State
    • 「No items found in last 10 mins.」

6.2 画面遷移図(ASCII)


+---------------------+
|  X (x.com) tab      |
|  (scroll/refresh)   |
+----------+----------+
|
| (視認ログが貯まる)
v
+---------------------+      action click / Side Panel open
| Background (SW)     |-------------------------------------+
| merge + TTL + cap   |                                     |
+----------+----------+                                     v
|                                      +-------------------+
| storage.local feed_log update         | Side Panel UI     |
| (array)                             | feed.html/feed.js  |
+---------------------+                            +---------+---------+
|                                      |
|                                      |<-------------------------------------+
+---------------------+         storage.onChanged


7. セレクタ戦略(data-testid)

  • Tweet container:[data-testid="tweet"]
  • Tweet text:[data-testid="tweetText"]
  • Photo wrapper:[data-testid="tweetPhoto"]
  • Video player:[data-testid="videoPlayer"]

注記:

  • クラス名はビルドごとに変動し得るため依存しない
  • data-testid の変更リスクはゼロではないため、定数へ集約し保守点を局所化する

8. ログデータ仕様

8.1 LogItem スキーマ(MVP)

type LogItem = {
  key: string;                 // dedupe キー(例:tweet:123..., image:https://pbs...)
  type: 'tweet'|'image'|'video';
  url: string;                 // typeごとの対象URL(tweetはtweetUrlでも可)
  tweetUrl?: string;           // 元ツイートURL
  text?: string;               // 取得できる範囲で(空可)
  author?: string;             // 取得できる範囲で(空可)
  timeISO?: string;            // time[datetime]等(空可)
  seenAt: number;              // epoch ms(最後に見た時刻)
  source?: { pageUrl?: string };
};

8.2 chrome.storage.local 保存形式

  • キー:feed_log

  • 値:LogItem[]新しい順にソート済み)

  • 制約:

    • TTL:now - seenAt < TTL_MS
    • 上限:MAX_ITEMS

9. 設定値一覧(CONFIG表)

9.1 Background(src/background.js

Key Default 意味 影響
TTL_MS 600,000 (10分) ログ保持期間 長いほどノイズ増、短いほど“取りこぼし”
MAX_ITEMS 500 最大保存件数 UI描画/容量の上限
STORAGE_KEY feed_log 保存キー UI/Background で共通
CLEANUP_ALARM cleanup アラーム名 定期削除の識別

9.2 Content(推奨パラメータ:実装側に存在)

Key Default 意味 影響
IO_THRESHOLD 0.6 視認判定閾値 低いほど誤検知増、高いほど取りこぼし
FLUSH_INTERVAL_MS 2000 バッチ送信間隔 小さいほどリアルタイムだが通信増
FLUSH_MAX_ITEMS 10 送信トリガ件数 小さいほど小刻み、大きいほど遅延
KEY_COOLDOWN_MS 30000 同一キー再送抑止 小さいほど再送増、大きいほど更新抑制

10. 通信仕様(Content → Background)

10.1 メッセージ種別

  • LOG_BATCH

10.2 ペイロード

{
  "type": "LOG_BATCH",
  "items": [
    {
      "key": "tweet:1234567890",
      "type": "tweet",
      "url": "https://x.com/user/status/1234567890",
      "tweetUrl": "https://x.com/user/status/1234567890",
      "text": "sample text",
      "author": "@user",
      "timeISO": "2025-12-12T01:23:45.000Z",
      "seenAt": 1765500000000,
      "source": { "pageUrl": "https://x.com/home" }
    }
  ]
}

11. Background 仕様(保存・マージ・クリーンアップ)

11.1 競合防止(書き込みキュー)

  • chrome.storage.localget → set を複数バッチで並列実行すると取りこぼしが発生し得る
  • 対策:Promise キューで processBatch() を直列化する

11.2 マージ仕様(フィールド単位)

  • 同一キーの既存 prev と新規 next がある場合:

    • seenAtmax(prev.seenAt, next.seenAt)
    • author/text/timeISO/tweetUrl/url/typenext が空でない時のみ上書き(情報劣化を防止)

11.3 TTL・上限

  • 保存時に必ず TTL フィルタと上限カットを実施(cleanup 依存禁止)

11.4 定期クリーンアップ(Alarm)

  • 1分ごとに期限切れを削除

  • SW再起動耐性:

    • 起動時に ensureCleanupAlarm() を呼び、存在しなければ作成

12. Feed UI 仕様(表示・リアクティブ更新)

12.1 フィルタ要件

  • All:全件
  • Tweets:type === 'tweet'
  • Media:type === 'image' || type === 'video'

12.2 リアルタイム更新

  • chrome.storage.onChanged を監視し、feed_log 更新時にデバウンス(例:100ms)して再描画

12.3 セキュリティ

  • DOM API で安全にレンダリングし、innerHTML を使用しない
  • 外部リンクは rel="noopener noreferrer"

12.4 画像表示最適化

  • loading="lazy", decoding="async"
  • referrerPolicy="no-referrer"

13. 画像キー正規化(Optional)

課題:

  • pbs.twimg.comname=small/large 等のパラメータ揺れがあり、同一画像が重複する可能性がある。

方針(推奨):

  • 表示URLは変更しない
  • 重複排除キーのみ正規化(例:name を削除)

適用箇所:

  • Content 側の key 生成時、または Background の processBatch() 前処理で key を補正

14. サンプルデータ(feed_log)

[
  {
    "key": "tweet:1234567890",
    "type": "tweet",
    "url": "https://x.com/user/status/1234567890",
    "tweetUrl": "https://x.com/user/status/1234567890",
    "text": "This is a sample tweet text.",
    "author": "@user",
    "timeISO": "2025-12-12T01:23:45.000Z",
    "seenAt": 1765500000000,
    "source": { "pageUrl": "https://x.com/home" }
  },
  {
    "key": "image:https://pbs.twimg.com/media/ABCDEF12345?format=jpg",
    "type": "image",
    "url": "https://pbs.twimg.com/media/ABCDEF12345?format=jpg&name=large",
    "tweetUrl": "https://x.com/user/status/1234567890",
    "seenAt": 1765500000123,
    "source": { "pageUrl": "https://x.com/home" }
  }
]

15. 受入基準(DoD)

  • X 上でスクロールすると、直近10分の tweet/image が feed_log に保存される
  • Feed UI に All/Tweets/Media のフィルタがあり、内容が正しく切り替わる
  • 期限切れ(10分超)データが削除される(保存時・定期cleanupの両方で担保)
  • 上限 500 件を超えない
  • 高速スクロールでも体感劣化がない(バッチ送信が機能)
  • UI は innerHTML を使用せず、外部リンクは noopener を付与

16. リポジトリ構成例

x-stream-saver/
  manifest.json
  src/
    content.js
    background.js
  feed/
    feed.html
    feed.js
    feed.css   (任意:CSS分離する場合)
  README.md    (任意:インストール手順)

17. 実装メモ(決定事項)

  • ストレージ:chrome.storage.local
  • TTL:10分
  • 上限:500件
  • セレクタ:data-testid 基準
  • 通信:LOG_BATCH(バッチ送信)
  • UI:Side Panel を基本(feed.html を default_path に設定)
  • Background:Promiseキューで競合防止、フィールド単位マージ、Alarm 再起動耐性