@feelflow/ffid-sdk 4.3.0 → 5.0.1

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.
@@ -1,6 +1,7 @@
1
1
  import { F as FFIDLogger, a as FFIDError, b as FFIDCacheAdapter, c as FFIDVerifyAccessTokenOptions, d as FFIDApiResponse, e as FFIDOAuthUserInfo } from '../ffid-client-BaStAONh.js';
2
2
  export { f as FFIDAddMemberParams, g as FFIDAddMemberRequest, h as FFIDAddMemberResponse, i as FFIDAssignableMemberRole, j as FFIDCacheConfig, k as FFIDClient, l as FFIDConfig, m as FFIDListMembersResponse, n as FFIDMemberRole, o as FFIDOrganization, p as FFIDOrganizationMember, q as FFIDOtpSendResponse, r as FFIDOtpVerifyResponse, s as FFIDPasswordResetConfirmResponse, t as FFIDPasswordResetResponse, u as FFIDPasswordResetVerifyResponse, v as FFIDProfileCallOptions, w as FFIDRemoveMemberResponse, x as FFIDResetSessionResponse, y as FFIDSubscription, z as FFIDUpdateMemberRoleResponse, A as FFIDUpdateUserProfileRequest, B as FFIDUser, C as FFIDUserProfile, T as TokenData, D as TokenStore, E as createFFIDClient, G as createTokenStore } from '../ffid-client-BaStAONh.js';
3
3
  export { D as DEFAULT_API_BASE_URL, a as DEFAULT_OAUTH_SCOPES } from '../constants-D61jqRIO.js';
4
+ import { z } from 'zod';
4
5
  import '../types-5g_Bg6Ey.js';
5
6
 
6
7
  /** Token verification - verifyAccessToken() implementation */
