npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@feelflow/ffid-sdk

v5.7.0

Published

FeelFlow ID Platform SDK for React/Next.js applications

Readme

@feelflow/ffid-sdk

npm version License: MIT

FeelFlow ID Platform SDK — React/Next.js 向け + サーバーサイドモジュールはフレームワーク非依存。

5行のコードでFFID認証を導入!

インストール

npm install @feelflow/ffid-sdk
# or
yarn add @feelflow/ffid-sdk
# or
pnpm add @feelflow/ffid-sdk

統合ガイド

サービスを FFID Platform に統合する詳細なガイドは Integration Guide を参照してください。OAuth フロー、フロントエンド/バックエンド実装パターン、セキュリティチェックリスト、アンチパターン集を網羅しています。

Cookie 同意管理基盤 (v5.0.0+)

GDPR / ePrivacy / 改正電気通信事業法 / APPI 準拠の Cookie 同意 UI を 15 分以内 に導入できます (opt-in、v4.x 完全互換)。

// app/layout.tsx (App Router、最短 3 ステップ)
import {
  FFIDProvider,
  FFIDAnalyticsProvider,
  FFIDCookieBanner,
  DEFAULT_OAUTH_SCOPES,
} from '@feelflow/ffid-sdk'

export default function RootLayout({ children }) {
  return (
    <FFIDProvider serviceCode="your-service" scope={DEFAULT_OAUTH_SCOPES}>
      <FFIDAnalyticsProvider
        baseUrl={process.env.NEXT_PUBLIC_FFID_BASE_URL!}
        serviceApiKey={process.env.FFID_SERVICE_API_KEY!}
        gaMeasurementId={process.env.NEXT_PUBLIC_GA_ID ?? null}
      >
        {children}
        <FFIDCookieBanner />
      </FFIDAnalyticsProvider>
    </FFIDProvider>
  )
}

クイックスタート

1. プロバイダーを設定(5行で完了!)

// app/layout.tsx
import { FFIDProvider, DEFAULT_OAUTH_SCOPES } from '@feelflow/ffid-sdk'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ja">
      <body>
        <FFIDProvider serviceCode="chatbot" scope={DEFAULT_OAUTH_SCOPES}>{children}</FFIDProvider>
      </body>
    </html>
  )
}

2. 認証情報を使用

import { useFFID, useSubscription } from '@feelflow/ffid-sdk'

function Dashboard() {
  const { user, isAuthenticated, login, logout } = useFFID()
  const { isActive, planCode } = useSubscription()

  if (!isAuthenticated) {
    return <button onClick={login}>ログイン</button>
  }

  return (
    <div>
      <p>Welcome, {user.displayName ?? user.email}!</p>
      <p>プラン: {planCode}</p>
      <button onClick={logout}>ログアウト</button>
    </div>
  )
}

UIコンポーネント

import {
  FFIDLoginButton,
  FFIDUserMenu,
  FFIDOrganizationSwitcher,
  FFIDSubscriptionBadge,
} from '@feelflow/ffid-sdk/components'

function Header() {
  return (
    <header>
      <FFIDLoginButton>ログイン</FFIDLoginButton>
      <FFIDOrganizationSwitcher />
      <FFIDSubscriptionBadge />
      <FFIDUserMenu />
    </header>
  )
}

コンポーネントのスタイリング(classNames パターン)

各UIコンポーネントは Radix UI スタイルパターン に従った classNames プロップをサポートしています。これにより、コンポーネントの各パーツに個別のクラスを適用できます。

FFIDUserMenu

<FFIDUserMenu
  classNames={{
    container: 'relative',          // ラッパー要素
    button: 'focus:ring-2',         // アバターボタン(トリガー)
    avatar: 'rounded-full',         // アバター画像/フォールバック
    menu: 'shadow-lg',              // ドロップダウンメニュー
    userInfo: 'border-b',           // ユーザー情報セクション
    menuItem: 'hover:bg-gray-100',  // カスタムメニュー項目
    logout: 'text-red-600',         // ログアウトボタン
  }}
/>

FFIDOrganizationSwitcher

