v0.10.0 — Tracked Link Intro & Reward Templates
✨ New Features
Tracked Link Intro Template
キャンペーン流入直後(友だち追加 → form push 直前)に届くメッセージを tracked_links.intro_template_id で差し替え可能に。
message_templatesの id を tracked_link に紐付ける- テンプレ内ボタン URL に `{formUrl}` を入れると送信時に実 LIFF フォーム URL に置換
- `{formUrl}` 不在テンプレや壊れた Flex JSON は安全のためデフォルト Flex にフォールバック
- `/r/{ref}?form=FORM_ID` 経由のキャンペーン流入時のみ発火
Tracked Link Reward Template
フォーム送信 + verify 通過後の特典メッセージも tracked_links.reward_template_id で差し替え可能に。
- `friend.first_tracked_link_id` に最初のキャンペーン帰属を pin(一度セットされたら不変)
- reward テンプレ内 `{displayName}` を friend 表示名に置換(JSON-escape 済みなので Flex でも壊れない)
- 1 つのフォームを N キャンペーンで再利用するには キャンペーン毎に別フォーム が必須
OAuth State 拡張
`gate` / `xh` を OAuth state に含めて form push URL で復元。既存フォームを複数キャンペーンで再利用しても、各キャンペーンが正しい gate に verify される。古い「フォームの webhook URL に焼き付き gate」依存を解消。
Security Hardening
- `/api/liff/send-form-link` で idToken 必須化 — 既存友だちが他人の lineUserId を spoof して別キャンペーンの reward を奪取する攻撃を遮断
- Flex テンプレ内の display name 置換を JSON-escape して JSON.parse 破壊を防止
- `xh:` プレフィックス付き ref を form push URL から除外
- `tracked_links.id` を信頼境界として扱う変更(client が `?ref=` を書き換えても reward は変わらない)
🗄️ Migrations
- `020_tracked_link_intro.sql` — `tracked_links.intro_template_id` 追加
- `021_tracked_link_reward.sql` — `tracked_links.reward_template_id` 追加
- `022_friend_first_tracked_link.sql` — `friends.first_tracked_link_id` 追加
📦 npm packages
🔗 Companion Release
- x-harness v0.4.0 — UI for selecting intro/reward templates per campaign