Skip to content

Releases: Shudesu/line-harness-oss

v0.17.0

Choose a tag to compare

@github-actions github-actions released this 07 Jul 16:41
a8fb9a5

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

Choose a tag to compare

@Shudesu Shudesu released this 07 Jul 07:05
bbe1af3

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 形式の配布リンク
  • 管理画面は /affiliates 1ページ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 domainLINK_BASE_URL setting + a redirect rule on your domain yields https://your-domain/xxxxxx links.
  • Admin UI consolidated into a single /affiliates page 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_conversations tool now applies the same resolved exclusion.

🔧 Fixed

  • Booking reminders run before heavy scheduled jobs (prevents cron-starvation misses).
  • OGP link previews for /o share URLs.
  • Update banner: hidden for self-hosted CI/CD builds, softer wording, new manual update guide (26-Manual-Update.md).
  • /updates page 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

Full Changelog: v0.15.0...v0.16.0

v0.15.0

Choose a tag to compare

@github-actions github-actions released this 07 Jun 05:01
1515a74

What's Changed

New Contributors

Full Changelog: v0.14.1...v0.15.0

v0.14.1

Choose a tag to compare

@github-actions github-actions released this 20 May 12:18

What's Changed

New Contributors

Full Changelog: v0.14.0...v0.14.1

v0.14.0 — Events Phase 1 / Inflow Links / Webhook DoS hardening

Choose a tag to compare

@Shudesu Shudesu released this 12 May 05:49

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 表示
  • 最新ソート + サイドバーアカウントフィルタ
  • /affiliates summary 統合、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)

Choose a tag to compare

@Shudesu Shudesu released this 21 Apr 09:36

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_logsource カラムを追加(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 のフォーム送信後 push
  • routes/liff.ts の LIFF 経由 push
  • services/event-bus.ts の automation reply/push
  • routes/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

Choose a tag to compare

@Shudesu Shudesu released this 14 Apr 13:19

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 / 修正内容

  1. Batch-lock deliveries / バッチロックgetFriendScenariosDueForDelivery() now atomically sets all due records to status='delivering' via db.batch() before returning them. Other cron instances see no active records to pick up.
    配信対象を取得と同時に一括で delivering ステータスに更新。他のcronインスタンスは同じレコードを取得できません。

  2. Single-pass delivery / 1回実行 — Delivery jobs now run once per cron, not per-account. Each function dynamically resolves the correct LineClient from friend.line_account_id.
    配信ジョブをアカウントループの外に出して1回だけ実行。各関数内でfriendのline_account_idから正しいlineClientを動的解決。

  3. Scheduled broadcast lock / ブロードキャスト楽観ロック — Optimistic lock (UPDATE ... WHERE status='scheduled') prevents parallel sends. Failures reset status to scheduled for retry.

  4. Reminder delivery lock / リマインダーロックINSERT OR IGNORE on friend_reminder_deliveries after successful send prevents duplicate delivery records.

  5. 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.

  6. Enrollment dedup / 登録デデュプenrollFriendInScenario() uses INSERT OR IGNORE, returns null if already enrolled.

  7. Crash recovery / クラッシュリカバリrecoverStuckDeliveries() resets records stuck in delivering for >5 minutes back to active.
    5分以上 delivering のままのレコードを active に戻す復旧処理を追加。

  8. Subrequest limit / サブリクエスト制限MAX_SENDS_PER_CRON = 40 respects 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_scenarios table with delivering status 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

Choose a tag to compare

@Shudesu Shudesu released this 07 Apr 11:31

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として優先解決:
    1. trackedLinkId 指定 + 該当 link が DB に存在 → その link の reward_template_id を採用(NULL の場合は null を返してフォームの on_submit_message_* に委譲)
    2. 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/submitbody.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

Choose a tag to compare

@Shudesu Shudesu released this 07 Apr 08:43

✨ 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

v0.9.0

Choose a tag to compare

@Shudesu Shudesu released this 02 Apr 09:25

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.sql

LIFF セットアップ(マルチアカウント利用時)

各アカウントの 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