<FFIDOrganizationSwitcher
  classNames={{
    container: 'relative',           // ラッパー要素
    button: 'border rounded',        // トリガーボタン
    dropdown: 'shadow-md',           // ドロップダウンメニュー
    option: 'px-4 py-2',             // 各組織オプション
    optionSelected: 'bg-blue-50',    // 選択中の組織(optionに加えて適用)
  }}
/>

FFIDSubscriptionBadge

<FFIDSubscriptionBadge
  classNames={{
    badge: 'font-semibold',  // バッジspan要素
  }}
/>

Note: FFIDLoginButton はシンプルな単一要素コンポーネントのため、標準の className プロップのみをサポートしています。

API リファレンス

FFIDProvider

アプリケーション全体をラップするプロバイダーコンポーネント。

<FFIDProvider
  serviceCode="chatbot"               // 必須: サービスコード
  scope={DEFAULT_OAUTH_SCOPES}        // 必須: OAuth scope (v3.0.0+)
  apiBaseUrl="..."                    // オプション: カスタムAPIエンドポイント
  debug={true}                        // オプション: デバッグログ有効化(非推奨、loggerを使用)
  logger={customLogger}               // オプション: カスタムロガー(下記参照)
  refreshInterval={300000}            // オプション: セッション更新間隔(ms)
  onAuthStateChange={(user) => {}}    // オプション: 認証状態変更時コールバック
  onError={(error) => {}}             // オプション: エラー時コールバック
>
  {children}
</FFIDProvider>

カスタムロガー

SDKのデバッグ出力をカスタマイズできます。デフォルトではログは出力されません(サイレント)。

import type { FFIDLogger } from '@feelflow/ffid-sdk'
import pino from 'pino' // または winston, bunyan 等

// アプリケーションのロガーインスタンス
const appLogger = pino({ level: 'debug' })

// FFID SDK用にラップ
const ffidLogger: FFIDLogger = {
  debug: (...args) => appLogger.debug({ sdk: 'ffid' }, ...args),
  info: (...args) => appLogger.info({ sdk: 'ffid' }, ...args),
  warn: (...args) => appLogger.warn({ sdk: 'ffid' }, ...args),
  error: (...args) => appLogger.error({ sdk: 'ffid' }, ...args),
}

// 使用例
<FFIDProvider serviceCode="chatbot" scope={DEFAULT_OAUTH_SCOPES} logger={ffidLogger}>
  {children}
</FFIDProvider>

ロガー優先順位:

  1. logger が指定されている場合 → カスタムロガーを使用
  2. debug: truelogger なし → console を使用(後方互換性)
  3. 両方なし → サイレント(no-op)

useFFID()

ユーザー・組織情報を取得するフック。

const {
  user,               // FFIDUser | null - 現在のユーザー
  organizations,      // FFIDOrganization[] - 所属組織一覧
  currentOrganization, // FFIDOrganization | null - 現在の組織
  isLoading,          // boolean - ロード中
  isAuthenticated,    // boolean - 認証済み
  login,              // () => void - ログインページへリダイレクト
  logout,             // () => Promise<void> - ログアウト
  switchOrganization, // (id: string) => void - 組織切り替え
  refresh,            // () => Promise<void> - セッション更新
} = useFFID()

useSubscription()

契約情報を取得するフック。

const {
  subscription,     // FFIDSubscription | null - 現在のサブスクリプション
  planCode,         // string | null - プランコード
  isActive,         // boolean - DB ステータスが 'active'
  isTrialing,       // boolean - トライアル中
  isCanceled,       // boolean - 解約済み
  isTrialExpired,   // boolean - トライアル期間超過
  effectiveStatus,  // EffectiveSubscriptionStatus | null - 意味論的アクセス制御値
  isBlocked,        // boolean - blocked / expired / canceled / trial_expired
  isGrace,          // boolean - past_due_grace (支払い失敗の猶予期間中)
  hasPlan,          // (plans: string | string[]) => boolean - プラン確認
  hasAccess,        // () => boolean - アクセス権確認 (active || past_due_grace)
  hasAccessLegacy,  // () => boolean - 旧セマンティクス (active || trialing, pre-2.19)
} = useSubscription()

effectiveStatus/api/v1/subscriptions/ext/check が返す意味論的ステータスと同じ値を取る。詳細は 契約期限切れハンドリング を参照。

useRequireActiveSubscription()

