@feelflow/ffid-sdk 7.2.0 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -5
- package/dist/{chunk-SOZ2B2ZL.cjs → chunk-CPXTIRB6.cjs} +204 -12
- package/dist/{chunk-E2AJAVX6.js → chunk-P5A6C5SK.js} +195 -13
- package/dist/components/index.cjs +8 -8
- package/dist/components/index.d.cts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +1 -1
- package/dist/{ffid-client-BFgoVPOZ.d.cts → ffid-client-6aLs9Fqc.d.cts} +116 -29
- package/dist/{ffid-client-QNEFA3mv.d.ts → ffid-client-B0cORHac.d.ts} +116 -29
- package/dist/{index-BMKhKhzF.d.cts → index-BG7g99pK.d.cts} +1 -0
- package/dist/{index-BMKhKhzF.d.ts → index-BG7g99pK.d.ts} +1 -0
- package/dist/index.cjs +104 -64
- package/dist/index.d.cts +389 -32
- package/dist/index.d.ts +389 -32
- package/dist/index.js +2 -2
- package/dist/server/index.cjs +128 -10
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +128 -10
- package/dist/server/test/index.d.cts +1 -1
- package/dist/server/test/index.d.ts +1 -1
- package/dist/webhooks/index.d.cts +125 -6
- package/dist/webhooks/index.d.ts +125 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -464,7 +464,7 @@ const ffid = createFFIDClient({
|
|
|
464
464
|
// 1. ユーザーを冪等に作成(新規なら created:true / 既存なら created:false)
|
|
465
465
|
const userRes = await ffid.provisionUser({
|
|
466
466
|
email: 'owner@example.com',
|
|
467
|
-
profile: { displayName: '山田 太郎'
|
|
467
|
+
profile: { displayName: '山田 太郎' },
|
|
468
468
|
})
|
|
469
469
|
if (userRes.error) throw new Error(userRes.error.message)
|
|
470
470
|
if (!userRes.data.dryRun) {
|
|
@@ -561,12 +561,13 @@ const { data: updated, error: updateError } = await client.updateProfile({
|
|
|
561
561
|
|
|
562
562
|
#### フィールドのクリア(null を渡す)
|
|
563
563
|
|
|
564
|
-
optional フィールドに `null` を渡すと、FFID backend 側で該当カラムを
|
|
564
|
+
対応する optional フィールドに `null` を渡すと、FFID backend 側で該当カラムを
|
|
565
|
+
SQL NULL にクリアします(v2.16.0〜 / #2354)。個人プロフィールの
|
|
566
|
+
`companyName` は廃止予定で、旧クライアントからの値は受理しても無視します。
|
|
565
567
|
|
|
566
568
|
```tsx
|
|
567
|
-
//
|
|
569
|
+
// 部署をクリア
|
|
568
570
|
await client.updateProfile({
|
|
569
|
-
companyName: null,
|
|
570
571
|
department: null,
|
|
571
572
|
})
|
|
572
573
|
```
|
|
@@ -579,7 +580,7 @@ await client.updateProfile({
|
|
|
579
580
|
| `null` | クリア(SQL NULL を書き込む) |
|
|
580
581
|
| `""`(空文字列) | 空文字リテラルをそのまま保存(`null` 扱いには**ならない**) |
|
|
581
582
|
|
|
582
|
-
対応フィールド: `displayName` / `phone` / `
|
|
583
|
+
対応フィールド: `displayName` / `phone` / `department` / `jobTitle` / `preferences`。`timezone` / `locale` は application-level invariant(サーバー側 normalization が string 前提)のため null 非許容。クリアは不可 — キー未指定で現状維持、もしくは新しい有効な値を渡す。
|
|
583
584
|
|
|
584
585
|
### getAnalyticsConfig() (#2347)
|
|
585
586
|
|
|
@@ -618,6 +619,115 @@ if (result.error) {
|
|
|
618
619
|
- `SERVICE_NOT_FOUND` (404) — DB に未登録の service code
|
|
619
620
|
- `INVALID_PARAM` (400) — server 側で形式違反を検出(pre-validate 通過後の boundary)
|
|
620
621
|
|
|
622
|
+
## 代理店の招待リンク(`?invite=`)
|
|
623
|
+
|
|
624
|
+
代理店の招待リンクから来た顧客が FFID で登録すると、作られた組織がその代理店に紐付きます。SDK の役割は**トークンを signup URL まで運ぶこと**だけで、紐付けの実務は FFID 側が行います。
|
|
625
|
+
|
|
626
|
+
> 代理店ポータルが発行するリンクは FFID の origin(`https://id.feelflow.net/signup?invite=…`)を指します。サービスサイトを着地点にする形(`https://<あなたのサービス>/?invite=…`)は、代理店がトークンを自分のキャンペーン URL に貼って成立させる導線です。この節が効くのはその形です。
|
|
627
|
+
|
|
628
|
+
### React 利用者(`FFIDProvider` を mount している場合)
|
|
629
|
+
|
|
630
|
+
**配線は不要です。** provider が mount 時に着地 URL の `invite` を捕捉し、first-party cookie へ保存します。`getSignupUrl()` が自動でトークンを載せます。
|
|
631
|
+
|
|
632
|
+
ただし provider は捕捉結果を返さないので、**cookie の保持に失敗したことを利用者側で観測する経路はありません**(debug ログには落ちます)。検知したい場合は provider に加えて自分で `captureAgencyInviteToken()` を呼び、戻り値の `persisted` を見てください。
|
|
633
|
+
|
|
634
|
+
### React 以外 / provider を使わない場合
|
|
635
|
+
|
|
636
|
+
`captureAgencyInviteToken()` を**ページ遷移ごとに**呼びます。
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
import { captureAgencyInviteToken } from '@feelflow/ffid-sdk'
|
|
640
|
+
|
|
641
|
+
// ページ読み込みごと(SPA ならルート遷移ごと)に呼ぶ
|
|
642
|
+
const { token, source, persisted } = captureAgencyInviteToken()
|
|
643
|
+
|
|
644
|
+
if (persisted === false) {
|
|
645
|
+
// cookie がブラウザに落とされた。今回の URL 経由では紐付くが、
|
|
646
|
+
// パラメータの無い別ページへ回遊すると失われる。
|
|
647
|
+
} else if (persisted === undefined && source === 'url') {
|
|
648
|
+
// 書ける環境が無かった(SSR)。ブラウザ側で捕捉し直す必要がある。
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
呼ばなかった場合は「着地ページで即 signup した人だけ紐付く」まで**劣化します**(壊れるのではなく、下記の回遊経路を救えなくなります)。
|
|
653
|
+
|
|
654
|
+
### なぜページ遷移ごとに呼ぶ必要があるのか
|
|
655
|
+
|
|
656
|
+
実際の導線は次の形です。
|
|
657
|
+
|
|
658
|
+
```
|
|
659
|
+
/?invite=abc に着地 → 料金ページを見る → /pricing で「登録」を押す
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
`getSignupUrl()` が呼ばれる時点の URL には `invite` がありません。着地時点で捕捉して保持する以外にこの経路を救う方法がなく、**SDK 自身はページ読み込みを hook できない**ため、起動点は利用者側にあります。
|
|
663
|
+
|
|
664
|
+
### ⚠️ 効かない経路: SSR(Server Component)で signup URL を組む場合
|
|
665
|
+
|
|
666
|
+
サーバーには `window.location.search` も `document.cookie` もありません。`getSignupUrl()` をそのまま呼ぶとトークンは載らないので、呼び出し側が読んだ値を渡してください(渡さない場合はプロセスあたり 1 度 warn します)。
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
// Server Component
|
|
670
|
+
import { cookies } from 'next/headers'
|
|
671
|
+
import { AGENCY_INVITE_COOKIE_NAME } from '@feelflow/ffid-sdk'
|
|
672
|
+
|
|
673
|
+
const inviteToken = (await cookies()).get(AGENCY_INVITE_COOKIE_NAME)?.value
|
|
674
|
+
const href = ffid.getSignupUrl(undefined, { inviteToken })
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### `authMode: 'token'`(OAuth モード)は v7.4.0 で対応済み
|
|
678
|
+
|
|
679
|
+
この設定では signup 意図が `/api/v1/oauth/authorize` を経由するため `getSignupUrl()` を通りません。**v7.4.0 以降は `redirectToAuthorize` が `invite` を authorize URL へ転送し、FFID 側が `/signup` へ引き継ぎます**(`redirectToLogin({ screenHint: 'signup' })` は token モードでは `redirectToAuthorize` へ委譲されるので、こちらも同様に載ります)。配線は不要です。
|
|
680
|
+
|
|
681
|
+
v7.3.x までは token モードでトークンが FFID に 1 度も届かず、紐付けが無言で落ちていました。**v7.4.0 未満を使っている場合は、SDK と FFID 本体の両方を更新してください** — 転送は SDK が `invite` を送り、FFID が `/signup` へ stamp する 2 段で成立します。
|
|
682
|
+
|
|
683
|
+
⚠️ 転送の gate は `screenHint === 'signup'` です。`prompt` に `'create'` を指定して signup 画面へ向かう経路は SDK の `FFIDPrompt`(`'select_account' | 'login'`)が受け付けないため到達しません。
|
|
684
|
+
|
|
685
|
+
### cookie の仕様
|
|
686
|
+
|
|
687
|
+
| 項目 | 値 |
|
|
688
|
+
|---|---|
|
|
689
|
+
| 名前 | `ffid_invite`(first-party・consumer 自身のホスト) |
|
|
690
|
+
| 有効期限 | 30 日 |
|
|
691
|
+
| `Path` | `/` |
|
|
692
|
+
| `SameSite` | `Lax` |
|
|
693
|
+
| `Secure` | `NODE_ENV === 'production'` のときだけ(`options.secure` で上書き可) |
|
|
694
|
+
| `Domain` | 既定は現在のホストのみ(`options.domain` で指定可) |
|
|
695
|
+
|
|
696
|
+
`Secure` を本番限定にしているのは、**https でない独自ドメイン**(LAN IP や plain http の検証機)でブラウザが `Secure` cookie を黙って捨てるためです。`http://localhost` は secure context として扱われるので `Secure` でも保持されます(ローカル開発では上書き不要)。
|
|
697
|
+
|
|
698
|
+
URL と cookie の両方にトークンがある場合は **URL が勝ち、cookie も更新されます**(最後に踏んだリンクが勝つ)。
|
|
699
|
+
|
|
700
|
+
**削除には書き込み時と同じ `path` / `domain` / `secure` が必要です。** ブラウザが一致を要求するため、既定以外で書いた cookie は既定の `clearAgencyInviteToken()` では消えません。
|
|
701
|
+
|
|
702
|
+
### ⚠️ 保持期間 30 日の副作用
|
|
703
|
+
|
|
704
|
+
招待リンクは既定で使用回数が無制限です。したがって保持中の cookie は、**同じブラウザで後から作られた組織も同じ代理店に紐付けます**。共有ブラウザではこれが誤紐付けになります。
|
|
705
|
+
|
|
706
|
+
SDK は登録完了時に cookie を消しません(登録完了は FFID のドメインで起こるため consumer から検知しにくい)。単回にしたい場合は、登録完了を検知できる地点で明示的に消してください。
|
|
707
|
+
|
|
708
|
+
```typescript
|
|
709
|
+
import { clearAgencyInviteToken } from '@feelflow/ffid-sdk'
|
|
710
|
+
|
|
711
|
+
clearAgencyInviteToken() // 削除が read-back で確認できたら true / SSR では undefined
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### ⚠️ トークンをログに出さないこと
|
|
715
|
+
|
|
716
|
+
このトークンは URL に載る**平文の資格情報**で、**単回使用ではありません**(招待リンクの使用回数上限は既定で無制限)。漏れたトークンは、新規組織が作られる限り何度でも紐付けに使えます。
|
|
717
|
+
|
|
718
|
+
SDK 内部ではログ・エラーレポート・アナリティクスに値を出しません。`getSignupUrl()` の出力を自前でログや解析イベントに送っている場合は、`redactAgencyInviteToken()` を通してください。
|
|
719
|
+
|
|
720
|
+
```typescript
|
|
721
|
+
import { redactAgencyInviteToken } from '@feelflow/ffid-sdk'
|
|
722
|
+
|
|
723
|
+
logger.info('signup url', redactAgencyInviteToken(ffid.getSignupUrl()))
|
|
724
|
+
// → https://id.feelflow.net/signup?redirect=...&service=svc&invite=[REDACTED]
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
素の `invite=` だけでなく、`redirect` パラメータに着地 URL が percent-encode されて入る**入れ子**(`redirect=…%3Finvite%3D…`)も潰します。覆う encode の深さは **2 段まで**です。GA4 の `page_location` のように URL を丸ごと送るイベントでも同じ経路を通してください。
|
|
728
|
+
|
|
729
|
+
**login URL にも `redirect` 経由で入れ子で載ります。** 専用の `&invite=` は付きませんが(紐付けは新規登録時の組織作成に対して起こるため)、着地 URL が `?invite=…` なら login URL の `redirect` の中に入ります。login URL をログに出す場合も redact してください。
|
|
730
|
+
|
|
621
731
|
## 型定義
|
|
622
732
|
|
|
623
733
|
```typescript
|
|
@@ -160,7 +160,7 @@ function normalizeUserinfo(raw) {
|
|
|
160
160
|
emailVerified: raw.email_verified,
|
|
161
161
|
name: raw.name,
|
|
162
162
|
picture: raw.picture,
|
|
163
|
-
companyName:
|
|
163
|
+
companyName: null,
|
|
164
164
|
department: raw.department ?? null,
|
|
165
165
|
position: raw.position ?? null,
|
|
166
166
|
phoneNumber: raw.phone_number ?? null,
|
|
@@ -469,7 +469,7 @@ function createVerifyAccessToken(deps) {
|
|
|
469
469
|
email_verified: introspectResponse.email_verified,
|
|
470
470
|
name: introspectResponse.name ?? null,
|
|
471
471
|
picture: introspectResponse.picture ?? null,
|
|
472
|
-
company_name:
|
|
472
|
+
company_name: null,
|
|
473
473
|
department: introspectResponse.department ?? null,
|
|
474
474
|
position: introspectResponse.position ?? null,
|
|
475
475
|
phone_number: introspectResponse.phone_number ?? null,
|
|
@@ -720,6 +720,24 @@ function validateSeatChangePreview(preview) {
|
|
|
720
720
|
`SDK: server returned malformed SeatChangePreview \u2014 estimateReason must be one of 'no_stripe_data' | 'custom_pricing' | 'stripe_error' (got ${JSON.stringify(p.estimateReason)})`
|
|
721
721
|
);
|
|
722
722
|
}
|
|
723
|
+
if (p.willApplyAtPeriodEnd !== void 0 && typeof p.willApplyAtPeriodEnd !== "boolean") {
|
|
724
|
+
throw new FFIDSDKError(
|
|
725
|
+
MALFORMED_SEAT_PREVIEW_CODE,
|
|
726
|
+
`SDK: server returned malformed SeatChangePreview \u2014 willApplyAtPeriodEnd must be boolean when present (got ${typeof p.willApplyAtPeriodEnd})`
|
|
727
|
+
);
|
|
728
|
+
}
|
|
729
|
+
if (p.effectiveDate !== void 0 && p.effectiveDate !== null && typeof p.effectiveDate !== "string") {
|
|
730
|
+
throw new FFIDSDKError(
|
|
731
|
+
MALFORMED_SEAT_PREVIEW_CODE,
|
|
732
|
+
`SDK: server returned malformed SeatChangePreview \u2014 effectiveDate must be string or null when present (got ${typeof p.effectiveDate})`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
if (p.willApplyAtPeriodEnd === true && p.proratedAmount !== 0) {
|
|
736
|
+
throw new FFIDSDKError(
|
|
737
|
+
MALFORMED_SEAT_PREVIEW_CODE,
|
|
738
|
+
`SDK: server returned malformed SeatChangePreview \u2014 willApplyAtPeriodEnd=true requires proratedAmount=0 (got ${JSON.stringify(p.proratedAmount)})`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
723
741
|
if (!Array.isArray(p.lineItems)) {
|
|
724
742
|
throw new FFIDSDKError(
|
|
725
743
|
MALFORMED_SEAT_PREVIEW_CODE,
|
|
@@ -1360,7 +1378,7 @@ var FFID_ORGANIZATION_PROFILE_ERROR_CODES = {
|
|
|
1360
1378
|
};
|
|
1361
1379
|
|
|
1362
1380
|
// src/client/version-check.ts
|
|
1363
|
-
var SDK_VERSION = "
|
|
1381
|
+
var SDK_VERSION = "8.1.0";
|
|
1364
1382
|
var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
|
|
1365
1383
|
var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
|
|
1366
1384
|
function sdkHeaders() {
|
|
@@ -2614,6 +2632,129 @@ function cleanupStateStorage(logger) {
|
|
|
2614
2632
|
}
|
|
2615
2633
|
}
|
|
2616
2634
|
|
|
2635
|
+
// src/client/agency-invite-token.ts
|
|
2636
|
+
var AGENCY_INVITE_TOKEN_PARAM = "invite";
|
|
2637
|
+
var AGENCY_INVITE_COOKIE_NAME = "ffid_invite";
|
|
2638
|
+
var SECONDS_PER_MINUTE = 60;
|
|
2639
|
+
var MINUTES_PER_HOUR = 60;
|
|
2640
|
+
var HOURS_PER_DAY = 24;
|
|
2641
|
+
var RETENTION_DAYS = 30;
|
|
2642
|
+
var AGENCY_INVITE_COOKIE_MAX_AGE_SEC = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * RETENTION_DAYS;
|
|
2643
|
+
var AGENCY_INVITE_TOKEN_REDACTION = "[REDACTED]";
|
|
2644
|
+
function hasDocument() {
|
|
2645
|
+
return typeof document !== "undefined";
|
|
2646
|
+
}
|
|
2647
|
+
function defaultSecure() {
|
|
2648
|
+
if (typeof process === "undefined") return true;
|
|
2649
|
+
return process.env?.NODE_ENV === "production";
|
|
2650
|
+
}
|
|
2651
|
+
function toQueryString(source) {
|
|
2652
|
+
const queryStart = source.indexOf("?");
|
|
2653
|
+
if (queryStart < 0) return "";
|
|
2654
|
+
const afterQuery = source.slice(queryStart);
|
|
2655
|
+
const fragmentStart = afterQuery.indexOf("#");
|
|
2656
|
+
return fragmentStart < 0 ? afterQuery : afterQuery.slice(0, fragmentStart);
|
|
2657
|
+
}
|
|
2658
|
+
function readTokenFromUrl(options) {
|
|
2659
|
+
const source = options.url ?? (typeof window !== "undefined" ? window.location.search : void 0);
|
|
2660
|
+
if (source === void 0) return void 0;
|
|
2661
|
+
const params = new URLSearchParams(toQueryString(source));
|
|
2662
|
+
return params.get(AGENCY_INVITE_TOKEN_PARAM)?.trim() || void 0;
|
|
2663
|
+
}
|
|
2664
|
+
function readAgencyInviteCookie() {
|
|
2665
|
+
if (!hasDocument()) return void 0;
|
|
2666
|
+
const prefix = `${AGENCY_INVITE_COOKIE_NAME}=`;
|
|
2667
|
+
const raw = document.cookie.split(";").map((entry) => entry.trim()).find((entry) => entry.startsWith(prefix));
|
|
2668
|
+
if (!raw) return void 0;
|
|
2669
|
+
try {
|
|
2670
|
+
return decodeURIComponent(raw.slice(prefix.length)).trim() || void 0;
|
|
2671
|
+
} catch {
|
|
2672
|
+
return void 0;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
function readAgencyInviteToken(options = {}) {
|
|
2676
|
+
return readTokenFromUrl(options) ?? readAgencyInviteCookie();
|
|
2677
|
+
}
|
|
2678
|
+
function buildAgencyInviteCookie(value, maxAgeSec, options) {
|
|
2679
|
+
const attrs = [
|
|
2680
|
+
`${AGENCY_INVITE_COOKIE_NAME}=${value}`,
|
|
2681
|
+
`Max-Age=${maxAgeSec}`,
|
|
2682
|
+
`Path=${options.path ?? "/"}`,
|
|
2683
|
+
"SameSite=Lax"
|
|
2684
|
+
];
|
|
2685
|
+
if (options.domain) attrs.push(`Domain=${options.domain}`);
|
|
2686
|
+
if (options.secure ?? defaultSecure()) attrs.push("Secure");
|
|
2687
|
+
return attrs.join("; ");
|
|
2688
|
+
}
|
|
2689
|
+
function writeAgencyInviteCookie(token, options) {
|
|
2690
|
+
if (!hasDocument()) return void 0;
|
|
2691
|
+
document.cookie = buildAgencyInviteCookie(
|
|
2692
|
+
encodeURIComponent(token),
|
|
2693
|
+
AGENCY_INVITE_COOKIE_MAX_AGE_SEC,
|
|
2694
|
+
options
|
|
2695
|
+
);
|
|
2696
|
+
return readAgencyInviteCookie() === token;
|
|
2697
|
+
}
|
|
2698
|
+
function clearAgencyInviteToken(options = {}) {
|
|
2699
|
+
if (!hasDocument()) return void 0;
|
|
2700
|
+
const CLEAR_MAX_AGE = 0;
|
|
2701
|
+
document.cookie = buildAgencyInviteCookie("", CLEAR_MAX_AGE, options);
|
|
2702
|
+
return readAgencyInviteCookie() === void 0;
|
|
2703
|
+
}
|
|
2704
|
+
function captureAgencyInviteToken(options = {}) {
|
|
2705
|
+
const fromUrl = readTokenFromUrl(options);
|
|
2706
|
+
if (fromUrl !== void 0) {
|
|
2707
|
+
return { source: "url", token: fromUrl, persisted: writeAgencyInviteCookie(fromUrl, options) };
|
|
2708
|
+
}
|
|
2709
|
+
const fromCookie = readAgencyInviteCookie();
|
|
2710
|
+
return fromCookie !== void 0 ? { source: "cookie", token: fromCookie, persisted: void 0 } : { source: "none", token: void 0, persisted: void 0 };
|
|
2711
|
+
}
|
|
2712
|
+
var REDACT_BOUNDARIES = [
|
|
2713
|
+
"%2526",
|
|
2714
|
+
"%2523",
|
|
2715
|
+
"%253B",
|
|
2716
|
+
"%26",
|
|
2717
|
+
"%23",
|
|
2718
|
+
"%3B",
|
|
2719
|
+
"&",
|
|
2720
|
+
"#",
|
|
2721
|
+
";"
|
|
2722
|
+
];
|
|
2723
|
+
var REDACT_LEADS = ["%253F", "%3F", "?", ...REDACT_BOUNDARIES];
|
|
2724
|
+
var REDACT_SEPARATORS = ["%253D", "%3D", "="];
|
|
2725
|
+
function escapeForRegExp(literal) {
|
|
2726
|
+
return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2727
|
+
}
|
|
2728
|
+
var REDACT_PATTERN = new RegExp(
|
|
2729
|
+
[
|
|
2730
|
+
// 先頭区切り。`\b` が使えないのは、入れ子形 (`%3Finvite%3D`) では `F` と `i` が
|
|
2731
|
+
// どちらも単語文字なので単語境界が存在しないため。
|
|
2732
|
+
`(^|${REDACT_LEADS.map(escapeForRegExp).join("|")})`,
|
|
2733
|
+
// パラメータ名と区切りはキャプチャして再出力する。`i` フラグがあるので、
|
|
2734
|
+
// 入力の `INVITE=` / `%3d` などの元の表記を保つ必要がある。
|
|
2735
|
+
`(${AGENCY_INVITE_TOKEN_PARAM})`,
|
|
2736
|
+
`(${REDACT_SEPARATORS.map(escapeForRegExp).join("|")})`,
|
|
2737
|
+
// 値。境界の直前まで貪欲に食う。`\s` は終端にしない — 生の空白や改行を含む
|
|
2738
|
+
// トークンでマスクが途中で切れ、後半が漏れる。
|
|
2739
|
+
`(?:(?!${REDACT_BOUNDARIES.map(escapeForRegExp).join("|")})[^&#;])*`
|
|
2740
|
+
].join(""),
|
|
2741
|
+
"gi"
|
|
2742
|
+
);
|
|
2743
|
+
function redactAgencyInviteToken(url) {
|
|
2744
|
+
return url.replace(
|
|
2745
|
+
REDACT_PATTERN,
|
|
2746
|
+
// 文字列 replacement(`'$1$2$3[REDACTED]'`)にしないのは、
|
|
2747
|
+
// `AGENCY_INVITE_TOKEN_REDACTION` に `$` が入った瞬間に置換パターンとして
|
|
2748
|
+
// 解釈される罠を作らないため。
|
|
2749
|
+
(_match, lead, name, separator) => `${lead}${name}${separator}${AGENCY_INVITE_TOKEN_REDACTION}`
|
|
2750
|
+
);
|
|
2751
|
+
}
|
|
2752
|
+
function appendAgencyInviteToken(signupUrl, token) {
|
|
2753
|
+
if (token === void 0) return signupUrl;
|
|
2754
|
+
const separator = signupUrl.includes("?") ? "&" : "?";
|
|
2755
|
+
return `${signupUrl}${separator}${AGENCY_INVITE_TOKEN_PARAM}=${encodeURIComponent(token)}`;
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2617
2758
|
// src/client/redirect.ts
|
|
2618
2759
|
var OAUTH_AUTHORIZE_ENDPOINT = "/api/v1/oauth/authorize";
|
|
2619
2760
|
var AUTH_LOGOUT_ENDPOINT = "/api/v1/auth/logout";
|
|
@@ -2626,6 +2767,7 @@ var REDIRECT_LOOP_WINDOW_MS = 6e4;
|
|
|
2626
2767
|
var REDIRECT_LOOP_THRESHOLD = 3;
|
|
2627
2768
|
var storageReadFailureLogged = false;
|
|
2628
2769
|
var storageWriteFailureLogged = false;
|
|
2770
|
+
var ssrInviteTokenUnavailableLogged = false;
|
|
2629
2771
|
function isLoopDetectionDegraded() {
|
|
2630
2772
|
return storageReadFailureLogged || storageWriteFailureLogged;
|
|
2631
2773
|
}
|
|
@@ -2804,12 +2946,16 @@ function createRedirectMethods(deps) {
|
|
|
2804
2946
|
}
|
|
2805
2947
|
if (options?.screenHint === SCREEN_HINT_SIGNUP) {
|
|
2806
2948
|
params.set(SCREEN_HINT_PARAM, SCREEN_HINT_SIGNUP);
|
|
2949
|
+
const inviteToken = readAgencyInviteToken();
|
|
2950
|
+
if (inviteToken !== void 0) {
|
|
2951
|
+
params.set(AGENCY_INVITE_TOKEN_PARAM, inviteToken);
|
|
2952
|
+
}
|
|
2807
2953
|
}
|
|
2808
2954
|
if (options?.prompt) {
|
|
2809
2955
|
params.set("prompt", options.prompt);
|
|
2810
2956
|
}
|
|
2811
2957
|
const authorizeUrl = `${baseUrl}${OAUTH_AUTHORIZE_ENDPOINT}?${params.toString()}`;
|
|
2812
|
-
logger.debug("Redirecting to authorize:", authorizeUrl);
|
|
2958
|
+
logger.debug("Redirecting to authorize:", redactAgencyInviteToken(authorizeUrl));
|
|
2813
2959
|
recordRedirectAttempt(authorizeKey, now, logger);
|
|
2814
2960
|
try {
|
|
2815
2961
|
window.location.href = authorizeUrl;
|
|
@@ -2828,7 +2974,7 @@ function createRedirectMethods(deps) {
|
|
|
2828
2974
|
return redirectToAuthorize(options);
|
|
2829
2975
|
}
|
|
2830
2976
|
const targetUrl = options?.screenHint === SCREEN_HINT_SIGNUP ? getSignupUrl() : getLoginUrl(void 0, options?.prompt ? { prompt: options.prompt } : void 0);
|
|
2831
|
-
logger.debug("Redirecting to auth page:", targetUrl);
|
|
2977
|
+
logger.debug("Redirecting to auth page:", redactAgencyInviteToken(targetUrl));
|
|
2832
2978
|
window.location.href = targetUrl;
|
|
2833
2979
|
return { success: true };
|
|
2834
2980
|
}
|
|
@@ -2845,7 +2991,7 @@ function createRedirectMethods(deps) {
|
|
|
2845
2991
|
const promptSuffix = options?.prompt ? `&prompt=${encodeURIComponent(options.prompt)}` : "";
|
|
2846
2992
|
return `${baseUrl}/login?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}${promptSuffix}`;
|
|
2847
2993
|
}
|
|
2848
|
-
function getSignupUrl(redirectUrl) {
|
|
2994
|
+
function getSignupUrl(redirectUrl, options) {
|
|
2849
2995
|
let redirect;
|
|
2850
2996
|
if (redirectUrl != null) {
|
|
2851
2997
|
redirect = redirectUrl;
|
|
@@ -2855,7 +3001,22 @@ function createRedirectMethods(deps) {
|
|
|
2855
3001
|
logger.warn("getSignupUrl: SSR \u74B0\u5883\u3067 redirectUrl \u304C\u672A\u6307\u5B9A\u306E\u305F\u3081\u7A7A\u6587\u5B57\u306B\u30D5\u30A9\u30FC\u30EB\u30D0\u30C3\u30AF\u3057\u307E\u3059");
|
|
2856
3002
|
redirect = "";
|
|
2857
3003
|
}
|
|
2858
|
-
|
|
3004
|
+
let inviteToken = options?.inviteToken;
|
|
3005
|
+
if (inviteToken === void 0) {
|
|
3006
|
+
inviteToken = readAgencyInviteToken();
|
|
3007
|
+
if (inviteToken === void 0 && typeof window === "undefined") {
|
|
3008
|
+
if (!ssrInviteTokenUnavailableLogged) {
|
|
3009
|
+
ssrInviteTokenUnavailableLogged = true;
|
|
3010
|
+
logger.warn(
|
|
3011
|
+
"[FFID SDK] getSignupUrl: SSR \u3067\u306F\u62DB\u5F85\u30C8\u30FC\u30AF\u30F3\uFF08invite\uFF09\u3092\u8AAD\u3081\u307E\u305B\u3093\u3002\u4EE3\u7406\u5E97\u306E\u62DB\u5F85\u30EA\u30F3\u30AF\u3092\u4F7F\u3046\u5834\u5408\u306F options.inviteToken \u306B\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F7F\u3063\u3066\u3044\u306A\u3044\u5834\u5408\u306F\u3053\u306E\u8B66\u544A\u3092\u7121\u8996\u3057\u3066\u3088\u3044\uFF09"
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
return appendAgencyInviteToken(
|
|
3017
|
+
`${baseUrl}/signup?redirect=${encodeURIComponent(redirect)}&service=${encodeURIComponent(serviceCode)}`,
|
|
3018
|
+
inviteToken
|
|
3019
|
+
);
|
|
2859
3020
|
}
|
|
2860
3021
|
function getLogoutUrl(postLogoutRedirectUri) {
|
|
2861
3022
|
const url = new URL(`${baseUrl}${AUTH_LOGOUT_ENDPOINT}`);
|
|
@@ -2871,7 +3032,7 @@ function createRedirectMethods(deps) {
|
|
|
2871
3032
|
return { success: false, error: "SSR \u74B0\u5883\u3067\u306F\u30EA\u30C0\u30A4\u30EC\u30AF\u30C8\u3067\u304D\u307E\u305B\u3093" };
|
|
2872
3033
|
}
|
|
2873
3034
|
const logoutUrl = getLogoutUrl(postLogoutRedirectUri);
|
|
2874
|
-
logger.debug("Redirecting to logout:", logoutUrl);
|
|
3035
|
+
logger.debug("Redirecting to logout:", redactAgencyInviteToken(logoutUrl));
|
|
2875
3036
|
window.location.href = logoutUrl;
|
|
2876
3037
|
return { success: true };
|
|
2877
3038
|
}
|
|
@@ -3484,11 +3645,11 @@ var EFFECTIVE_SUBSCRIPTION_STATUSES = [
|
|
|
3484
3645
|
"expired"
|
|
3485
3646
|
];
|
|
3486
3647
|
var PAST_DUE_GRACE_PERIOD_DAYS = 7;
|
|
3487
|
-
var
|
|
3488
|
-
var
|
|
3489
|
-
var
|
|
3648
|
+
var HOURS_PER_DAY2 = 24;
|
|
3649
|
+
var MINUTES_PER_HOUR2 = 60;
|
|
3650
|
+
var SECONDS_PER_MINUTE2 = 60;
|
|
3490
3651
|
var MS_PER_SECOND2 = 1e3;
|
|
3491
|
-
var MS_PER_DAY =
|
|
3652
|
+
var MS_PER_DAY = HOURS_PER_DAY2 * MINUTES_PER_HOUR2 * SECONDS_PER_MINUTE2 * MS_PER_SECOND2;
|
|
3492
3653
|
|
|
3493
3654
|
// src/subscriptions/compute-effective-status.ts
|
|
3494
3655
|
function isExpired(isoTimestamp, nowMs) {
|
|
@@ -3641,6 +3802,18 @@ var FFID_ERROR_CODES = {
|
|
|
3641
3802
|
TOKEN_EXPIRED: "TOKEN_EXPIRED",
|
|
3642
3803
|
STATE_MISMATCH_ERROR: "STATE_MISMATCH_ERROR"
|
|
3643
3804
|
};
|
|
3805
|
+
var FFID_SUBSCRIPTION_ERROR_CODES = {
|
|
3806
|
+
CANCELLATION_SCHEDULED: "CANCELLATION_SCHEDULED",
|
|
3807
|
+
PENDING_CHANGE_EXISTS: "PENDING_CHANGE_EXISTS",
|
|
3808
|
+
CHANGE_DIRECTION_MIXED: "CHANGE_DIRECTION_MIXED",
|
|
3809
|
+
CHANGE_PAYMENT_FAILED: "CHANGE_PAYMENT_FAILED",
|
|
3810
|
+
SAME_PLAN_SELECTED: "SAME_PLAN_SELECTED",
|
|
3811
|
+
NO_PENDING_DOWNGRADE: "NO_PENDING_DOWNGRADE",
|
|
3812
|
+
SUBSCRIPTION_NOT_CANCELABLE: "SUBSCRIPTION_NOT_CANCELABLE",
|
|
3813
|
+
SUBSCRIPTION_ALREADY_CANCELED: "SUBSCRIPTION_ALREADY_CANCELED",
|
|
3814
|
+
FREE_DOWNGRADE_NOT_ALLOWED: "FREE_DOWNGRADE_NOT_ALLOWED",
|
|
3815
|
+
PAYMENT_METHOD_REQUIRED: "PAYMENT_METHOD_REQUIRED"
|
|
3816
|
+
};
|
|
3644
3817
|
|
|
3645
3818
|
// src/client/ffid-client.ts
|
|
3646
3819
|
var UNAUTHORIZED_STATUS2 = 401;
|
|
@@ -4324,6 +4497,15 @@ function FFIDProvider({
|
|
|
4324
4497
|
}),
|
|
4325
4498
|
[serviceCode, scope, apiBaseUrl, debug, logger, authMode, clientId, timeout, tokenStore]
|
|
4326
4499
|
);
|
|
4500
|
+
react.useEffect(() => {
|
|
4501
|
+
const captured = captureAgencyInviteToken();
|
|
4502
|
+
if (captured.persisted === false) {
|
|
4503
|
+
client.logger.debug(
|
|
4504
|
+
"[FFID SDK] \u62DB\u5F85\u30C8\u30FC\u30AF\u30F3\u306E cookie \u4FDD\u6301\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF08\u5225\u30DA\u30FC\u30B8\u3078\u56DE\u904A\u3059\u308B\u3068\u4EE3\u7406\u5E97\u306E\u7D10\u4ED8\u3051\u304C\u5931\u308F\u308C\u307E\u3059\uFF09",
|
|
4505
|
+
{ source: captured.source }
|
|
4506
|
+
);
|
|
4507
|
+
}
|
|
4508
|
+
}, [client]);
|
|
4327
4509
|
const refresh = react.useCallback(async () => {
|
|
4328
4510
|
client.logger.debug("Refreshing session...");
|
|
4329
4511
|
try {
|
|
@@ -6300,6 +6482,10 @@ function FFIDInquiryForm({
|
|
|
6300
6482
|
}
|
|
6301
6483
|
|
|
6302
6484
|
exports.ACCESS_GRANTING_EFFECTIVE_STATUSES = ACCESS_GRANTING_EFFECTIVE_STATUSES;
|
|
6485
|
+
exports.AGENCY_INVITE_COOKIE_MAX_AGE_SEC = AGENCY_INVITE_COOKIE_MAX_AGE_SEC;
|
|
6486
|
+
exports.AGENCY_INVITE_COOKIE_NAME = AGENCY_INVITE_COOKIE_NAME;
|
|
6487
|
+
exports.AGENCY_INVITE_TOKEN_PARAM = AGENCY_INVITE_TOKEN_PARAM;
|
|
6488
|
+
exports.AGENCY_INVITE_TOKEN_REDACTION = AGENCY_INVITE_TOKEN_REDACTION;
|
|
6303
6489
|
exports.BLOCKING_EFFECTIVE_STATUSES = BLOCKING_EFFECTIVE_STATUSES;
|
|
6304
6490
|
exports.CATALOG_HASH_PATTERN = CATALOG_HASH_PATTERN;
|
|
6305
6491
|
exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL;
|
|
@@ -6328,8 +6514,11 @@ exports.FFID_INQUIRY_CATEGORIES_SITE_2026 = FFID_INQUIRY_CATEGORIES_SITE_2026;
|
|
|
6328
6514
|
exports.FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES = FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_BYTES;
|
|
6329
6515
|
exports.FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB = FFID_MAX_ORGANIZATION_LOGO_FILE_SIZE_MB;
|
|
6330
6516
|
exports.FFID_ORGANIZATION_PROFILE_ERROR_CODES = FFID_ORGANIZATION_PROFILE_ERROR_CODES;
|
|
6517
|
+
exports.FFID_SUBSCRIPTION_ERROR_CODES = FFID_SUBSCRIPTION_ERROR_CODES;
|
|
6331
6518
|
exports.canonicalizeCatalogValue = canonicalizeCatalogValue;
|
|
6519
|
+
exports.captureAgencyInviteToken = captureAgencyInviteToken;
|
|
6332
6520
|
exports.cleanupStateStorage = cleanupStateStorage;
|
|
6521
|
+
exports.clearAgencyInviteToken = clearAgencyInviteToken;
|
|
6333
6522
|
exports.computeEffectiveStatusFromSession = computeEffectiveStatusFromSession;
|
|
6334
6523
|
exports.computeFFIDCheckoutCopyHash = computeFFIDCheckoutCopyHash;
|
|
6335
6524
|
exports.computeLegalDisclosureHash = computeLegalDisclosureHash;
|
|
@@ -6349,6 +6538,9 @@ exports.isBlockedFromUserinfo = isBlockedFromUserinfo;
|
|
|
6349
6538
|
exports.isFFIDInquiryAcquisitionSource = isFFIDInquiryAcquisitionSource;
|
|
6350
6539
|
exports.isFFIDInquiryCategorySite2026 = isFFIDInquiryCategorySite2026;
|
|
6351
6540
|
exports.normalizeRedirectUri = normalizeRedirectUri;
|
|
6541
|
+
exports.readAgencyInviteCookie = readAgencyInviteCookie;
|
|
6542
|
+
exports.readAgencyInviteToken = readAgencyInviteToken;
|
|
6543
|
+
exports.redactAgencyInviteToken = redactAgencyInviteToken;
|
|
6352
6544
|
exports.retrieveCodeVerifier = retrieveCodeVerifier;
|
|
6353
6545
|
exports.retrieveState = retrieveState;
|
|
6354
6546
|
exports.sha256Hex = sha256Hex;
|