Releases: Shudesu/line-harness-oss
Release list
v0.17.0
What's Changed
- docs: README に ASP 機能を追記 (5言語) by @Shudesu in #204
- sync: v0.17.0 — per-account /t LIFF resolution + link-tracking toggle by @Shudesu in #205
- sync: v0.17.0 — installer/self-update overhaul by @Shudesu in #207
- ci(release): build the worker artifact with wrangler + add worker_bundle_hash by @Shudesu in #206
Full Changelog: v0.16.0...v0.17.0
v0.16.0
v0.16.0 — アフィリエイト計測(ASP)/ Affiliate Tracking
🇯🇵 日本語
✨ 新機能: アフィリエイト計測(ASP)
「クリック → 友だち追加 → フォーム/予約 → 決済」を時系列で追跡し、last-touch(90日窓)で成果を帰属するセルフサーブ型アフィリエイト機能を搭載しました。
- セルフサーブリンク発行 — アフィリエイターは LIFF(
?page=affiliate)からワンタップで登録・リンク発行。6文字乱数 slug、媒体別ラベル、上限20本。管理画面からも友だち検索で作成可能(コードはサーバ側乱数、友だちと1:1バインド) - 案件(オファー) — 1成約あたり固定額報酬の案件を定義。アフィリエイターは案件単位で参加し、案件専用リンクを取得。案件に設定したタグ/シナリオは流入時に自動適用(停止した案件はフロー停止、計測は継続)
- last-touch 帰属 — CV 発生時に直近90日の最終アフィリタッチをスナップショット保存(自己クリック除外)。全タッチ履歴は時系列で保存され、後から検証可能
- 成果承認フロー — 帰属 CV は「審査中」で発生 → 管理画面で承認/却下。確定報酬 = 承認済み件数 × 固定額。identity key による重複(水増し)フラグ付き
- 成果 push 通知 — 紹介経由の新規友だち追加・成果承認をアフィリエイター本人に LINE で通知(二重送信は DB レベルで防止)
- ジャーニー API — 友だち単位・アフィリエイター単位の時系列イベント(タッチ/友だち追加/フォーム/CV)
- 短縮ドメイン対応 —
LINK_BASE_URL設定 + Redirect Rule でhttps://your-domain/xxxxxx形式の配布リンク - 管理画面は
/affiliates1ページ3タブ(アフィリエイター/案件/成果承認)に統合
ガイド: docs/wiki/27-Affiliate-ASP.md
✨ 新機能: iOS 代理予約
- 管理者による代理予約 + 空き枠 API(iOS アプリ連携用)
🚀 改善: チャット / 受信箱
- チャット一覧を約8倍高速化(本番実測 3,473ms → 435ms)+ カーソルページング
- 未対応バッジ改善 — 「解決済」で即減少(新着で自動復帰)、返信直後に即時更新、画像/スタンプ受信でも未読化
- MCP
list_conversationsにも同じ判定を適用(管理画面との乖離解消)
🔧 修正
- 予約リマインドを重いジョブより先に実行(cron 飢餓による送り漏れ防止)
/o共有 URL のリンクプレビュー(OGP)対応- update バナー: 自前 CI/CD ビルドでは非表示、文言改善、手動更新ガイド(26-Manual-Update.md)新設
/updatesページの Failed to fetch 解消
🗄 データベース
- migration 046 / 047(いずれも追加専用、047 は既存帰属 CV の backfill 含む)
🇬🇧 English
✨ New: Affiliate tracking (ASP)
Self-serve affiliate tracking with a full funnel timeline (click → friend add → form/booking → payment) and last-touch attribution over a 90-day window.
- Self-serve links — affiliates register and issue links from LIFF (
?page=affiliate): 6-char random slugs, per-channel labels, up to 20 links. Admins can also create affiliates from the dashboard (server-generated random codes, 1:1 friend binding). - Offers — define campaigns with a fixed reward per conversion. Affiliates enroll per offer and receive offer-specific links; offer tags/scenarios are applied automatically on inflow (paused offers stop the flow while measurement continues).
- Last-touch attribution — the latest affiliate touch within 90 days is snapshotted at conversion time (self-clicks excluded). The full touch history remains queryable for audits.
- Approval flow — attributed conversions start as pending; approve/reject from the dashboard. Confirmed reward = approved count × fixed amount, with identity-key duplicate flags for fraud review.
- Push notifications — affiliates get a LINE push on new referred friend adds and on approval (double-send prevented at the DB level).
- Journey APIs — chronological events (touch / friend add / form / conversion) per friend and per affiliate, with cursor pagination.
- Short link domain —
LINK_BASE_URLsetting + a redirect rule on your domain yieldshttps://your-domain/xxxxxxlinks. - Admin UI consolidated into a single
/affiliatespage with three tabs (affiliates / offers / approvals).
Guide: docs/wiki/27-Affiliate-ASP.md
✨ New: iOS proxy booking
- Admin proxy booking and availability routes for the iOS app flow.
🚀 Improved: Chat & inbox
- ~8x faster chat list (production: 3,473ms → 435ms) with cursor pagination.
- Unanswered badge — resolved conversations drop off immediately (auto-revive on new inbound), instant refresh after replies, non-text inbound now marks chats unread.
- The MCP
list_conversationstool now applies the same resolved exclusion.
🔧 Fixed
- Booking reminders run before heavy scheduled jobs (prevents cron-starvation misses).
- OGP link previews for
/oshare URLs. - Update banner: hidden for self-hosted CI/CD builds, softer wording, new manual update guide (26-Manual-Update.md).
/updatespage no longer shows "Failed to fetch" on unconfigured environments.
🗄 Database
- Migrations 046 / 047 (both additive; 047 backfills pending status for existing attributed conversions).
What's Changed
- fix: show admin build fingerprint by @aiagentmini-bit in #157
- fix(ci): build update-engine before worker in deploy workflow by @Shudesu in #164
- fix(liff): open-redirect guard on /auth/line ?redirect= by @Shudesu in #163
- feat(ci): bake admin CORS vars into deploy config (survive redeploys) by @Shudesu in #165
- docs: add OSS sandbox merge gate by @Shudesu in #175
- fix(worker): register existing friends on first webhook contact by @Shudesu in #174
- fix(worker): accept SDK rich menu imageData uploads by @Shudesu in #176
- fix(mcp): compare account summary with LINE followers by @Shudesu in #178
- chore(create): publish CLI build with nodejs compat deploy fix by @Shudesu in #180
- fix: honor next_step_on_false jump target + scope comma cleanup to flex by @9kumiko3oka5-jpg in #191
- sync: private → oss (chat inbox fix + chat list perf + reminder ordering) by @Shudesu in #193
- chore: remove internal planning docs by @Shudesu in #194
- sync: update バナー改善 (文言/リンク/dev非表示) + /o OGP 対応 by @Shudesu in #195
- sync: 未構成環境でのアップデート履歴ページ graceful 化 by @Shudesu in #196
- sync: ASP アフィリエイト計測機能 by @Shudesu in #197
- sync: サイドバーにアフィリエイト導線 by @Shudesu in #198
- sync: admin からのアフィリエイター作成 by @Shudesu in #199
- sync: ASP 案件 + 成果承認フロー by @Shudesu in #200
- sync: アフィリ管理3タブ統合 + LIFF 案件中心UI by @Shudesu in #201
- sync: アフィリ成果通知 + MCP resolved 除外 by @Shudesu in #202
- sync: v0.16.0 リリース by @Shudesu in #203
New Contributors
- @9kumiko3oka5-jpg made their first contribution in #191
Full Changelog: v0.15.0...v0.16.0
v0.15.0
What's Changed
- Fix setup Worker deploy for update-engine builds by @aiagentmini-bit in #142
- fix(chat): render LINE sticker messages by @aiagentmini-bit in #153
- fix: move admin auth to production-safe httpOnly cookies by @aiagentmini-bit in #154
- fix(update): proxy manifest fetch through worker by @aiagentmini-bit in #155
- feat: 管理画面更新バナーを無効化できるようにする by @kandotrun in #151
- feat: 流入リンクで自動付与タグを設定できるようにする by @kandotrun in #150
- chore: prepare 0.15.0 release by @aiagentmini-bit in #156
New Contributors
- @kandotrun made their first contribution in #151
Full Changelog: v0.14.1...v0.15.0
v0.14.1
What's Changed
- chore: add OSS community health files by @aiagentmini-bit in #133
- chore(ci): make OSS worker workflow verify only by @aiagentmini-bit in #134
- fix(worker): sync private baseline and assets guard by @aiagentmini-bit in #131
- docs: sync OSS sync charter hardening by @aiagentmini-bit in #135
- fix(api,web): sync account-scoped list behavior by @aiagentmini-bit in #136
- fix(scenario): sync safe step condition evaluation by @aiagentmini-bit in #137
- fix(create-line-harness): sync D1 schema TTY retry by @aiagentmini-bit in #138
- fix: harden dev package metadata and CLI guards by @aiagentmini-bit in #139
- docs: add fork Cloudflare operations flow by @aiagentmini-bit in #140
- sync: publish latest LINE OSS update by @aiagentmini-bit in #141
New Contributors
- @aiagentmini-bit made their first contribution in #133
Full Changelog: v0.14.0...v0.14.1
v0.14.0 — Events Phase 1 / Inflow Links / Webhook DoS hardening
Security — /webhook pre-verify DoS hardening (#104)
公開 /webhook エンドポイントが署名検証より前に body を全 parse していた点を強化。攻撃者が制御可能な巨大/不正 body が JSON.parse に到達することを防ぐ。
- 1 MiB body cap — Content-Length pre-check で先行棄却、post-read は UTF-8 byte length で確認
- 44-char signature pre-check — LINE 署名は HMAC-SHA256 + base64 = 44 chars。長さ違いは D1 lookup / HMAC compute の前に fail-fast
- HMAC verify を
JSON.parseの前に移動 — env secret fast path → multi-account DB lookup の順、検証成功時のみ parse - env-secret fast path 経由でも
matchedAccountIdを解決 — multi-account 互換維持
Features
Events Phase 1 — 多アカウント対応イベント予約
サロン予約とは独立した「イベント」予約システム。多アカウント対応で migration 040 経由でアカウント単位の events / slots / bookings を分離。
- 管理画面: イベント一覧 / エディタ (3 タブ) / slot 一括生成 / bookings 管理 / draft/公開トグル
- LIFF: イベント詳細 / 確認 / 完了 / 履歴ページ
- Cron: 予約リマインダー、期限切れ予約 expire
Inflow Links — 流入元トラッキング
ref code 経由の友だち獲得を可視化する管理画面と main pool autosetup。
/inflow-links一覧 + detail page、main pool fallback 表示- 最新ソート + サイドバーアカウントフィルタ
/affiliatessummary 統合、X-Harness UUID refs テーブル表面化- ref-summary aggregation で accountId filter が正確に narrow
Scenario 配信モード + テンプレート参照
- 配信モード「時刻指定 / 経過時間」を選択可能に
- 作成モーダルに「いつ開始する?」trigger select 追加
- テンプレート参照: 到達率統計 + 到達タグ自動付与(migration 038)
未対応インボックス UX
- MAX_PAGE_SIZE 200 → 2000、長期未対応の取りこぼし防止
- 新着順に反転表示
- UX polish (Codex review 2 ラウンド)
Standalone LIFF App
apps/liff/ を独立 SPA として追加(booking / event-booking / history)。
npm
Migrations
- 037: no-op (hotfix)
- 038: entry routes pool & push / scenario templates & stats
- 039: default main pool
- 040: events multi-account
Diff: v0.11.0..v0.14.0
v0.11.0 — Conversation inbox MCP (list_conversations + get_conversation)
Highlights
放置されている個別返信を MCP から検知・返信できるようにする機能。受信側の可視化が丸ごと欠けていた穴を埋めた。
New MCP tools
list_conversations— 未返信会話一覧。最新のincoming以降に「人間の返信(source='manual')」が無い friend を、放置期間が長い順に返す。broadcast/scenario/auto_reply/reminderによる自動送信は「返信」と見なさないget_conversation— 特定 friend のメッセージ履歴(送受信両方、sourceタグ付き)
返信自体は既存の send_message でOK。
Changes
Database
messages_logにsourceカラムを追加(user/broadcast/scenario/auto_reply/reminder/manual)- 既存データは direction / broadcast_id / scenario_step_id / delivery_type から推定して backfill
- 全
INSERT INTO messages_log箇所(webhook / broadcasts / friends / chats / step-delivery / reminder-delivery / segment-send / services/broadcast)で source を明示
Worker
GET /api/conversations/GET /api/conversations/:friendId- 非テキスト受信(sticker / image / audio / video / file / location)も messages_log に記録
SDK
LineHarness.conversations.list()/.get()- Vitest 4 ケース同梱
Migration
028_messages_log_source.sql:ALTER TABLE + backfill UPDATE + インデックス2本。既存環境では pnpm db:migrate を実行する 前 に必ずこの migration を先に適用すること(schema.sql の CREATE TABLE IF NOT EXISTS は既存テーブルに source カラムを追加しない)。
Known limitations(v0.11.1 で解消予定)
get_conversation の履歴で以下経路の outgoing は取り逃す可能性がある(messages_log 未記録のため):
routes/forms.tsのフォーム送信後 pushroutes/liff.tsの LIFF 経由 pushservices/event-bus.tsの automation reply/pushroutes/meet-callback.tsの Meet 予約確認 push
いずれも「人間の返信」(source='manual')には該当しないため、list_conversations の未返信判定は正確に動作する。
Review
Codex review 3ラウンドで JST 二重オフセット / カーソル比較 / 非テキスト受信 / 秒精度 / legacy test-send 分類 を修正済み。
v0.10.2 — Fix duplicate message delivery from cron race conditions
Critical Bug Fix — Cron duplicate delivery
重大バグ修正 — Cron重複配信
Root Cause / 根本原因
The cron handler (scheduled()) ran delivery jobs once per LINE account in parallel via Promise.allSettled. Since the DB queries had no account filter, all workers fetched and sent the same pending deliveries — resulting in duplicate messages per user.
cronハンドラが配信ジョブをLINEアカウントごとに並列実行していました。DBクエリにアカウントフィルタがなく、全ワーカーが同じ配信対象を取得・送信し、重複メッセージが発生していました。
Fixes / 修正内容
-
Batch-lock deliveries / バッチロック —
getFriendScenariosDueForDelivery()now atomically sets all due records tostatus='delivering'viadb.batch()before returning them. Other cron instances see noactiverecords to pick up.
配信対象を取得と同時に一括でdeliveringステータスに更新。他のcronインスタンスは同じレコードを取得できません。 -
Single-pass delivery / 1回実行 — Delivery jobs now run once per cron, not per-account. Each function dynamically resolves the correct
LineClientfromfriend.line_account_id.
配信ジョブをアカウントループの外に出して1回だけ実行。各関数内でfriendのline_account_idから正しいlineClientを動的解決。 -
Scheduled broadcast lock / ブロードキャスト楽観ロック — Optimistic lock (
UPDATE ... WHERE status='scheduled') prevents parallel sends. Failures reset status toscheduledfor retry. -
Reminder delivery lock / リマインダーロック —
INSERT OR IGNOREonfriend_reminder_deliveriesafter successful send prevents duplicate delivery records. -
UNIQUE constraint / UNIQUE制約 — Partial unique index on
friend_scenarios (friend_id, scenario_id) WHERE status != 'completed'prevents duplicate active enrollments while allowing re-enrollment after completion. -
Enrollment dedup / 登録デデュプ —
enrollFriendInScenario()usesINSERT OR IGNORE, returnsnullif already enrolled. -
Crash recovery / クラッシュリカバリ —
recoverStuckDeliveries()resets records stuck indeliveringfor >5 minutes back toactive.
5分以上deliveringのままのレコードをactiveに戻す復旧処理を追加。 -
Subrequest limit / サブリクエスト制限 —
MAX_SENDS_PER_CRON = 40respects CF Free plan's 50 subrequest limit.
CF無料プランの50サブリクエスト制限に対応(マージン込みで40件/cron実行)。
Migration / マイグレーション
packages/db/migrations/027_dedup_delivery.sql:
- Cleans up duplicate non-completed enrollments (keeps most progressed) / 重複enrollmentを削除(最も進んだものを保持)
- Recreates
friend_scenariostable withdeliveringstatus in CHECK constraint / CHECK制約にdeliveringを追加 - Adds partial UNIQUE index / 部分UNIQUEインデックス追加
Upgrade / アップグレード
Run pending migrations via npx create-line-harness update or wrangler d1 migrations apply.
v0.10.1 — Per-campaign reward attribution
Behavior Change — Per-campaign reward attribution
v0.10.0 では reward template の解決を friends.first_tracked_link_id(最初のキャンペーンに永久 pin)に固定していたため、既存友だちが新しいキャンペーンの tracked link を踏んでも reward が古いキャンペーンのままになる問題がありました。本リリースでこれを反転します。
How it works
- LIFF が
?ref=(= 当該キャンペーンの tracked link id) をフォーム送信時のtrackedLinkIdとして乗せる - サーバー (
apps/worker/src/services/reward-resolver.ts) はtrackedLinkIdを正として優先解決:trackedLinkId指定 + 該当 link が DB に存在 → その link のreward_template_idを採用(NULL の場合はnullを返してフォームのon_submit_message_*に委譲)trackedLinkId不明 / 該当 link なし → 旧来のfirst_tracked_link_idフォールバック(後方互換)
- DB マイグレーションなし、追加カラムなし
- リプレイ防止は意図的に入れない。本機能はオプトイン誘導が目的で、エンゲージメントゲートが上流の真のアンチフラウドを担う想定
v0.10.0 → v0.10.1 の挙動差
| シナリオ | v0.10.0 | v0.10.1 |
|---|---|---|
| 既存友だちが新キャンペーンの link → 同じフォームを再 submit | 古い (first-touch) キャンペーンの reward | 新しいキャンペーンの reward |
新キャンペーンの link で reward_template_id=NULL → submit |
古いキャンペーンの reward が漏れる(バグ) | フォームの on_submit_message_* にフォールバック |
?ref= なし(古いリンク経由) |
first-touch reward | first-touch reward(後方互換) |
Trade-off
v0.10.0 は friends.first_tracked_link_id の 1 回 pin によって URL 改ざんによる reward 奪取を防いでいました。v0.10.1 はその境界を意図的に緩めます — /api/forms/:id/submit の body.trackedLinkId を信用するため、攻撃者が手動でリクエストを書き換えると別キャンペーンの reward を取得し得ます。本プロジェクトの想定利用文脈ではエンゲージメントゲートで担保するため許容しています。
Implementation
apps/worker/src/services/reward-resolver.ts— DB アクセサを引数注入する純粋関数。Vitest で 5 ケース単体テスト同梱apps/worker/src/routes/forms.ts— inline reward 解決をresolveRewardTemplate()呼び出しに置換apps/worker/src/client/form.ts— LIFF が?ref=を読み取り、通常経路と Webhook 経路の両方でtrackedLinkIdを submit body に乗せる
Docs
詳細は docs/wiki/10-Tracked-Links.md の「キャンペーンメッセージ」セクションを参照。
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
v0.9.0
v0.9.0 — Traffic Pool, LIFF マルチアカウント, セキュリティ修正
新機能
- Traffic Pool — 外部URLを変えずにLINEアカウントを即時切替(
/pool/:slug) - LIFF マルチアカウント —
/api/liff/configでBot情報を動的取得(ビルド時のハードコード不要) - Auth Pool 連携 —
/auth/lineがTraffic Poolに対応、?pool=でテスト可能 - 自動返信マルチアカウント —
line_account_idでアカウント別設定 - 配信インサイト — 配信結果のInsight API + 管理画面表示
- フォーム送信後メッセージ — text/flex 形式で送信後カスタムメッセージ
- Postback イベント — Webhook でpostbackイベント処理対応
- メタデータ条件分岐 —
{{#if_metadata.KEY}}...{{/if_metadata.KEY}}テンプレート - wrangler.toml env分離 —
--env productionで本番、デフォルトはテスト環境
セキュリティ修正
/api/liff/send-form-link— idToken検証追加(不正push防止)?form=ショートカット — 友だち追加チェック追加- フォームWebhook — GET/POSTメソッドをサーバー側と統一
- フォームWebhook — クライアント側で
webhookPassedチェック
マイグレーション(v0.8.0 からのアップグレード)
npx wrangler d1 execute <DB_NAME> --remote --file=packages/db/migrations/014_form_submit_message.sql
npx wrangler d1 execute <DB_NAME> --remote --file=packages/db/migrations/015_auto_reply_account.sql
npx wrangler d1 execute <DB_NAME> --remote --file=packages/db/migrations/016_traffic_pools.sqlLIFF セットアップ(マルチアカウント利用時)
各アカウントの LINE Developers Console で、LIFF エンドポイントURLを以下の形式に設定:
https://<WORKER_URL>?liffId=<LIFF_ID>
デフォルトアカウントは変更不要(VITE_LIFF_ID フォールバック)。
パッケージバージョン
@line-harness/sdk→ 0.9.0@line-harness/mcp-server→ 0.9.0