契約が期限切れ/遮断状態のときに自動でリダイレクトするフック。

'use client'
import { useRouter } from 'next/navigation'
import { useRequireActiveSubscription } from '@feelflow/ffid-sdk'

function ProtectedShell({ children }: { children: React.ReactNode }) {
  const router = useRouter()
  const { loading } = useRequireActiveSubscription({
    redirectTo: (status) =>
      status === 'canceled' ? '/contract-ended' : '/contract-required',
    onRedirect: (url) => router.replace(url),
  })

  if (loading) return <FullPageSpinner />
  return <>{children}</>
}

オプション:

  • redirectTo: string または (status: EffectiveSubscriptionStatus) => string
  • allowGrace (default: true): past_due_grace を通過させるか
  • onRedirect (optional): 独自のリダイレクト関数(未指定時は window.location.href

withSubscription()

サブスクリプション確認HOC。

const PremiumFeature = withSubscription(MyComponent, {
  plans: ['pro', 'enterprise'],
  fallback: <UpgradePrompt />,
  loading: <Spinner />,
})

契約期限切れハンドリング

契約が失効したり解約されたとき、外部サービス側で適切にアクセスを遮断し、ユーザーを再契約動線に案内する必要があります。SDK は 3 層構成(トークン検証 / 契約チェック / Webhook 受信)をサポートしています。

最小構成(Next.js App Router)

'use client'
import { useRouter } from 'next/navigation'
import { useRequireActiveSubscription } from '@feelflow/ffid-sdk'

export default function Layout({ children }: { children: React.ReactNode }) {
  const router = useRouter()
  const { loading, effectiveStatus } = useRequireActiveSubscription({
    redirectTo: '/contract-required',
    onRedirect: (url) => router.replace(url),
  })

  if (loading) return <Spinner />
  if (effectiveStatus !== 'active' && effectiveStatus !== 'past_due_grace') {
    return null // リダイレクト発火中
  }
  return <>{children}</>
}

詳細な実装レシピ(middleware、Express、UI 分岐、Webhook 受信、fixture API を使ったテスト、EffectiveSubscriptionStatus 別の UI 推奨動作まで) → docs/03-implementation/EXPIRED_CONTRACT_HANDLING.md

Server-side service access decision

API route / middleware では checkServiceAccess() を使う。FFID の /api/v1/subscriptions/ext/check が返す canonical decision をそのまま受け取り、外部サービス側で past_due_since + 7dcurrent_period_end + 7dpayment_failed_at + 7d のような lifecycle date math を再実装しない。

import { createFFIDClient } from '@feelflow/ffid-sdk'

const ffid = createFFIDClient({
  serviceCode: 'flow-board-ai',
  scope: '',
  authMode: 'service-key',
  serviceApiKey: process.env.FFID_SERVICE_API_KEY!,
})

const { data: access, error } = await ffid.checkServiceAccess({
  userId,
  organizationId,
  allowGrace: true,
})

if (error || !access?.hasAccess) {
  return Response.redirect('/contract-required')
}
  • checkServiceAccess()data.hasAccess が唯一の gateresult.error は入力 validation など SDK が decision を作れない場合にだけ返る。
  • hasAccess: FFID が決めた canonical access decision。allowGrace=false のときだけ SDK 側で past_due_grace を deny に変換する。
  • effectiveStatus: active / past_due_grace / blocked / canceled / trial_expired / expired
  • gracePeriodEndsAt: past_due_graceblocked に変わる時刻。表示・警告用であり、アクセス判定の source of truth にしない。
  • failPolicy: 現在は failClosed 固定。FFID に到達できない、または canonical decision を取得できない場合は result.error ではなく data.hasAccess=false / denialReason='ffid_unreachable' / data.error を返す。呼び出し側は result.error だけで通過判定しない。

Organization member management

organization:read / organization:write scope を持つ service-key または token で、組織メンバーの参照・追加・ロール変更・削除ができます。

import { createFFIDClient } from '@feelflow/ffid-sdk/server'

const ffid = createFFIDClient({
  serviceCode: 'flow-board-ai',
  scope: '',
  authMode: 'service-key',
  serviceApiKey: process.env.FFID_SERVICE_API_KEY!,
})

const addResult = await ffid.addMember({
  organizationId,
  email: '[email protected]',
  role: 'member',
})

if (addResult.error) {
  throw new Error(addResult.error.message)
}

await ffid.updateMemberRole({
  organizationId,
  userId: addResult.data.member.userId,
  role: 'admin',
})
  • addMember は既存 FFID ユーザーを active member として追加します。招待メール送信や token 発行は行いません。
  • roleadmin / member / viewer のみ指定可能です。owner は追加・昇格 API では扱いません。
  • agency client と同じ呼び味が必要な場合は ffid.addMember(organizationId, { email, role }) も使えます。

getProfile() / updateProfile()

ログイン中ユーザー自身のプロフィールを取得・更新するメソッド(createFFIDClient から呼び出し)。

  • エンドポイント: GET /api/v1/users/ext/me / PUT /api/v1/users/ext/me
  • 対応 authMode: token(Bearer)のみが SDK 経由で成立する
    • cookie モードは非対応 — ext エンドポイントはクロスオリジン用途のため、Bearer token または X-Service-Api-Key のどちらかが必須。FFID 自身の UI(同一オリジン)は従来通り /api/v1/users/me を使う
    • service-key モードも SDK 経由では非対応 — API Key 認証時はバックエンドが ?userId=<uuid> クエリを要求するが、getProfile() / updateProfile() は自分自身(ambient user)を前提とするため、userId を受け取らない。サーバー間で任意ユーザーのプロフィールを操作したい場合は fetchWithAuth で直接エンドポイントを叩いてください
  • 外部サービス(hub 等)のフロントエンドから token モードで呼ぶのが想定される主要パターン
import { createFFIDClient } from '@feelflow/ffid-sdk'

const client = createFFIDClient({
  serviceCode: 'hub',
  authMode: 'token',
  apiBaseUrl: 'https://id.feelflow.net',
})

// 取得
const { data: profile, error } = await client.getProfile()
if (error) {
  console.error('プロフィール取得失敗:', error.message)
} else {
  console.log(profile.email, profile.displayName, profile.timezone)
}

// 更新(部分更新 — 渡したフィールドだけ差し替え)
const { data: updated, error: updateError } = await client.updateProfile({
  displayName: '山田 太郎',
  timezone: 'Asia/Tokyo',
  locale: 'ja',
  preferences: { theme: 'dark' },
})

updateProfile に空オブジェクト {} を渡すと VALIDATION_ERROR が返ります(無意味なラウンドトリップを防止)。

フィールドのクリア(null を渡す)

optional フィールドに null を渡すと、FFID backend 側で該当カラムを SQL NULL にクリアします(v2.16.0〜 / #2354)。

// 会社名と部署をクリア
await client.updateProfile({
  companyName: null,
  department: null,
})

値のセマンティクス:

| 渡す値 | FFID backend の挙動 | | --- | --- | | undefined / キー未指定 | 未変更(partial update) | | null | クリア(SQL NULL を書き込む) | | ""(空文字列) | 空文字リテラルをそのまま保存(null 扱いにはならない) |

対応フィールド: displayName / phone / companyName / department / jobTitle / preferencestimezone / locale は application-level invariant(サーバー側 normalization が string 前提)のため null 非許容。クリアは不可 — キー未指定で現状維持、もしくは新しい有効な値を渡す。

getAnalyticsConfig() (#2347)

外部サービスが自身に割り当てられた GA4 Measurement ID を取得するメソッド。

  • エンドポイント: GET /api/v1/ext/analytics/config?service=<code>
  • 必要 scope: analytics:read(Bearer / API Key 両方で enforce)
  • レスポンス: FFIDAnalyticsConfig ({ code, measurementId, displayName, isActive })
  • archived service (isActive: false) でも 200 を返す — caller が次回 deploy で tracking 停止判断する間、in-flight events が 5xx を起こさないように measurementId は引き続き返す
import { createFFIDClient } from '@feelflow/ffid-sdk/server'

const client = createFFIDClient({
  serviceCode: 'flow-board-ai',
  authMode: 'service-key',
  serviceApiKey: process.env.FFID_SERVICE_API_KEY!,
})

const result = await client.getAnalyticsConfig('feel-agent-ai')
if (result.error) {
  if (result.error.code === 'SERVICE_NOT_FOUND') {
    // 該当 GA4 stream がまだ sync されていない / typo
  } else {
    console.error(`[FFID] analytics config fetch failed: ${result.error.code}`)
  }
} else if (result.data.isActive) {
  // GA4 タグを描画して events を送信
  loadGA4Script(result.data.measurementId)
}

エラーコード:

  • VALIDATION_ERRORserviceCode が空 / kebab-case 形式違反(SDK 側 pre-validate)
  • INSUFFICIENT_SCOPE (403) — analytics:read scope なし
  • SERVICE_NOT_FOUND (404) — DB に未登録の service code
  • INVALID_PARAM (400) — server 側で形式違反を検出(pre-validate 通過後の boundary)

型定義

interface FFIDUser {
  id: string
  email: string
  displayName: string | null
  avatarUrl: string | null
  locale: string | null
  timezone: string | null
  createdAt: string
}

interface FFIDOrganization {
  id: string
  name: string
  slug: string
  role: 'owner' | 'admin' | 'member'
  status: 'active' | 'invited' | 'suspended'
}

interface FFIDSubscription {
  id: string
  serviceCode: string
  serviceName: string
  planCode: string
  planName: string
  status: 'trialing' | 'active' | 'past_due' | 'canceled' | 'paused'
  currentPeriodEnd: string | null
}

OAuth userinfo の契約要約

token mode では SDK は /api/v1/oauth/userinfo を呼び出し、基本プロフィールに加えてサービス契約の要約を受け取ります。 この要約により、追加 API を呼ばずにプラン判定や UI 分岐を行えます。

interface FFIDOAuthUserInfoSubscription {
  subscriptionId: string | null
  status: 'trialing' | 'active' | 'past_due' | 'canceled' | 'paused' | null
  planCode: string | null
  seatModel: 'organization' | null
  memberRole: 'owner' | 'admin' | 'member' | 'viewer' | null
  organizationId: string | null
}

seatModel はシートモデル識別用であり、organization と role は userinfo の解決済み組織文脈として扱います。

React 以外の環境で使う

本 SDK は React/Next.js 向けに設計されていますが、一部のモジュールはフレームワーク非依存で利用できます。

サーバーサイドモジュール(React 依存なし)

以下の subpath exports は React に一切依存しません。Node.js、Deno、Bun 等で即座に利用できます。

// 利用規約・法的文書
import { createFFIDLegalClient } from '@feelflow/ffid-sdk/legal'

// Agency(代理店)管理
import { createFFIDAgencyClient } from '@feelflow/ffid-sdk/agency'

// お知らせ取得
import { createFFIDAnnouncementsClient } from '@feelflow/ffid-sdk/announcements'

// Webhook 署名検証・ハンドラー(※ Node.js crypto が必要)
import { createFFIDWebhookHandler, verifyWebhookSignature } from '@feelflow/ffid-sdk/webhooks'

Note: webhooks モジュールは Node.js の crypto モジュールと Buffer を使用します。Cloudflare Workers で利用する場合は nodejs_compat 互換フラグを有効にしてください。

これらのモジュールは独立した subpath export として公開されているため、メインエントリ (@feelflow/ffid-sdk) を経由せず、React の依存が伝播しません。

createFFIDClient を非 React 環境で使う

非 React 環境では、可能な限り上記の subpath exports(/legal/webhooks 等)の個別クライアントを使用してください。メインエントリの createFFIDClient を使う必要がある場合、createFFIDClient 自体は React を使用しませんが、メインエントリに含まれるため bundler 環境では React を external に指定する必要があります。

Cloudflare Workers

ビルドコマンドで --external を指定するか、カスタム esbuild 設定で external を設定してください。

# wrangler のビルドコマンド例
esbuild src/index.ts --bundle --format=esm --external:react --external:react-dom

Vue / Nuxt(Vite)

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      external: ['react', 'react-dom'],
    },
  },
})

