exodus-monetization 0.2.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/dist/react.cjs ADDED
@@ -0,0 +1,394 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var React = require('react');
5
+
6
+ function _interopNamespace(e) {
7
+ if (e && e.__esModule) return e;
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
25
+
26
+ // src/react.tsx
27
+
28
+ // src/errors.ts
29
+ var MonetizationError = class extends Error {
30
+ constructor(message, code, status, body = null) {
31
+ super(message);
32
+ this.name = "MonetizationError";
33
+ this.code = code;
34
+ this.status = status;
35
+ this.body = body;
36
+ }
37
+ };
38
+ function mapSatIssueError(err) {
39
+ switch (err) {
40
+ case "unauthorized":
41
+ return "unauthorized";
42
+ case "not_entitled":
43
+ return "not_entitled";
44
+ case "rate_limited":
45
+ return "rate_limited";
46
+ case "sat_jti_unavailable":
47
+ return "sat_store_unavailable";
48
+ default:
49
+ return "sat_issue_failed";
50
+ }
51
+ }
52
+ function errorFromSatResponse(status, body) {
53
+ const err = typeof body?.error === "string" ? body.error : void 0;
54
+ if (status === 401) {
55
+ return new MonetizationError("Session requise pour \xE9mettre un SAT.", "unauthorized", 401, body);
56
+ }
57
+ if (status === 403) {
58
+ return new MonetizationError("Action non autoris\xE9e (entitlement ou r\xE8gle serveur).", "forbidden", 403, body);
59
+ }
60
+ if (status === 429) {
61
+ return new MonetizationError("Trop de demandes de SAT.", "rate_limited", 429, body);
62
+ }
63
+ if (status === 400) {
64
+ return new MonetizationError(err || "Requ\xEAte SAT invalide.", "bad_request", 400, body);
65
+ }
66
+ if (status === 503) {
67
+ return new MonetizationError("Stockage SAT indisponible.", "sat_store_unavailable", 503, body);
68
+ }
69
+ const code = mapSatIssueError(err);
70
+ return new MonetizationError(err || "\xC9mission SAT \xE9chou\xE9e.", code, status || 500, body);
71
+ }
72
+ function errorFromEntitlementsResponse(status, body) {
73
+ if (status === 401) {
74
+ return new MonetizationError("Non authentifi\xE9.", "unauthorized", 401, body);
75
+ }
76
+ return new MonetizationError("Erreur entitlements.", "unknown", status, body);
77
+ }
78
+ function isMintRetryableError(e) {
79
+ if (e.code === "network") return true;
80
+ if (e.code === "sat_store_unavailable") return true;
81
+ if (e.code === "sat_issue_failed" && e.status >= 500) return true;
82
+ const raw = e.body;
83
+ const apiErr = typeof raw?.error === "string" ? raw.error : "";
84
+ if (apiErr === "sat_invalid" || apiErr === "sat_expired") return true;
85
+ return false;
86
+ }
87
+
88
+ // src/types.ts
89
+ var SAT_FEATURE_KEYS = [
90
+ "chat.send",
91
+ "chat.media",
92
+ "contacts.view",
93
+ "whatsapp.handoff",
94
+ "demo.secure_read"
95
+ ];
96
+
97
+ // src/features.ts
98
+ var KNOWN = new Set(SAT_FEATURE_KEYS);
99
+ function validateFeatureKey(feature) {
100
+ if (typeof feature !== "string" || !KNOWN.has(feature)) {
101
+ throw new MonetizationError(
102
+ `Invalid SAT feature key "${String(feature)}". Expected one of SAT_FEATURE_KEYS.`,
103
+ "bad_request",
104
+ 400,
105
+ { feature, allowed: [...SAT_FEATURE_KEYS] }
106
+ );
107
+ }
108
+ }
109
+
110
+ // src/paths.ts
111
+ function pathnameForSat(input) {
112
+ const raw = (input ?? "").trim();
113
+ if (!raw) return "";
114
+ if (raw.startsWith("http://") || raw.startsWith("https://")) {
115
+ try {
116
+ return new URL(raw).pathname.replace(/\/{2,}/g, "/");
117
+ } catch {
118
+ return "";
119
+ }
120
+ }
121
+ const noHash = raw.split("#")[0] ?? raw;
122
+ const noQuery = (noHash.split("?")[0] ?? noHash).trim();
123
+ if (!noQuery.startsWith("/")) return "";
124
+ if (noQuery.startsWith("//")) return "";
125
+ if (noQuery.includes("://")) return "";
126
+ return noQuery.replace(/\/{2,}/g, "/");
127
+ }
128
+ function joinBaseUrl(baseUrl, path) {
129
+ const b = (baseUrl ?? "").replace(/\/$/, "");
130
+ const p = path.startsWith("/") ? path : `/${path}`;
131
+ if (!b) return p;
132
+ return `${b}${p}`;
133
+ }
134
+ function resolveRequestUrl(input, baseUrl) {
135
+ if (typeof input === "string") {
136
+ if (input.startsWith("http://") || input.startsWith("https://")) return input;
137
+ return joinBaseUrl(baseUrl, input);
138
+ }
139
+ if (input instanceof URL) return input.href;
140
+ return input.url;
141
+ }
142
+
143
+ // src/client.ts
144
+ function safeEmit(onEvent, e) {
145
+ try {
146
+ onEvent?.(e);
147
+ } catch {
148
+ }
149
+ }
150
+ async function readJsonBody(res) {
151
+ return await res.json().catch(() => null);
152
+ }
153
+ function createMonetizationClient(config = {}) {
154
+ const baseUrl = (config.baseUrl ?? "").trim().replace(/\/$/, "");
155
+ const credentials = config.credentials ?? "include";
156
+ const fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis);
157
+ const cacheTtl = config.entitlementsCacheTtlMs ?? 12e3;
158
+ const staleAfterFreshMs = config.entitlementsStaleAfterFreshMs ?? Math.min((cacheTtl > 0 ? cacheTtl : 12e3) * 2, 12e4);
159
+ let entitlementsCache = null;
160
+ let inflightRefresh = null;
161
+ const onEvent = config.onEvent;
162
+ function mergeHeaders(init) {
163
+ const h = new Headers(config.defaultHeaders);
164
+ if (init) new Headers(init).forEach((v, k) => h.set(k, v));
165
+ return h;
166
+ }
167
+ async function fetchEntitlementsFresh() {
168
+ const url = joinBaseUrl(baseUrl, "/api/entitlements");
169
+ let res;
170
+ try {
171
+ res = await fetchImpl(url, {
172
+ method: "GET",
173
+ credentials,
174
+ cache: "no-store",
175
+ headers: mergeHeaders()
176
+ });
177
+ } catch {
178
+ throw new MonetizationError("R\xE9seau indisponible.", "network", 0, null);
179
+ }
180
+ const body = await res.json().catch(() => null);
181
+ if (!res.ok || !body || body.ok !== true) {
182
+ throw errorFromEntitlementsResponse(res.status, body);
183
+ }
184
+ return body;
185
+ }
186
+ async function getEntitlements(options) {
187
+ if (options?.skipCache || cacheTtl <= 0) {
188
+ const data2 = await fetchEntitlementsFresh();
189
+ if (cacheTtl > 0) entitlementsCache = { data: data2, fetchedAt: Date.now() };
190
+ return data2;
191
+ }
192
+ const now = Date.now();
193
+ if (!entitlementsCache) {
194
+ const data2 = await fetchEntitlementsFresh();
195
+ entitlementsCache = { data: data2, fetchedAt: now };
196
+ return data2;
197
+ }
198
+ const age = now - entitlementsCache.fetchedAt;
199
+ if (age < cacheTtl) {
200
+ return entitlementsCache.data;
201
+ }
202
+ if (age < cacheTtl + staleAfterFreshMs) {
203
+ if (!inflightRefresh) {
204
+ inflightRefresh = fetchEntitlementsFresh().then((data2) => {
205
+ entitlementsCache = { data: data2, fetchedAt: Date.now() };
206
+ }).catch(() => {
207
+ }).finally(() => {
208
+ inflightRefresh = null;
209
+ });
210
+ }
211
+ return entitlementsCache.data;
212
+ }
213
+ const data = await fetchEntitlementsFresh();
214
+ entitlementsCache = { data, fetchedAt: Date.now() };
215
+ return data;
216
+ }
217
+ function invalidateEntitlementsCache() {
218
+ entitlementsCache = null;
219
+ inflightRefresh = null;
220
+ }
221
+ async function mintSatOnce(params) {
222
+ const path = pathnameForSat(params.path);
223
+ if (!path) {
224
+ throw new MonetizationError("Chemin SAT invalide.", "bad_request", 400, null);
225
+ }
226
+ const url = joinBaseUrl(baseUrl, "/api/sat");
227
+ const method = params.method.toUpperCase();
228
+ let res;
229
+ try {
230
+ res = await fetchImpl(url, {
231
+ method: "POST",
232
+ credentials,
233
+ headers: mergeHeaders({ "content-type": "application/json" }),
234
+ body: JSON.stringify({
235
+ feature: params.feature,
236
+ method,
237
+ path
238
+ })
239
+ });
240
+ } catch {
241
+ throw new MonetizationError("R\xE9seau indisponible.", "network", 0, null);
242
+ }
243
+ const body = await res.json().catch(() => null);
244
+ if (!body || body.ok !== true || typeof body.token !== "string") {
245
+ throw errorFromSatResponse(res.status, body);
246
+ }
247
+ const exp = typeof body.exp === "number" ? body.exp : 0;
248
+ return { token: body.token, exp };
249
+ }
250
+ async function mintSat(params) {
251
+ validateFeatureKey(params.feature);
252
+ try {
253
+ return await mintSatOnce(params);
254
+ } catch (e) {
255
+ if (e instanceof MonetizationError && isMintRetryableError(e)) {
256
+ safeEmit(onEvent, {
257
+ type: "sat_mint_retried",
258
+ feature: params.feature,
259
+ reason: e.code
260
+ });
261
+ try {
262
+ return await mintSatOnce(params);
263
+ } catch (e2) {
264
+ if (e2 instanceof MonetizationError) {
265
+ safeEmit(onEvent, {
266
+ type: "sat_mint_failed",
267
+ feature: params.feature,
268
+ code: e2.code,
269
+ status: e2.status
270
+ });
271
+ }
272
+ throw e2;
273
+ }
274
+ }
275
+ if (e instanceof MonetizationError) {
276
+ safeEmit(onEvent, {
277
+ type: "sat_mint_failed",
278
+ feature: params.feature,
279
+ code: e.code,
280
+ status: e.status
281
+ });
282
+ }
283
+ throw e;
284
+ }
285
+ }
286
+ async function guardedFetch(input, init = {}) {
287
+ const { feature, ...rest } = init;
288
+ if (!feature) {
289
+ if (typeof input === "string") {
290
+ const u = input.startsWith("http://") || input.startsWith("https://") ? input : joinBaseUrl(baseUrl, input);
291
+ return fetchImpl(u, {
292
+ ...rest,
293
+ credentials: rest.credentials ?? credentials,
294
+ headers: mergeHeaders(rest.headers)
295
+ });
296
+ }
297
+ if (input instanceof URL) {
298
+ return fetchImpl(input, {
299
+ ...rest,
300
+ credentials: rest.credentials ?? credentials,
301
+ headers: mergeHeaders(rest.headers)
302
+ });
303
+ }
304
+ return fetchImpl(input, {
305
+ ...rest,
306
+ credentials: rest.credentials ?? credentials,
307
+ headers: mergeHeaders(rest.headers)
308
+ });
309
+ }
310
+ validateFeatureKey(feature);
311
+ const targetUrl = resolveRequestUrl(input, baseUrl);
312
+ const method = (rest.method ?? "GET").toUpperCase();
313
+ const path = pathnameForSat(targetUrl);
314
+ if (!path) {
315
+ throw new MonetizationError("Impossible de d\xE9river le path pour le SAT.", "bad_request", 400, null);
316
+ }
317
+ async function doFetch(token) {
318
+ const headers = mergeHeaders(rest.headers);
319
+ headers.set("X-SAT", token);
320
+ return fetchImpl(targetUrl, {
321
+ ...rest,
322
+ headers,
323
+ credentials: rest.credentials ?? credentials
324
+ });
325
+ }
326
+ const first = await mintSat({ feature, method, path });
327
+ let res = await doFetch(first.token);
328
+ if (res.status === 403) {
329
+ const j = await readJsonBody(res.clone());
330
+ const err = j?.error ?? "";
331
+ if (err === "sat_invalid" || err === "sat_expired") {
332
+ safeEmit(onEvent, { type: "sat_action_retry", feature, path, reason: err });
333
+ const second = await mintSat({ feature, method, path });
334
+ res = await doFetch(second.token);
335
+ } else if (err === "sat_replay_or_expired" || err === "sat_feature_forbidden" || err === "sat_required") {
336
+ safeEmit(onEvent, {
337
+ type: "sat_action_blocked",
338
+ feature,
339
+ path,
340
+ reason: err,
341
+ status: res.status
342
+ });
343
+ }
344
+ }
345
+ return res;
346
+ }
347
+ async function protect(feature, method, pathOrUrl, run) {
348
+ validateFeatureKey(feature);
349
+ const path = pathnameForSat(pathOrUrl);
350
+ if (!path) {
351
+ throw new MonetizationError("Chemin SAT invalide.", "bad_request", 400, null);
352
+ }
353
+ const { token } = await mintSat({ feature, method: method.toUpperCase(), path });
354
+ const headers = mergeHeaders();
355
+ headers.set("X-SAT", token);
356
+ return run({ headers });
357
+ }
358
+ return {
359
+ config: {
360
+ baseUrl,
361
+ credentials,
362
+ entitlementsCacheTtlMs: cacheTtl,
363
+ entitlementsStaleAfterFreshMs: staleAfterFreshMs
364
+ },
365
+ getEntitlements,
366
+ invalidateEntitlementsCache,
367
+ mintSat,
368
+ guardedFetch,
369
+ protect
370
+ };
371
+ }
372
+
373
+ // src/react.tsx
374
+ function useMonetizationClient(cfg = {}) {
375
+ const baseUrl = cfg.baseUrl ?? "";
376
+ const credentials = cfg.credentials ?? "include";
377
+ const entitlementsCacheTtlMs = cfg.entitlementsCacheTtlMs;
378
+ const entitlementsStaleAfterFreshMs = cfg.entitlementsStaleAfterFreshMs;
379
+ const onEvent = cfg.onEvent;
380
+ return React__namespace.useMemo(
381
+ () => createMonetizationClient({
382
+ baseUrl,
383
+ credentials,
384
+ entitlementsCacheTtlMs,
385
+ entitlementsStaleAfterFreshMs,
386
+ onEvent
387
+ }),
388
+ [baseUrl, credentials, entitlementsCacheTtlMs, entitlementsStaleAfterFreshMs, onEvent]
389
+ );
390
+ }
391
+
392
+ exports.useMonetizationClient = useMonetizationClient;
393
+ //# sourceMappingURL=react.cjs.map
394
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/types.ts","../src/features.ts","../src/paths.ts","../src/client.ts","../src/react.tsx"],"names":["data","React"],"mappings":"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","file":"react.cjs","sourcesContent":["export type MonetizationErrorCode =\n | \"unauthorized\"\n | \"forbidden\"\n | \"not_entitled\"\n | \"rate_limited\"\n | \"bad_request\"\n | \"sat_issue_failed\"\n | \"sat_store_unavailable\"\n | \"network\"\n | \"unknown\";\n\nexport class MonetizationError extends Error {\n readonly code: MonetizationErrorCode;\n readonly status: number;\n readonly body: unknown;\n\n constructor(message: string, code: MonetizationErrorCode, status: number, body: unknown = null) {\n super(message);\n this.name = \"MonetizationError\";\n this.code = code;\n this.status = status;\n this.body = body;\n }\n}\n\nfunction mapSatIssueError(err: string | undefined): MonetizationErrorCode {\n switch (err) {\n case \"unauthorized\":\n return \"unauthorized\";\n case \"not_entitled\":\n return \"not_entitled\";\n case \"rate_limited\":\n return \"rate_limited\";\n case \"sat_jti_unavailable\":\n return \"sat_store_unavailable\";\n default:\n return \"sat_issue_failed\";\n }\n}\n\nexport function errorFromSatResponse(status: number, body: Record<string, unknown> | null): MonetizationError {\n const err = typeof body?.error === \"string\" ? body.error : undefined;\n if (status === 401) {\n return new MonetizationError(\"Session requise pour émettre un SAT.\", \"unauthorized\", 401, body);\n }\n if (status === 403) {\n return new MonetizationError(\"Action non autorisée (entitlement ou règle serveur).\", \"forbidden\", 403, body);\n }\n if (status === 429) {\n return new MonetizationError(\"Trop de demandes de SAT.\", \"rate_limited\", 429, body);\n }\n if (status === 400) {\n return new MonetizationError(err || \"Requête SAT invalide.\", \"bad_request\", 400, body);\n }\n if (status === 503) {\n return new MonetizationError(\"Stockage SAT indisponible.\", \"sat_store_unavailable\", 503, body);\n }\n const code = mapSatIssueError(err);\n return new MonetizationError(err || \"Émission SAT échouée.\", code, status || 500, body);\n}\n\nexport function errorFromEntitlementsResponse(status: number, body: unknown): MonetizationError {\n if (status === 401) {\n return new MonetizationError(\"Non authentifié.\", \"unauthorized\", 401, body);\n }\n return new MonetizationError(\"Erreur entitlements.\", \"unknown\", status, body);\n}\n\n/** Une seconde tentative de mint SAT est pertinente (transient / horloge / invalid recoverable). */\nexport function isMintRetryableError(e: MonetizationError): boolean {\n if (e.code === \"network\") return true;\n if (e.code === \"sat_store_unavailable\") return true;\n if (e.code === \"sat_issue_failed\" && e.status >= 500) return true;\n\n const raw = e.body as Record<string, unknown> | null;\n const apiErr = typeof raw?.error === \"string\" ? raw.error : \"\";\n if (apiErr === \"sat_invalid\" || apiErr === \"sat_expired\") return true;\n\n return false;\n}\n","/**\n * Clés acceptées par POST /api/sat côté serveur Exodus (whitelist API).\n * Toute autre valeur est refusée par le serveur.\n */\nexport const SAT_FEATURE_KEYS = [\n \"chat.send\",\n \"chat.media\",\n \"contacts.view\",\n \"whatsapp.handoff\",\n \"demo.secure_read\",\n] as const;\n\nexport type SatFeatureKey = (typeof SAT_FEATURE_KEYS)[number];\n\n/** Réponse 200 de GET /api/entitlements */\nexport type EntitlementsGetResponse = {\n ok: true;\n claim: {\n sub: string;\n plan: { key: string; name: string; active: boolean };\n features: string[];\n sid: string;\n device: string;\n iat: number;\n exp: number;\n jti: string;\n ver: number;\n ev?: unknown;\n };\n token: string | null;\n};\n\nexport type EntitlementsGetErrorBody = {\n ok: false;\n error: string;\n};\n\n/** Observabilité côté client (analytics / debug) — ne remplace pas les logs serveur. */\nexport type MonetizationSdkEvent =\n | { type: \"sat_mint_failed\"; feature: string; code: string; status: number }\n | { type: \"sat_mint_retried\"; feature: string; reason: string }\n | { type: \"sat_action_retry\"; feature: string; path: string; reason: string }\n | { type: \"sat_action_blocked\"; feature: string; path: string; reason: string; status: number };\n\nexport type MonetizationClientConfig = {\n /**\n * Origine de l’app (ex. https://app.exemple.com). Chaîne vide = URLs relatives (même origine).\n */\n baseUrl?: string;\n credentials?: RequestCredentials;\n /** TTL « frais » cache GET /api/entitlements (0 = pas de cache). */\n entitlementsCacheTtlMs?: number;\n /**\n * Fenêtre stale-after-fresh : pendant ce délai après l’échéance « fraîche »,\n * la dernière copie est renvoyée immédiatement et un rafraîchissement async part en arrière-plan.\n * Défaut : min(2 × fresh, 120_000 ms).\n */\n entitlementsStaleAfterFreshMs?: number;\n /** Surcharge fetch (tests, SSR avec cookies explicites). */\n fetchImpl?: typeof fetch;\n /** Headers ajoutés à chaque appel (ex. tracing). */\n defaultHeaders?: HeadersInit;\n /** Hook optionnel produit / télémétrie (SAT mint échoué, retry, action bloquée). */\n onEvent?: (event: MonetizationSdkEvent) => void;\n};\n\nexport type MintSatResult = {\n token: string;\n exp: number;\n};\n\nexport type GuardedRequestInit = RequestInit & {\n /** Si défini, un SAT est résolu via POST /api/sat avant la requête. */\n feature?: SatFeatureKey;\n};\n","import { MonetizationError } from \"./errors\";\nimport { SAT_FEATURE_KEYS, type SatFeatureKey } from \"./types\";\n\nconst KNOWN = new Set<string>(SAT_FEATURE_KEYS);\n\n/** Fail-fast : la clé doit être dans la whitelist alignée sur `POST /api/sat`. */\nexport function validateFeatureKey(feature: unknown): asserts feature is SatFeatureKey {\n if (typeof feature !== \"string\" || !KNOWN.has(feature)) {\n throw new MonetizationError(\n `Invalid SAT feature key \"${String(feature)}\". Expected one of SAT_FEATURE_KEYS.`,\n \"bad_request\",\n 400,\n { feature, allowed: [...SAT_FEATURE_KEYS] },\n );\n }\n}\n\nexport function isSatFeatureKey(feature: string): feature is SatFeatureKey {\n return KNOWN.has(feature);\n}\n","/** URL absolue ou chemin relatif → pathname pour contrainte SAT (htu). */\nexport function pathnameForSat(input: string): string {\n const raw = (input ?? \"\").trim();\n if (!raw) return \"\";\n\n if (raw.startsWith(\"http://\") || raw.startsWith(\"https://\")) {\n try {\n return new URL(raw).pathname.replace(/\\/{2,}/g, \"/\");\n } catch {\n return \"\";\n }\n }\n\n const noHash = raw.split(\"#\")[0] ?? raw;\n const noQuery = (noHash.split(\"?\")[0] ?? noHash).trim();\n\n if (!noQuery.startsWith(\"/\")) return \"\";\n if (noQuery.startsWith(\"//\")) return \"\";\n if (noQuery.includes(\"://\")) return \"\";\n\n return noQuery.replace(/\\/{2,}/g, \"/\");\n}\n\nexport function joinBaseUrl(baseUrl: string, path: string): string {\n const b = (baseUrl ?? \"\").replace(/\\/$/, \"\");\n const p = path.startsWith(\"/\") ? path : `/${path}`;\n if (!b) return p;\n return `${b}${p}`;\n}\n\nexport function resolveRequestUrl(input: RequestInfo | URL, baseUrl: string): string {\n if (typeof input === \"string\") {\n if (input.startsWith(\"http://\") || input.startsWith(\"https://\")) return input;\n return joinBaseUrl(baseUrl, input);\n }\n if (input instanceof URL) return input.href;\n return input.url;\n}\n","import {\n errorFromEntitlementsResponse,\n errorFromSatResponse,\n isMintRetryableError,\n MonetizationError,\n type MonetizationErrorCode,\n} from \"./errors\";\nimport { validateFeatureKey } from \"./features\";\nimport { joinBaseUrl, pathnameForSat, resolveRequestUrl } from \"./paths\";\nimport type {\n EntitlementsGetResponse,\n GuardedRequestInit,\n MintSatResult,\n MonetizationClientConfig,\n MonetizationSdkEvent,\n SatFeatureKey,\n} from \"./types\";\n\nexport type MonetizationClient = ReturnType<typeof createMonetizationClient>;\n\nfunction safeEmit(onEvent: MonetizationClientConfig[\"onEvent\"], e: MonetizationSdkEvent) {\n try {\n onEvent?.(e);\n } catch {\n /* hook ne doit pas casser le client */\n }\n}\n\nasync function readJsonBody(res: Response): Promise<{ error?: string } | null> {\n return (await res.json().catch(() => null)) as { error?: string } | null;\n}\n\nexport function createMonetizationClient(config: MonetizationClientConfig = {}) {\n const baseUrl = (config.baseUrl ?? \"\").trim().replace(/\\/$/, \"\");\n const credentials = config.credentials ?? \"include\";\n const fetchImpl = config.fetchImpl ?? globalThis.fetch.bind(globalThis);\n const cacheTtl = config.entitlementsCacheTtlMs ?? 12_000;\n const staleAfterFreshMs =\n config.entitlementsStaleAfterFreshMs ?? Math.min((cacheTtl > 0 ? cacheTtl : 12_000) * 2, 120_000);\n\n let entitlementsCache: { data: EntitlementsGetResponse; fetchedAt: number } | null = null;\n let inflightRefresh: Promise<void> | null = null;\n const onEvent = config.onEvent;\n\n function mergeHeaders(init?: HeadersInit): Headers {\n const h = new Headers(config.defaultHeaders);\n if (init) new Headers(init).forEach((v, k) => h.set(k, v));\n return h;\n }\n\n async function fetchEntitlementsFresh(): Promise<EntitlementsGetResponse> {\n const url = joinBaseUrl(baseUrl, \"/api/entitlements\");\n let res: Response;\n try {\n res = await fetchImpl(url, {\n method: \"GET\",\n credentials,\n cache: \"no-store\",\n headers: mergeHeaders(),\n });\n } catch {\n throw new MonetizationError(\"Réseau indisponible.\", \"network\", 0, null);\n }\n\n const body = (await res.json().catch(() => null)) as EntitlementsGetResponse | { ok?: boolean; error?: string };\n\n if (!res.ok || !body || (body as { ok?: boolean }).ok !== true) {\n throw errorFromEntitlementsResponse(res.status, body);\n }\n\n return body as EntitlementsGetResponse;\n }\n\n async function getEntitlements(options?: { skipCache?: boolean }): Promise<EntitlementsGetResponse> {\n if (options?.skipCache || cacheTtl <= 0) {\n const data = await fetchEntitlementsFresh();\n if (cacheTtl > 0) entitlementsCache = { data, fetchedAt: Date.now() };\n return data;\n }\n\n const now = Date.now();\n\n if (!entitlementsCache) {\n const data = await fetchEntitlementsFresh();\n entitlementsCache = { data, fetchedAt: now };\n return data;\n }\n\n const age = now - entitlementsCache.fetchedAt;\n\n if (age < cacheTtl) {\n return entitlementsCache.data;\n }\n\n if (age < cacheTtl + staleAfterFreshMs) {\n if (!inflightRefresh) {\n inflightRefresh = fetchEntitlementsFresh()\n .then((data) => {\n entitlementsCache = { data, fetchedAt: Date.now() };\n })\n .catch(() => {\n /* garde le snapshot stale */\n })\n .finally(() => {\n inflightRefresh = null;\n });\n }\n return entitlementsCache.data;\n }\n\n const data = await fetchEntitlementsFresh();\n entitlementsCache = { data, fetchedAt: Date.now() };\n return data;\n }\n\n function invalidateEntitlementsCache() {\n entitlementsCache = null;\n inflightRefresh = null;\n }\n\n async function mintSatOnce(params: {\n feature: SatFeatureKey;\n method: string;\n path: string;\n }): Promise<MintSatResult> {\n const path = pathnameForSat(params.path);\n if (!path) {\n throw new MonetizationError(\"Chemin SAT invalide.\", \"bad_request\", 400, null);\n }\n\n const url = joinBaseUrl(baseUrl, \"/api/sat\");\n const method = params.method.toUpperCase();\n\n let res: Response;\n try {\n res = await fetchImpl(url, {\n method: \"POST\",\n credentials,\n headers: mergeHeaders({ \"content-type\": \"application/json\" }),\n body: JSON.stringify({\n feature: params.feature,\n method,\n path,\n }),\n });\n } catch {\n throw new MonetizationError(\"Réseau indisponible.\", \"network\", 0, null);\n }\n\n const body = (await res.json().catch(() => null)) as Record<string, unknown> | null;\n if (!body || body.ok !== true || typeof body.token !== \"string\") {\n throw errorFromSatResponse(res.status, body);\n }\n\n const exp = typeof body.exp === \"number\" ? body.exp : 0;\n return { token: body.token, exp };\n }\n\n async function mintSat(params: {\n feature: SatFeatureKey;\n method: string;\n path: string;\n }): Promise<MintSatResult> {\n validateFeatureKey(params.feature);\n try {\n return await mintSatOnce(params);\n } catch (e) {\n if (e instanceof MonetizationError && isMintRetryableError(e)) {\n safeEmit(onEvent, {\n type: \"sat_mint_retried\",\n feature: params.feature,\n reason: e.code,\n });\n try {\n return await mintSatOnce(params);\n } catch (e2) {\n if (e2 instanceof MonetizationError) {\n safeEmit(onEvent, {\n type: \"sat_mint_failed\",\n feature: params.feature,\n code: e2.code,\n status: e2.status,\n });\n }\n throw e2;\n }\n }\n if (e instanceof MonetizationError) {\n safeEmit(onEvent, {\n type: \"sat_mint_failed\",\n feature: params.feature,\n code: e.code,\n status: e.status,\n });\n }\n throw e;\n }\n }\n\n async function guardedFetch(input: RequestInfo | URL, init: GuardedRequestInit = {}): Promise<Response> {\n const { feature, ...rest } = init;\n\n if (!feature) {\n if (typeof input === \"string\") {\n const u = input.startsWith(\"http://\") || input.startsWith(\"https://\") ? input : joinBaseUrl(baseUrl, input);\n return fetchImpl(u, {\n ...rest,\n credentials: rest.credentials ?? credentials,\n headers: mergeHeaders(rest.headers as HeadersInit | undefined),\n });\n }\n if (input instanceof URL) {\n return fetchImpl(input, {\n ...rest,\n credentials: rest.credentials ?? credentials,\n headers: mergeHeaders(rest.headers as HeadersInit | undefined),\n });\n }\n return fetchImpl(input, {\n ...rest,\n credentials: rest.credentials ?? credentials,\n headers: mergeHeaders(rest.headers as HeadersInit | undefined),\n });\n }\n\n validateFeatureKey(feature);\n\n const targetUrl = resolveRequestUrl(input, baseUrl);\n const method = (rest.method ?? \"GET\").toUpperCase();\n const path = pathnameForSat(targetUrl);\n if (!path) {\n throw new MonetizationError(\"Impossible de dériver le path pour le SAT.\", \"bad_request\", 400, null);\n }\n\n async function doFetch(token: string) {\n const headers = mergeHeaders(rest.headers as HeadersInit | undefined);\n headers.set(\"X-SAT\", token);\n return fetchImpl(targetUrl, {\n ...rest,\n headers,\n credentials: rest.credentials ?? credentials,\n });\n }\n\n const first = await mintSat({ feature, method, path });\n let res = await doFetch(first.token);\n\n if (res.status === 403) {\n const j = await readJsonBody(res.clone());\n const err = j?.error ?? \"\";\n if (err === \"sat_invalid\" || err === \"sat_expired\") {\n safeEmit(onEvent, { type: \"sat_action_retry\", feature, path, reason: err });\n const second = await mintSat({ feature, method, path });\n res = await doFetch(second.token);\n } else if (err === \"sat_replay_or_expired\" || err === \"sat_feature_forbidden\" || err === \"sat_required\") {\n safeEmit(onEvent, {\n type: \"sat_action_blocked\",\n feature,\n path,\n reason: err,\n status: res.status,\n });\n }\n }\n\n return res;\n }\n\n async function protect<T>(\n feature: SatFeatureKey,\n method: string,\n pathOrUrl: string,\n run: (ctx: { headers: Headers }) => Promise<T>,\n ): Promise<T> {\n validateFeatureKey(feature);\n const path = pathnameForSat(pathOrUrl);\n if (!path) {\n throw new MonetizationError(\"Chemin SAT invalide.\", \"bad_request\", 400, null);\n }\n const { token } = await mintSat({ feature, method: method.toUpperCase(), path });\n const headers = mergeHeaders();\n headers.set(\"X-SAT\", token);\n return run({ headers });\n }\n\n return {\n config: {\n baseUrl,\n credentials,\n entitlementsCacheTtlMs: cacheTtl,\n entitlementsStaleAfterFreshMs: staleAfterFreshMs,\n } as const,\n getEntitlements,\n invalidateEntitlementsCache,\n mintSat,\n guardedFetch,\n protect,\n };\n}\n\nexport function isMonetizationError(e: unknown): e is MonetizationError {\n return e instanceof MonetizationError;\n}\n\nexport type { MonetizationErrorCode };\n","import * as React from \"react\";\nimport { createMonetizationClient, type MonetizationClient } from \"./client\";\nimport type { MonetizationClientConfig } from \"./types\";\n\ntype StableMonetizationConfig = Pick<\n MonetizationClientConfig,\n \"baseUrl\" | \"credentials\" | \"entitlementsCacheTtlMs\" | \"entitlementsStaleAfterFreshMs\" | \"onEvent\"\n>;\n\n/**\n * Stable browser client (primitive config deps only).\n * For custom `defaultHeaders` / `fetchImpl`, use `createMonetizationClient` outside the hook.\n */\nexport function useMonetizationClient(cfg: StableMonetizationConfig = {}): MonetizationClient {\n const baseUrl = cfg.baseUrl ?? \"\";\n const credentials = cfg.credentials ?? \"include\";\n const entitlementsCacheTtlMs = cfg.entitlementsCacheTtlMs;\n const entitlementsStaleAfterFreshMs = cfg.entitlementsStaleAfterFreshMs;\n const onEvent = cfg.onEvent;\n\n return React.useMemo(\n () =>\n createMonetizationClient({\n baseUrl,\n credentials,\n entitlementsCacheTtlMs,\n entitlementsStaleAfterFreshMs,\n onEvent,\n }),\n [baseUrl, credentials, entitlementsCacheTtlMs, entitlementsStaleAfterFreshMs, onEvent],\n );\n}\n"]}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Clés acceptées par POST /api/sat côté serveur Exodus (whitelist API).
3
+ * Toute autre valeur est refusée par le serveur.
4
+ */
5
+ declare const SAT_FEATURE_KEYS: readonly ["chat.send", "chat.media", "contacts.view", "whatsapp.handoff", "demo.secure_read"];
6
+ type SatFeatureKey = (typeof SAT_FEATURE_KEYS)[number];
7
+ /** Réponse 200 de GET /api/entitlements */
8
+ type EntitlementsGetResponse = {
9
+ ok: true;
10
+ claim: {
11
+ sub: string;
12
+ plan: {
13
+ key: string;
14
+ name: string;
15
+ active: boolean;
16
+ };
17
+ features: string[];
18
+ sid: string;
19
+ device: string;
20
+ iat: number;
21
+ exp: number;
22
+ jti: string;
23
+ ver: number;
24
+ ev?: unknown;
25
+ };
26
+ token: string | null;
27
+ };
28
+ /** Observabilité côté client (analytics / debug) — ne remplace pas les logs serveur. */
29
+ type MonetizationSdkEvent = {
30
+ type: "sat_mint_failed";
31
+ feature: string;
32
+ code: string;
33
+ status: number;
34
+ } | {
35
+ type: "sat_mint_retried";
36
+ feature: string;
37
+ reason: string;
38
+ } | {
39
+ type: "sat_action_retry";
40
+ feature: string;
41
+ path: string;
42
+ reason: string;
43
+ } | {
44
+ type: "sat_action_blocked";
45
+ feature: string;
46
+ path: string;
47
+ reason: string;
48
+ status: number;
49
+ };
50
+ type MonetizationClientConfig = {
51
+ /**
52
+ * Origine de l’app (ex. https://app.exemple.com). Chaîne vide = URLs relatives (même origine).
53
+ */
54
+ baseUrl?: string;
55
+ credentials?: RequestCredentials;
56
+ /** TTL « frais » cache GET /api/entitlements (0 = pas de cache). */
57
+ entitlementsCacheTtlMs?: number;
58
+ /**
59
+ * Fenêtre stale-after-fresh : pendant ce délai après l’échéance « fraîche »,
60
+ * la dernière copie est renvoyée immédiatement et un rafraîchissement async part en arrière-plan.
61
+ * Défaut : min(2 × fresh, 120_000 ms).
62
+ */
63
+ entitlementsStaleAfterFreshMs?: number;
64
+ /** Surcharge fetch (tests, SSR avec cookies explicites). */
65
+ fetchImpl?: typeof fetch;
66
+ /** Headers ajoutés à chaque appel (ex. tracing). */
67
+ defaultHeaders?: HeadersInit;
68
+ /** Hook optionnel produit / télémétrie (SAT mint échoué, retry, action bloquée). */
69
+ onEvent?: (event: MonetizationSdkEvent) => void;
70
+ };
71
+ type MintSatResult = {
72
+ token: string;
73
+ exp: number;
74
+ };
75
+ type GuardedRequestInit = RequestInit & {
76
+ /** Si défini, un SAT est résolu via POST /api/sat avant la requête. */
77
+ feature?: SatFeatureKey;
78
+ };
79
+
80
+ type MonetizationClient = ReturnType<typeof createMonetizationClient>;
81
+ declare function createMonetizationClient(config?: MonetizationClientConfig): {
82
+ config: {
83
+ readonly baseUrl: string;
84
+ readonly credentials: RequestCredentials;
85
+ readonly entitlementsCacheTtlMs: number;
86
+ readonly entitlementsStaleAfterFreshMs: number;
87
+ };
88
+ getEntitlements: (options?: {
89
+ skipCache?: boolean;
90
+ }) => Promise<EntitlementsGetResponse>;
91
+ invalidateEntitlementsCache: () => void;
92
+ mintSat: (params: {
93
+ feature: SatFeatureKey;
94
+ method: string;
95
+ path: string;
96
+ }) => Promise<MintSatResult>;
97
+ guardedFetch: (input: RequestInfo | URL, init?: GuardedRequestInit) => Promise<Response>;
98
+ protect: <T>(feature: SatFeatureKey, method: string, pathOrUrl: string, run: (ctx: {
99
+ headers: Headers;
100
+ }) => Promise<T>) => Promise<T>;
101
+ };
102
+
103
+ type StableMonetizationConfig = Pick<MonetizationClientConfig, "baseUrl" | "credentials" | "entitlementsCacheTtlMs" | "entitlementsStaleAfterFreshMs" | "onEvent">;
104
+ /**
105
+ * Stable browser client (primitive config deps only).
106
+ * For custom `defaultHeaders` / `fetchImpl`, use `createMonetizationClient` outside the hook.
107
+ */
108
+ declare function useMonetizationClient(cfg?: StableMonetizationConfig): MonetizationClient;
109
+
110
+ export { useMonetizationClient };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Clés acceptées par POST /api/sat côté serveur Exodus (whitelist API).
3
+ * Toute autre valeur est refusée par le serveur.
4
+ */
5
+ declare const SAT_FEATURE_KEYS: readonly ["chat.send", "chat.media", "contacts.view", "whatsapp.handoff", "demo.secure_read"];
6
+ type SatFeatureKey = (typeof SAT_FEATURE_KEYS)[number];
7
+ /** Réponse 200 de GET /api/entitlements */
8
+ type EntitlementsGetResponse = {
9
+ ok: true;
10
+ claim: {
11
+ sub: string;
12
+ plan: {
13
+ key: string;
14
+ name: string;
15
+ active: boolean;
16
+ };
17
+ features: string[];
18
+ sid: string;
19
+ device: string;
20
+ iat: number;
21
+ exp: number;
22
+ jti: string;
23
+ ver: number;
24
+ ev?: unknown;
25
+ };
26
+ token: string | null;
27
+ };
28
+ /** Observabilité côté client (analytics / debug) — ne remplace pas les logs serveur. */
29
+ type MonetizationSdkEvent = {
30
+ type: "sat_mint_failed";
31
+ feature: string;
32
+ code: string;
33
+ status: number;
34
+ } | {
35
+ type: "sat_mint_retried";
36
+ feature: string;
37
+ reason: string;
38
+ } | {
39
+ type: "sat_action_retry";
40
+ feature: string;
41
+ path: string;
42
+ reason: string;
43
+ } | {
44
+ type: "sat_action_blocked";
45
+ feature: string;
46
+ path: string;
47
+ reason: string;
48
+ status: number;
49
+ };
50
+ type MonetizationClientConfig = {
51
+ /**
52
+ * Origine de l’app (ex. https://app.exemple.com). Chaîne vide = URLs relatives (même origine).
53
+ */
54
+ baseUrl?: string;
55
+ credentials?: RequestCredentials;
56
+ /** TTL « frais » cache GET /api/entitlements (0 = pas de cache). */
57
+ entitlementsCacheTtlMs?: number;
58
+ /**
59
+ * Fenêtre stale-after-fresh : pendant ce délai après l’échéance « fraîche »,
60
+ * la dernière copie est renvoyée immédiatement et un rafraîchissement async part en arrière-plan.
61
+ * Défaut : min(2 × fresh, 120_000 ms).
62
+ */
63
+ entitlementsStaleAfterFreshMs?: number;
64
+ /** Surcharge fetch (tests, SSR avec cookies explicites). */
65
+ fetchImpl?: typeof fetch;
66
+ /** Headers ajoutés à chaque appel (ex. tracing). */
67
+ defaultHeaders?: HeadersInit;
68
+ /** Hook optionnel produit / télémétrie (SAT mint échoué, retry, action bloquée). */
69
+ onEvent?: (event: MonetizationSdkEvent) => void;
70
+ };
71
+ type MintSatResult = {
72
+ token: string;
73
+ exp: number;
74
+ };
75
+ type GuardedRequestInit = RequestInit & {
76
+ /** Si défini, un SAT est résolu via POST /api/sat avant la requête. */
77
+ feature?: SatFeatureKey;
78
+ };
79
+
80
+ type MonetizationClient = ReturnType<typeof createMonetizationClient>;
81
+ declare function createMonetizationClient(config?: MonetizationClientConfig): {
82
+ config: {
83
+ readonly baseUrl: string;
84
+ readonly credentials: RequestCredentials;
85
+ readonly entitlementsCacheTtlMs: number;
86
+ readonly entitlementsStaleAfterFreshMs: number;
87
+ };
88
+ getEntitlements: (options?: {
89
+ skipCache?: boolean;
90
+ }) => Promise<EntitlementsGetResponse>;
91
+ invalidateEntitlementsCache: () => void;
92
+ mintSat: (params: {
93
+ feature: SatFeatureKey;
94
+ method: string;
95
+ path: string;
96
+ }) => Promise<MintSatResult>;
97
+ guardedFetch: (input: RequestInfo | URL, init?: GuardedRequestInit) => Promise<Response>;
98
+ protect: <T>(feature: SatFeatureKey, method: string, pathOrUrl: string, run: (ctx: {
99
+ headers: Headers;
100
+ }) => Promise<T>) => Promise<T>;
101
+ };
102
+
103
+ type StableMonetizationConfig = Pick<MonetizationClientConfig, "baseUrl" | "credentials" | "entitlementsCacheTtlMs" | "entitlementsStaleAfterFreshMs" | "onEvent">;
104
+ /**
105
+ * Stable browser client (primitive config deps only).
106
+ * For custom `defaultHeaders` / `fetchImpl`, use `createMonetizationClient` outside the hook.
107
+ */
108
+ declare function useMonetizationClient(cfg?: StableMonetizationConfig): MonetizationClient;
109
+
110
+ export { useMonetizationClient };