@@ -57,4 +58,151 @@ interface KVNamespaceLike {
57
58
  */
58
59
  declare function createKVCacheAdapter(kv: KVNamespaceLike): FFIDCacheAdapter;
59
60
 
60
- export { FFIDCacheAdapter, FFIDOAuthUserInfo, FFIDVerifyAccessTokenOptions, type KVNamespaceLike, createKVCacheAdapter, createMemoryCacheAdapter, createVerifyAccessToken };
61
+ /**
62
+ * Cookie Consent — Zod schemas (canonical source of truth).
63
+ *
64
+ * Two layers of schemas live in this file:
65
+ *
66
+ * 1. **SDK consumer-facing schemas** (camelCase, discriminated union). These
67
+ * are what `useFFIDConsent()` / `useFFIDConsentPreferences()` surface to
68
+ * React code, and what spec §6.4 documents as the SDK public contract.
69
+ *
70
+ * 2. **Wire schemas** (`*WireSchema`, snake_case envelopes). These mirror the
71
+ * actual FFID API responses from `src/app/api/v1/ext/consent/*`, which
72
+ * follow the FFID server-side convention of snake_case body fields plus
73
+ * nullable `consent` envelopes. `consent-client.ts` parses responses with
74
+ * these and then maps to the consumer-facing types via `mappers.ts`.
75
+ *
76
+ * TypeScript types are derived in `types.ts` via `z.infer<>`.
77
+ * `as` casting is forbidden; cross-trust-boundary data must be safe-parsed.
78
+ *
79
+ * Refs: docs/02-design/COOKIE_CONSENT.md §6.4 + §5
80
+ */
81
+
82
+ declare const FFIDConsentCategoryCodeSchema: z.ZodEnum<{
83
+ necessary: "necessary";
84
+ functional: "functional";
85
+ analytics: "analytics";
86
+ marketing: "marketing";
87
+ }>;
88
+ /**
89
+ * Consent state per category. `necessary` is enforced to be `true` at both
90
+ * type and runtime levels (`z.literal(true)`). The DB CHECK constraint
91
+ * `ucc_necessary_always_true` mirrors this on the storage side.
92
+ */
93
+ declare const FFIDConsentCategoriesSchema: z.ZodObject<{
94
+ necessary: z.ZodLiteral<true>;
95
+ functional: z.ZodBoolean;
96
+ analytics: z.ZodBoolean;
97
+ marketing: z.ZodBoolean;
98
+ }, z.core.$strip>;
99
+
100
+ /**
101
+ * Cookie Consent — TypeScript types derived from Zod schemas.
102
+ *
103
+ * Always derive types via `z.infer<>`; never hand-roll TypeScript types that
104
+ * could drift from the canonical Zod schema. Refs: schemas.ts
105
+ */
106
+
107
+ type FFIDConsentCategoryCode = z.infer<typeof FFIDConsentCategoryCodeSchema>;
108
+ type FFIDConsentCategories = z.infer<typeof FFIDConsentCategoriesSchema>;
109
+
110
+ /**
111
+ * Server-side cookie consent helpers for RSC / Server Actions.
112
+ *
113
+ * These are intentionally cookie-only (no DB / API calls) so they remain
114
+ * cheap for every request. The semantics mirror `readConsentCookie()` on the
115
+ * client side — fail-safe to `ALL_DENIED_EXCEPT_NECESSARY` on any error.
116
+ *
117
+ * @example
118
+ * ```tsx
119
+ * // app/page.tsx (Next.js App Router)
120
+ * import { cookies } from 'next/headers'
121
+ * import { getFFIDConsentFromCookies } from '@feelflow/ffid-sdk/server'
122
+ *
123
+ * export default async function Page() {
124
+ * const cookieStore = await cookies()
125
+ * const consent = getFFIDConsentFromCookies(cookieStore)
126
+ * if (consent.analytics) { ... }
127
+ * }
128
+ * ```
129
+ *
130
+ * Refs: docs/02-design/COOKIE_CONSENT.md §6.6
131
+ */
132
+
133
+ /**
134
+ * Structural type that matches both `next/headers` `cookies()` return value
135
+ * (`RequestCookies`) and any custom store with a `get(name)` method that
136
+ * returns `{ value }` or `undefined`.
137
+ */
138
+ interface FFIDCookieStoreLike {
139
+ get(name: string): {
140
+ value: string;
141
+ } | undefined;
142
+ }
143
+ /**
144
+ * Read the FFID consent cookie from a Next.js `cookies()` store (or any
145
+ * compatible object) and decode it.
146
+ *
147
+ * Returns `ALL_DENIED_EXCEPT_NECESSARY` when the cookie is absent,
148
+ * malformed, or the store is undefined — `decodeConsentCookie` never throws.
149
+ */
150
+ declare function getFFIDConsentFromCookies(store: FFIDCookieStoreLike | undefined | null): FFIDConsentCategories;
151
+ /**
152
+ * Parse a raw cookie header string (`document.cookie` shape: "k=v; k2=v2").
153
+ * Useful when working with raw Request headers in Edge runtimes that don't
154
+ * expose a `cookies()` API.
155
+ */
156
+ declare function getFFIDConsentFromCookieHeader(cookieHeader: string | null | undefined): FFIDConsentCategories;
157
+
158
+ /**
159
+ * Cookie Consent codec — encode / decode the `ffid_consent` 1st-party cookie.
160
+ *
161
+ * Format: `v1:n+,f+,a-,m+`
162
+ * - `v1:` version prefix (forward-compat for v2+)
163
+ * - n=necessary, f=functional, a=analytics, m=marketing
164
+ * - `+` (granted) / `-` (denied)
165
+ *
166
+ * **Safe-parse contract**: `decodeConsentCookie` NEVER throws. Any malformed,
167
+ * truncated, future-version, or otherwise unparseable input returns
168
+ * `ALL_DENIED_EXCEPT_NECESSARY`. Callers are responsible for logging via
169
+ * `cookie_consent_parse_failed` errorId if needed.
170
+ *
171
+ * This codec is **byte-compatible** with the FFID server-side implementation
172
+ * (`src/lib/cookie-consent/codec.ts`). Both implementations must produce and
173
+ * accept the identical wire format — a roundtrip through either codec must
174
+ * yield the same bytes.
175
+ *
176
+ * Refs: docs/02-design/COOKIE_CONSENT.md §6.5
177
+ */
178
+
179
+ declare const COOKIE_VERSION: "v1";
180
+ declare const ALL_DENIED_EXCEPT_NECESSARY: FFIDConsentCategories;
181
+ declare function encodeConsentCookie(cats: FFIDConsentCategories): string;
182
+ /**
183
+ * Safely decode the consent cookie string.
184
+ * Returns `ALL_DENIED_EXCEPT_NECESSARY` on any failure (fail-safe).
185
+ */
186
+ declare function decodeConsentCookie(raw: string | null | undefined): FFIDConsentCategories;
187
+
188
+ /**
189
+ * Cookie Consent — browser cookie read/write with read-back verification.
190
+ *
191
+ * Spec §6.5:
192
+ * - Key: `ffid_consent`
193
+ * - 1st-party, SameSite=Lax, Secure (production only — localhost dev uses
194
+ * non-Secure)
195
+ * - TTL: 1 year
196
+ * - Write must be verified by read-back; failure -> caller surfaces
197
+ * `cookie_consent_cookie_write_failed` and falls back to sessionStorage.
198
+ *
199
+ * This module is SSR-safe: all functions become no-ops when `document` is
200
+ * unavailable (server-side rendering), so calling code does not need to
201
+ * `typeof window` check itself.
202
+ *
203
+ * Refs: docs/02-design/COOKIE_CONSENT.md §6.5
204
+ */
205
+
206
+ declare const CONSENT_COOKIE_NAME = "ffid_consent";
207
+
208
+ export { ALL_DENIED_EXCEPT_NECESSARY, CONSENT_COOKIE_NAME, COOKIE_VERSION, FFIDCacheAdapter, type FFIDConsentCategories, type FFIDConsentCategoryCode, type FFIDCookieStoreLike, FFIDOAuthUserInfo, FFIDVerifyAccessTokenOptions, type KVNamespaceLike, createKVCacheAdapter, createMemoryCacheAdapter, createVerifyAccessToken, decodeConsentCookie, encodeConsentCookie, getFFIDConsentFromCookieHeader, getFFIDConsentFromCookies };
@@ -834,7 +834,7 @@ function createProfileMethods(deps) {
834
834
  }
835
835
 
836
836
  // src/client/version-check.ts
837
- var SDK_VERSION = "4.3.0";
837
+ var SDK_VERSION = "5.0.1";
838
838
  var SDK_USER_AGENT = `FFID-SDK/${SDK_VERSION} (TypeScript)`;
839
839
  var SDK_VERSION_HEADER = "X-FFID-SDK-Version";
840
840
  function sdkHeaders() {
@@ -2759,4 +2759,95 @@ function createKVCacheAdapter(kv) {
2759
2759
  };
2760
2760
  }
2761
2761
 
2762
- export { createFFIDClient, createKVCacheAdapter, createMemoryCacheAdapter, createTokenStore, createVerifyAccessToken };
2762
+ // src/consent/storage/encode.ts
2763
+ var COOKIE_VERSION = "v1";
2764
+ var ALL_DENIED_EXCEPT_NECESSARY = {
2765
+ necessary: true,
2766
+ functional: false,
2767
+ analytics: false,
2768
+ marketing: false
2769
+ };
2770
+ var CATEGORY_LETTERS = {
2771
+ n: "necessary",
2772
+ f: "functional",
2773
+ a: "analytics",
2774
+ m: "marketing"
2775
+ };
2776
+ var ORDERED_LETTERS = ["n", "f", "a", "m"];
2777
+ function encodeConsentCookie(cats) {
2778
+ const parts = ORDERED_LETTERS.map((letter) => {
2779
+ const key = CATEGORY_LETTERS[letter];
2780
+ return `${letter}${cats[key] ? "+" : "-"}`;
2781
+ });
2782
+ return `${COOKIE_VERSION}:${parts.join(",")}`;
2783
+ }
2784
+ function decodeConsentCookie(raw) {
2785
+ if (typeof raw !== "string" || raw.length === 0) {
2786
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2787
+ }
2788
+ const colonIdx = raw.indexOf(":");
2789
+ if (colonIdx === -1) {
2790
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2791
+ }
2792
+ const version = raw.slice(0, colonIdx);
2793
+ if (version !== COOKIE_VERSION) {
2794
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2795
+ }
2796
+ const body = raw.slice(colonIdx + 1);
2797
+ if (body.length === 0) {
2798
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2799
+ }
2800
+ const tokens = body.split(",");
2801
+ if (tokens.length !== ORDERED_LETTERS.length) {
2802
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2803
+ }
2804
+ const seen = /* @__PURE__ */ new Set();
2805
+ const result = {};
2806
+ for (const token of tokens) {
2807
+ if (token.length !== 2) {
2808
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2809
+ }
2810
+ const letter = token[0];
2811
+ const value = token[1];
2812
+ if (!CATEGORY_LETTERS[letter] || value !== "+" && value !== "-") {
2813
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2814
+ }
2815
+ if (seen.has(letter)) {
2816
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2817
+ }
2818
+ seen.add(letter);
2819
+ const key = CATEGORY_LETTERS[letter];
2820
+ result[key] = value === "+";
2821
+ }
2822
+ if (result.necessary !== true) {
2823
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2824
+ }
2825
+ return {
2826
+ necessary: true,
2827
+ functional: result.functional ?? false,
2828
+ analytics: result.analytics ?? false,
2829
+ marketing: result.marketing ?? false
2830
+ };
2831
+ }
2832
+
2833
+ // src/consent/storage/cookie.ts
2834
+ var CONSENT_COOKIE_NAME = "ffid_consent";
2835
+
2836
+ // src/server/cookie-consent.ts
2837
+ function getFFIDConsentFromCookies(store) {
2838
+ if (!store) return { ...ALL_DENIED_EXCEPT_NECESSARY };
2839
+ const entry = store.get(CONSENT_COOKIE_NAME);
2840
+ if (!entry) return { ...ALL_DENIED_EXCEPT_NECESSARY };
2841
+ return decodeConsentCookie(entry.value);
2842
+ }
2843
+ function getFFIDConsentFromCookieHeader(cookieHeader) {
2844
+ if (typeof cookieHeader !== "string" || cookieHeader.length === 0) {
2845
+ return { ...ALL_DENIED_EXCEPT_NECESSARY };
2846
+ }
2847
+ const raw = cookieHeader.split(";").map((c) => c.trim()).find((c) => c.startsWith(`${CONSENT_COOKIE_NAME}=`));
2848
+ if (!raw) return { ...ALL_DENIED_EXCEPT_NECESSARY };
2849
+ const value = raw.slice(CONSENT_COOKIE_NAME.length + 1);
2850
+ return decodeConsentCookie(decodeURIComponent(value));
2851
+ }
2852
+
2853
+ export { ALL_DENIED_EXCEPT_NECESSARY, CONSENT_COOKIE_NAME, COOKIE_VERSION, createFFIDClient, createKVCacheAdapter, createMemoryCacheAdapter, createTokenStore, createVerifyAccessToken, decodeConsentCookie, encodeConsentCookie, getFFIDConsentFromCookieHeader, getFFIDConsentFromCookies };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feelflow/ffid-sdk",
3
- "version": "4.3.0",
3
+ "version": "5.0.1",
4
4
  "description": "FeelFlow ID Platform SDK for React/Next.js applications",
5
5
  "keywords": [
6
6
  "feelflow",
@@ -36,6 +36,11 @@
36
36
  "import": "./dist/components/index.js",
37
37
  "require": "./dist/components/index.cjs"
38
38
  },
39
+ "./consent": {
40
+ "types": "./dist/consent/index.d.ts",
41
+ "import": "./dist/consent/index.js",
42
+ "require": "./dist/consent/index.cjs"
43
+ },
39
44
  "./legal": {
40
45
  "types": "./dist/legal/index.d.ts",
41
46
  "import": "./dist/legal/index.js",
@@ -85,7 +90,8 @@
85
90
  "sdk:publish": "./scripts/publish.sh"
86
91
  },
87
92
  "dependencies": {
88
- "jose": "^6.0.0"
93
+ "jose": "^6.0.0",
94
+ "zod": "^4.4.3"
89
95
  },
90
96
  "peerDependencies": {
91
97
  "react": "^18.0.0 || ^19.0.0",