esbuild

esbuild src/index.ts --bundle --external:react --external:react-dom

webpack

// webpack.config.js
module.exports = {
  externals: {
    react: 'react',
    'react-dom': 'react-dom',
  },
}

Note: サーバーサイドで bundler を使わずに実行する場合、subpath exports(@feelflow/ffid-sdk/legal 等)を使用すれば external 設定なしで React がインストールされていなくても動作します。メインエントリ(@feelflow/ffid-sdk)を ESM で import する場合は、モジュールグラフが静的に解決されるため React が必要です。

peerDependencies は optional です

SDK の package.jsonreact / react-domoptional: true に設定済みです(利用者側での設定は不要)。React をインストールしなくても npm install 時に warning は発生しません。

// SDK の package.json に設定済み(参考)
{
  "peerDependenciesMeta": {
    "react": { "optional": true },
    "react-dom": { "optional": true }
  }
}

E2E テストモード(@feelflow/ffid-sdk/server/test

⚠️ SECURITY NOTICE — テストモードは Bearer トークン検証を 意図的にバイパス する仕組みです。本番環境で誤って有効化すると、登録されたバイパストークンを持つ任意のリクエストが認証通過します。

各サービスで E2E テストを書く際、verifyAccessToken の introspect 呼び出しをモックする実装を独自に持つ必要がなくなります。SDK 側で 多重 production guard 付き の bypass クライアントを提供します。

import { createFFIDClient } from '@feelflow/ffid-sdk/server'
import { createTestFFIDClient } from '@feelflow/ffid-sdk/server/test'

const isE2E =
  process.env.NODE_ENV !== 'production' &&
  process.env.FFID_TEST_MODE === 'true'

const client = isE2E
  ? createTestFFIDClient({
      users: [
        {
          bypassToken: process.env.E2E_TEST_BYPASS_SECRET!,
          userInfo: {
            sub: 'e2e-test-sub',
            email: '[email protected]',
            name: 'E2E Test User',
            picture: null,
          },
        },
      ],
    })
  : createFFIDClient({ /* normal production options */ })

const result = await client.verifyAccessToken(bearerToken)

Built-in production guards (defense-in-depth)

  • NODE_ENVtrim + lowercase 後に比較。"production "(改行混入)や "Production" も production として扱う(Vercel 環境変数の copy/paste 事故対策)
  • process.env を露出しない runtime(Edge / Cloudflare Workers / browser)では fail-close で構築拒否
  • bypassToken の重複検知 / 空チェック / userInfo.sub 必須チェック(構築時 throw)
  • 未登録 token は fail-close(実 introspect への暗黙 fallthrough は行わない)
  • 構築時点で users のスナップショットを取り、入力配列の post-construction mutation は無視
  • verifyAccessToken 呼び出しは新しいオブジェクトを返却(caller mutation が後続呼び出しを汚染しない)

allowInProduction escape hatch

staging が NODE_ENV=production をミラーするケース等のみ、明示的な ack 文字列で有効化できます:

import {
  createTestFFIDClient,
  TEST_CLIENT_ALLOW_IN_PRODUCTION_ACK,
} from '@feelflow/ffid-sdk/server/test'

createTestFFIDClient({
  users: [...],
  allowInProduction: TEST_CLIENT_ALLOW_IN_PRODUCTION_ACK,
})
// → process.emitWarning(..., 'FFIDTestModeInProduction') を毎構築時に発火

boolean ではなく literal string ack を要求する型なので、allowInProduction: someBooleanFlag のような誤代入はコンパイルエラーになります。ack 文字列は grep 可能で監査も容易です。

サブパス分離

createTestFFIDClient@feelflow/ffid-sdk/server/test からのみ import 可能です(@feelflow/ffid-sdk/server にも main entry にも含まれない)。本番コードからの誤 import は ESLint の no-restricted-imports 等で検知することを推奨します:

// eslint.config.mjs (flat config)
import { defineConfig } from 'eslint/config'

export default defineConfig([
  {
    files: ['src/**/*.{ts,tsx}'],          // production code only
    ignores: ['**/__tests__/**', 'tests/e2e/**'],
    rules: {
      'no-restricted-imports': ['error', {
        patterns: ['@feelflow/ffid-sdk/server/test'],
      }],
    },
  },
])

環境変数

オプションで環境変数を使用してデフォルト設定を上書きできます:

NEXT_PUBLIC_FFID_API_URL=https://id.feelflow.net
NEXT_PUBLIC_FFID_SERVICE_CODE=chatbot

ライセンス

MIT