@alleyboss/micropay-solana-x402-paywall 1.0.0 → 2.0.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.
Files changed (66) hide show
  1. package/README.md +100 -167
  2. package/dist/client/index.cjs +99 -0
  3. package/dist/client/index.cjs.map +1 -0
  4. package/dist/client/index.d.cts +112 -0
  5. package/dist/client/index.d.ts +112 -0
  6. package/dist/client/index.js +95 -0
  7. package/dist/client/index.js.map +1 -0
  8. package/dist/client-CSZHI8o8.d.ts +32 -0
  9. package/dist/client-vRr48m2x.d.cts +32 -0
  10. package/dist/index.cjs +803 -41
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +11 -3
  13. package/dist/index.d.ts +11 -3
  14. package/dist/index.js +783 -42
  15. package/dist/index.js.map +1 -1
  16. package/dist/memory-Daxkczti.d.cts +29 -0
  17. package/dist/memory-Daxkczti.d.ts +29 -0
  18. package/dist/middleware/index.cjs +261 -0
  19. package/dist/middleware/index.cjs.map +1 -0
  20. package/dist/middleware/index.d.cts +90 -0
  21. package/dist/middleware/index.d.ts +90 -0
  22. package/dist/middleware/index.js +255 -0
  23. package/dist/middleware/index.js.map +1 -0
  24. package/dist/nextjs-BK0pVb9Y.d.ts +78 -0
  25. package/dist/nextjs-Bm272Jkj.d.cts +78 -0
  26. package/dist/{client-kfCr7G-P.d.cts → payment-CTxdtqmc.d.cts} +23 -34
  27. package/dist/{client-kfCr7G-P.d.ts → payment-CTxdtqmc.d.ts} +23 -34
  28. package/dist/pricing/index.cjs +79 -0
  29. package/dist/pricing/index.cjs.map +1 -0
  30. package/dist/pricing/index.d.cts +67 -0
  31. package/dist/pricing/index.d.ts +67 -0
  32. package/dist/pricing/index.js +72 -0
  33. package/dist/pricing/index.js.map +1 -0
  34. package/dist/session/index.cjs +51 -11
  35. package/dist/session/index.cjs.map +1 -1
  36. package/dist/session/index.d.cts +29 -1
  37. package/dist/session/index.d.ts +29 -1
  38. package/dist/session/index.js +51 -11
  39. package/dist/session/index.js.map +1 -1
  40. package/dist/{index-DptevtnU.d.cts → session-D2IoWAWV.d.cts} +1 -24
  41. package/dist/{index-DptevtnU.d.ts → session-D2IoWAWV.d.ts} +1 -24
  42. package/dist/solana/index.cjs +235 -15
  43. package/dist/solana/index.cjs.map +1 -1
  44. package/dist/solana/index.d.cts +61 -3
  45. package/dist/solana/index.d.ts +61 -3
  46. package/dist/solana/index.js +232 -16
  47. package/dist/solana/index.js.map +1 -1
  48. package/dist/store/index.cjs +99 -0
  49. package/dist/store/index.cjs.map +1 -0
  50. package/dist/store/index.d.cts +38 -0
  51. package/dist/store/index.d.ts +38 -0
  52. package/dist/store/index.js +96 -0
  53. package/dist/store/index.js.map +1 -0
  54. package/dist/utils/index.cjs +68 -0
  55. package/dist/utils/index.cjs.map +1 -0
  56. package/dist/utils/index.d.cts +30 -0
  57. package/dist/utils/index.d.ts +30 -0
  58. package/dist/utils/index.js +65 -0
  59. package/dist/utils/index.js.map +1 -0
  60. package/dist/x402/index.cjs +119 -18
  61. package/dist/x402/index.cjs.map +1 -1
  62. package/dist/x402/index.d.cts +6 -1
  63. package/dist/x402/index.d.ts +6 -1
  64. package/dist/x402/index.js +119 -18
  65. package/dist/x402/index.js.map +1 -1
  66. package/package.json +56 -3
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ // src/pricing/index.ts
4
+ var cachedPrice = null;
5
+ var CACHE_TTL_MS = 6e4;
6
+ async function getSolPrice() {
7
+ if (cachedPrice && Date.now() - cachedPrice.fetchedAt.getTime() < CACHE_TTL_MS) {
8
+ return cachedPrice;
9
+ }
10
+ try {
11
+ const response = await fetch(
12
+ "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
13
+ {
14
+ headers: { "Accept": "application/json" },
15
+ signal: AbortSignal.timeout(5e3)
16
+ }
17
+ );
18
+ if (!response.ok) {
19
+ throw new Error(`Price fetch failed: ${response.status}`);
20
+ }
21
+ const data = await response.json();
22
+ if (!data.solana?.usd) {
23
+ throw new Error("Invalid price response");
24
+ }
25
+ cachedPrice = {
26
+ solPrice: data.solana.usd,
27
+ fetchedAt: /* @__PURE__ */ new Date(),
28
+ source: "coingecko"
29
+ };
30
+ return cachedPrice;
31
+ } catch (error) {
32
+ if (cachedPrice) {
33
+ return cachedPrice;
34
+ }
35
+ return {
36
+ solPrice: 150,
37
+ // Fallback price
38
+ fetchedAt: /* @__PURE__ */ new Date(),
39
+ source: "fallback"
40
+ };
41
+ }
42
+ }
43
+ async function lamportsToUsd(lamports) {
44
+ const { solPrice } = await getSolPrice();
45
+ const sol = Number(lamports) / 1e9;
46
+ return sol * solPrice;
47
+ }
48
+ async function usdToLamports(usd) {
49
+ const { solPrice } = await getSolPrice();
50
+ const sol = usd / solPrice;
51
+ return BigInt(Math.floor(sol * 1e9));
52
+ }
53
+ async function formatPriceDisplay(lamports) {
54
+ const { solPrice } = await getSolPrice();
55
+ const sol = Number(lamports) / 1e9;
56
+ const usd = sol * solPrice;
57
+ return `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`;
58
+ }
59
+ function formatPriceSync(lamports, solPrice) {
60
+ const sol = Number(lamports) / 1e9;
61
+ const usd = sol * solPrice;
62
+ return {
63
+ sol,
64
+ usd,
65
+ formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`
66
+ };
67
+ }
68
+ function clearPriceCache() {
69
+ cachedPrice = null;
70
+ }
71
+
72
+ exports.clearPriceCache = clearPriceCache;
73
+ exports.formatPriceDisplay = formatPriceDisplay;
74
+ exports.formatPriceSync = formatPriceSync;
75
+ exports.getSolPrice = getSolPrice;
76
+ exports.lamportsToUsd = lamportsToUsd;
77
+ exports.usdToLamports = usdToLamports;
78
+ //# sourceMappingURL=index.cjs.map
79
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pricing/index.ts"],"names":[],"mappings":";;;AAkBA,IAAI,WAAA,GAAgC,IAAA;AACpC,IAAM,YAAA,GAAe,GAAA;AAYrB,eAAsB,WAAA,GAAkC;AAEpD,EAAA,IAAI,WAAA,IAAe,KAAK,GAAA,EAAI,GAAI,YAAY,SAAA,CAAU,OAAA,KAAY,YAAA,EAAc;AAC5E,IAAA,OAAO,WAAA;AAAA,EACX;AAEA,EAAA,IAAI;AAEA,IAAA,MAAM,WAAW,MAAM,KAAA;AAAA,MACnB,4EAAA;AAAA,MACA;AAAA,QACI,OAAA,EAAS,EAAE,QAAA,EAAU,kBAAA,EAAmB;AAAA,QACxC,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,GAAI;AAAA;AACpC,KACJ;AAEA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IAC5D;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,EAAQ,GAAA,EAAK;AACnB,MAAA,MAAM,IAAI,MAAM,wBAAwB,CAAA;AAAA,IAC5C;AAEA,IAAA,WAAA,GAAc;AAAA,MACV,QAAA,EAAU,KAAK,MAAA,CAAO,GAAA;AAAA,MACtB,SAAA,sBAAe,IAAA,EAAK;AAAA,MACpB,MAAA,EAAQ;AAAA,KACZ;AAEA,IAAA,OAAO,WAAA;AAAA,EACX,SAAS,KAAA,EAAO;AAEZ,IAAA,IAAI,WAAA,EAAa;AACb,MAAA,OAAO,WAAA;AAAA,IACX;AAGA,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,GAAA;AAAA;AAAA,MACV,SAAA,sBAAe,IAAA,EAAK;AAAA,MACpB,MAAA,EAAQ;AAAA,KACZ;AAAA,EACJ;AACJ;AAWA,eAAsB,cAAc,QAAA,EAAmC;AACnE,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,WAAA,EAAY;AACvC,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAQ,CAAA,GAAI,GAAA;AAC/B,EAAA,OAAO,GAAA,GAAM,QAAA;AACjB;AAWA,eAAsB,cAAc,GAAA,EAA8B;AAC9D,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,WAAA,EAAY;AACvC,EAAA,MAAM,MAAM,GAAA,GAAM,QAAA;AAClB,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,GAAa,CAAC,CAAA;AACjD;AAWA,eAAsB,mBAAmB,QAAA,EAAmC;AACxE,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,WAAA,EAAY;AACvC,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAQ,CAAA,GAAI,GAAA;AAC/B,EAAA,MAAM,MAAM,GAAA,GAAM,QAAA;AAElB,EAAA,OAAO,CAAA,EAAG,IAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,QAAA,EAAW,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AACrD;AAMO,SAAS,eAAA,CAAgB,UAAkB,QAAA,EAIhD;AACE,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAQ,CAAA,GAAI,GAAA;AAC/B,EAAA,MAAM,MAAM,GAAA,GAAM,QAAA;AAElB,EAAA,OAAO;AAAA,IACH,GAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,CAAA,EAAG,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,QAAA,EAAW,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA;AAAA,GACzD;AACJ;AAKO,SAAS,eAAA,GAAwB;AACpC,EAAA,WAAA,GAAc,IAAA;AAClB","file":"index.cjs","sourcesContent":["// Price Conversion Helpers\n// Fetch SOL price and convert between USD/SOL\n\n/**\n * Price data from API\n */\nexport interface PriceData {\n /** SOL price in USD */\n solPrice: number;\n /** Timestamp of price fetch */\n fetchedAt: Date;\n /** Source of price data */\n source: string;\n}\n\n/**\n * Cached price data\n */\nlet cachedPrice: PriceData | null = null;\nconst CACHE_TTL_MS = 60000; // 1 minute cache\n\n/**\n * Fetch current SOL price from CoinGecko\n * Includes caching to avoid rate limits\n * \n * @example\n * ```typescript\n * const { solPrice } = await getSolPrice();\n * console.log(`SOL is $${solPrice}`);\n * ```\n */\nexport async function getSolPrice(): Promise<PriceData> {\n // Return cached price if still valid\n if (cachedPrice && Date.now() - cachedPrice.fetchedAt.getTime() < CACHE_TTL_MS) {\n return cachedPrice;\n }\n\n try {\n // CoinGecko free API (no key required, 10-30 calls/min)\n const response = await fetch(\n 'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd',\n {\n headers: { 'Accept': 'application/json' },\n signal: AbortSignal.timeout(5000),\n }\n );\n\n if (!response.ok) {\n throw new Error(`Price fetch failed: ${response.status}`);\n }\n\n const data = await response.json() as { solana?: { usd?: number } };\n\n if (!data.solana?.usd) {\n throw new Error('Invalid price response');\n }\n\n cachedPrice = {\n solPrice: data.solana.usd,\n fetchedAt: new Date(),\n source: 'coingecko',\n };\n\n return cachedPrice;\n } catch (error) {\n // If fetch fails and we have stale cache, use it\n if (cachedPrice) {\n return cachedPrice;\n }\n\n // Fallback to a reasonable estimate (update periodically)\n return {\n solPrice: 150, // Fallback price\n fetchedAt: new Date(),\n source: 'fallback',\n };\n }\n}\n\n/**\n * Convert lamports to USD\n * \n * @example\n * ```typescript\n * const usd = await lamportsToUsd(10000000n); // 0.01 SOL\n * console.log(`$${usd.toFixed(2)}`);\n * ```\n */\nexport async function lamportsToUsd(lamports: bigint): Promise<number> {\n const { solPrice } = await getSolPrice();\n const sol = Number(lamports) / 1_000_000_000; // LAMPORTS_PER_SOL\n return sol * solPrice;\n}\n\n/**\n * Convert USD to lamports\n * \n * @example\n * ```typescript\n * const lamports = await usdToLamports(1.50); // $1.50\n * console.log(`${lamports} lamports`);\n * ```\n */\nexport async function usdToLamports(usd: number): Promise<bigint> {\n const { solPrice } = await getSolPrice();\n const sol = usd / solPrice;\n return BigInt(Math.floor(sol * 1_000_000_000));\n}\n\n/**\n * Format a price for display with both SOL and USD\n * \n * @example\n * ```typescript\n * const display = await formatPriceDisplay(10000000n);\n * // Returns: \"0.01 SOL (~$1.50)\"\n * ```\n */\nexport async function formatPriceDisplay(lamports: bigint): Promise<string> {\n const { solPrice } = await getSolPrice();\n const sol = Number(lamports) / 1_000_000_000;\n const usd = sol * solPrice;\n\n return `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`;\n}\n\n/**\n * Synchronous price formatting (requires pre-fetched price)\n * Use this when you've already fetched the price\n */\nexport function formatPriceSync(lamports: bigint, solPrice: number): {\n sol: number;\n usd: number;\n formatted: string;\n} {\n const sol = Number(lamports) / 1_000_000_000;\n const usd = sol * solPrice;\n\n return {\n sol,\n usd,\n formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`,\n };\n}\n\n/**\n * Clear the price cache (for testing or manual refresh)\n */\nexport function clearPriceCache(): void {\n cachedPrice = null;\n}\n"]}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Price data from API
3
+ */
4
+ interface PriceData {
5
+ /** SOL price in USD */
6
+ solPrice: number;
7
+ /** Timestamp of price fetch */
8
+ fetchedAt: Date;
9
+ /** Source of price data */
10
+ source: string;
11
+ }
12
+ /**
13
+ * Fetch current SOL price from CoinGecko
14
+ * Includes caching to avoid rate limits
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { solPrice } = await getSolPrice();
19
+ * console.log(`SOL is $${solPrice}`);
20
+ * ```
21
+ */
22
+ declare function getSolPrice(): Promise<PriceData>;
23
+ /**
24
+ * Convert lamports to USD
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const usd = await lamportsToUsd(10000000n); // 0.01 SOL
29
+ * console.log(`$${usd.toFixed(2)}`);
30
+ * ```
31
+ */
32
+ declare function lamportsToUsd(lamports: bigint): Promise<number>;
33
+ /**
34
+ * Convert USD to lamports
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const lamports = await usdToLamports(1.50); // $1.50
39
+ * console.log(`${lamports} lamports`);
40
+ * ```
41
+ */
42
+ declare function usdToLamports(usd: number): Promise<bigint>;
43
+ /**
44
+ * Format a price for display with both SOL and USD
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const display = await formatPriceDisplay(10000000n);
49
+ * // Returns: "0.01 SOL (~$1.50)"
50
+ * ```
51
+ */
52
+ declare function formatPriceDisplay(lamports: bigint): Promise<string>;
53
+ /**
54
+ * Synchronous price formatting (requires pre-fetched price)
55
+ * Use this when you've already fetched the price
56
+ */
57
+ declare function formatPriceSync(lamports: bigint, solPrice: number): {
58
+ sol: number;
59
+ usd: number;
60
+ formatted: string;
61
+ };
62
+ /**
63
+ * Clear the price cache (for testing or manual refresh)
64
+ */
65
+ declare function clearPriceCache(): void;
66
+
67
+ export { type PriceData, clearPriceCache, formatPriceDisplay, formatPriceSync, getSolPrice, lamportsToUsd, usdToLamports };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Price data from API
3
+ */
4
+ interface PriceData {
5
+ /** SOL price in USD */
6
+ solPrice: number;
7
+ /** Timestamp of price fetch */
8
+ fetchedAt: Date;
9
+ /** Source of price data */
10
+ source: string;
11
+ }
12
+ /**
13
+ * Fetch current SOL price from CoinGecko
14
+ * Includes caching to avoid rate limits
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { solPrice } = await getSolPrice();
19
+ * console.log(`SOL is $${solPrice}`);
20
+ * ```
21
+ */
22
+ declare function getSolPrice(): Promise<PriceData>;
23
+ /**
24
+ * Convert lamports to USD
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * const usd = await lamportsToUsd(10000000n); // 0.01 SOL
29
+ * console.log(`$${usd.toFixed(2)}`);
30
+ * ```
31
+ */
32
+ declare function lamportsToUsd(lamports: bigint): Promise<number>;
33
+ /**
34
+ * Convert USD to lamports
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * const lamports = await usdToLamports(1.50); // $1.50
39
+ * console.log(`${lamports} lamports`);
40
+ * ```
41
+ */
42
+ declare function usdToLamports(usd: number): Promise<bigint>;
43
+ /**
44
+ * Format a price for display with both SOL and USD
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const display = await formatPriceDisplay(10000000n);
49
+ * // Returns: "0.01 SOL (~$1.50)"
50
+ * ```
51
+ */
52
+ declare function formatPriceDisplay(lamports: bigint): Promise<string>;
53
+ /**
54
+ * Synchronous price formatting (requires pre-fetched price)
55
+ * Use this when you've already fetched the price
56
+ */
57
+ declare function formatPriceSync(lamports: bigint, solPrice: number): {
58
+ sol: number;
59
+ usd: number;
60
+ formatted: string;
61
+ };
62
+ /**
63
+ * Clear the price cache (for testing or manual refresh)
64
+ */
65
+ declare function clearPriceCache(): void;
66
+
67
+ export { type PriceData, clearPriceCache, formatPriceDisplay, formatPriceSync, getSolPrice, lamportsToUsd, usdToLamports };
@@ -0,0 +1,72 @@
1
+ // src/pricing/index.ts
2
+ var cachedPrice = null;
3
+ var CACHE_TTL_MS = 6e4;
4
+ async function getSolPrice() {
5
+ if (cachedPrice && Date.now() - cachedPrice.fetchedAt.getTime() < CACHE_TTL_MS) {
6
+ return cachedPrice;
7
+ }
8
+ try {
9
+ const response = await fetch(
10
+ "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
11
+ {
12
+ headers: { "Accept": "application/json" },
13
+ signal: AbortSignal.timeout(5e3)
14
+ }
15
+ );
16
+ if (!response.ok) {
17
+ throw new Error(`Price fetch failed: ${response.status}`);
18
+ }
19
+ const data = await response.json();
20
+ if (!data.solana?.usd) {
21
+ throw new Error("Invalid price response");
22
+ }
23
+ cachedPrice = {
24
+ solPrice: data.solana.usd,
25
+ fetchedAt: /* @__PURE__ */ new Date(),
26
+ source: "coingecko"
27
+ };
28
+ return cachedPrice;
29
+ } catch (error) {
30
+ if (cachedPrice) {
31
+ return cachedPrice;
32
+ }
33
+ return {
34
+ solPrice: 150,
35
+ // Fallback price
36
+ fetchedAt: /* @__PURE__ */ new Date(),
37
+ source: "fallback"
38
+ };
39
+ }
40
+ }
41
+ async function lamportsToUsd(lamports) {
42
+ const { solPrice } = await getSolPrice();
43
+ const sol = Number(lamports) / 1e9;
44
+ return sol * solPrice;
45
+ }
46
+ async function usdToLamports(usd) {
47
+ const { solPrice } = await getSolPrice();
48
+ const sol = usd / solPrice;
49
+ return BigInt(Math.floor(sol * 1e9));
50
+ }
51
+ async function formatPriceDisplay(lamports) {
52
+ const { solPrice } = await getSolPrice();
53
+ const sol = Number(lamports) / 1e9;
54
+ const usd = sol * solPrice;
55
+ return `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`;
56
+ }
57
+ function formatPriceSync(lamports, solPrice) {
58
+ const sol = Number(lamports) / 1e9;
59
+ const usd = sol * solPrice;
60
+ return {
61
+ sol,
62
+ usd,
63
+ formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`
64
+ };
65
+ }
66
+ function clearPriceCache() {
67
+ cachedPrice = null;
68
+ }
69
+
70
+ export { clearPriceCache, formatPriceDisplay, formatPriceSync, getSolPrice, lamportsToUsd, usdToLamports };
71
+ //# sourceMappingURL=index.js.map
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/pricing/index.ts"],"names":[],"mappings":";AAkBA,IAAI,WAAA,GAAgC,IAAA;AACpC,IAAM,YAAA,GAAe,GAAA;AAYrB,eAAsB,WAAA,GAAkC;AAEpD,EAAA,IAAI,WAAA,IAAe,KAAK,GAAA,EAAI,GAAI,YAAY,SAAA,CAAU,OAAA,KAAY,YAAA,EAAc;AAC5E,IAAA,OAAO,WAAA;AAAA,EACX;AAEA,EAAA,IAAI;AAEA,IAAA,MAAM,WAAW,MAAM,KAAA;AAAA,MACnB,4EAAA;AAAA,MACA;AAAA,QACI,OAAA,EAAS,EAAE,QAAA,EAAU,kBAAA,EAAmB;AAAA,QACxC,MAAA,EAAQ,WAAA,CAAY,OAAA,CAAQ,GAAI;AAAA;AACpC,KACJ;AAEA,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AACd,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,IAC5D;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,EAAQ,GAAA,EAAK;AACnB,MAAA,MAAM,IAAI,MAAM,wBAAwB,CAAA;AAAA,IAC5C;AAEA,IAAA,WAAA,GAAc;AAAA,MACV,QAAA,EAAU,KAAK,MAAA,CAAO,GAAA;AAAA,MACtB,SAAA,sBAAe,IAAA,EAAK;AAAA,MACpB,MAAA,EAAQ;AAAA,KACZ;AAEA,IAAA,OAAO,WAAA;AAAA,EACX,SAAS,KAAA,EAAO;AAEZ,IAAA,IAAI,WAAA,EAAa;AACb,MAAA,OAAO,WAAA;AAAA,IACX;AAGA,IAAA,OAAO;AAAA,MACH,QAAA,EAAU,GAAA;AAAA;AAAA,MACV,SAAA,sBAAe,IAAA,EAAK;AAAA,MACpB,MAAA,EAAQ;AAAA,KACZ;AAAA,EACJ;AACJ;AAWA,eAAsB,cAAc,QAAA,EAAmC;AACnE,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,WAAA,EAAY;AACvC,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAQ,CAAA,GAAI,GAAA;AAC/B,EAAA,OAAO,GAAA,GAAM,QAAA;AACjB;AAWA,eAAsB,cAAc,GAAA,EAA8B;AAC9D,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,WAAA,EAAY;AACvC,EAAA,MAAM,MAAM,GAAA,GAAM,QAAA;AAClB,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,GAAA,GAAM,GAAa,CAAC,CAAA;AACjD;AAWA,eAAsB,mBAAmB,QAAA,EAAmC;AACxE,EAAA,MAAM,EAAE,QAAA,EAAS,GAAI,MAAM,WAAA,EAAY;AACvC,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAQ,CAAA,GAAI,GAAA;AAC/B,EAAA,MAAM,MAAM,GAAA,GAAM,QAAA;AAElB,EAAA,OAAO,CAAA,EAAG,IAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,QAAA,EAAW,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AACrD;AAMO,SAAS,eAAA,CAAgB,UAAkB,QAAA,EAIhD;AACE,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,QAAQ,CAAA,GAAI,GAAA;AAC/B,EAAA,MAAM,MAAM,GAAA,GAAM,QAAA;AAElB,EAAA,OAAO;AAAA,IACH,GAAA;AAAA,IACA,GAAA;AAAA,IACA,SAAA,EAAW,CAAA,EAAG,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,QAAA,EAAW,GAAA,CAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA;AAAA,GACzD;AACJ;AAKO,SAAS,eAAA,GAAwB;AACpC,EAAA,WAAA,GAAc,IAAA;AAClB","file":"index.js","sourcesContent":["// Price Conversion Helpers\n// Fetch SOL price and convert between USD/SOL\n\n/**\n * Price data from API\n */\nexport interface PriceData {\n /** SOL price in USD */\n solPrice: number;\n /** Timestamp of price fetch */\n fetchedAt: Date;\n /** Source of price data */\n source: string;\n}\n\n/**\n * Cached price data\n */\nlet cachedPrice: PriceData | null = null;\nconst CACHE_TTL_MS = 60000; // 1 minute cache\n\n/**\n * Fetch current SOL price from CoinGecko\n * Includes caching to avoid rate limits\n * \n * @example\n * ```typescript\n * const { solPrice } = await getSolPrice();\n * console.log(`SOL is $${solPrice}`);\n * ```\n */\nexport async function getSolPrice(): Promise<PriceData> {\n // Return cached price if still valid\n if (cachedPrice && Date.now() - cachedPrice.fetchedAt.getTime() < CACHE_TTL_MS) {\n return cachedPrice;\n }\n\n try {\n // CoinGecko free API (no key required, 10-30 calls/min)\n const response = await fetch(\n 'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd',\n {\n headers: { 'Accept': 'application/json' },\n signal: AbortSignal.timeout(5000),\n }\n );\n\n if (!response.ok) {\n throw new Error(`Price fetch failed: ${response.status}`);\n }\n\n const data = await response.json() as { solana?: { usd?: number } };\n\n if (!data.solana?.usd) {\n throw new Error('Invalid price response');\n }\n\n cachedPrice = {\n solPrice: data.solana.usd,\n fetchedAt: new Date(),\n source: 'coingecko',\n };\n\n return cachedPrice;\n } catch (error) {\n // If fetch fails and we have stale cache, use it\n if (cachedPrice) {\n return cachedPrice;\n }\n\n // Fallback to a reasonable estimate (update periodically)\n return {\n solPrice: 150, // Fallback price\n fetchedAt: new Date(),\n source: 'fallback',\n };\n }\n}\n\n/**\n * Convert lamports to USD\n * \n * @example\n * ```typescript\n * const usd = await lamportsToUsd(10000000n); // 0.01 SOL\n * console.log(`$${usd.toFixed(2)}`);\n * ```\n */\nexport async function lamportsToUsd(lamports: bigint): Promise<number> {\n const { solPrice } = await getSolPrice();\n const sol = Number(lamports) / 1_000_000_000; // LAMPORTS_PER_SOL\n return sol * solPrice;\n}\n\n/**\n * Convert USD to lamports\n * \n * @example\n * ```typescript\n * const lamports = await usdToLamports(1.50); // $1.50\n * console.log(`${lamports} lamports`);\n * ```\n */\nexport async function usdToLamports(usd: number): Promise<bigint> {\n const { solPrice } = await getSolPrice();\n const sol = usd / solPrice;\n return BigInt(Math.floor(sol * 1_000_000_000));\n}\n\n/**\n * Format a price for display with both SOL and USD\n * \n * @example\n * ```typescript\n * const display = await formatPriceDisplay(10000000n);\n * // Returns: \"0.01 SOL (~$1.50)\"\n * ```\n */\nexport async function formatPriceDisplay(lamports: bigint): Promise<string> {\n const { solPrice } = await getSolPrice();\n const sol = Number(lamports) / 1_000_000_000;\n const usd = sol * solPrice;\n\n return `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`;\n}\n\n/**\n * Synchronous price formatting (requires pre-fetched price)\n * Use this when you've already fetched the price\n */\nexport function formatPriceSync(lamports: bigint, solPrice: number): {\n sol: number;\n usd: number;\n formatted: string;\n} {\n const sol = Number(lamports) / 1_000_000_000;\n const usd = sol * solPrice;\n\n return {\n sol,\n usd,\n formatted: `${sol.toFixed(4)} SOL (~$${usd.toFixed(2)})`,\n };\n}\n\n/**\n * Clear the price cache (for testing or manual refresh)\n */\nexport function clearPriceCache(): void {\n cachedPrice = null;\n}\n"]}
@@ -4,13 +4,38 @@ var jose = require('jose');
4
4
  var uuid = require('uuid');
5
5
 
6
6
  // src/session/core.ts
7
+ var MAX_ARTICLES_PER_SESSION = 100;
8
+ var MIN_SECRET_LENGTH = 32;
7
9
  function getSecretKey(secret) {
8
- if (secret.length < 32) {
9
- throw new Error("Session secret must be at least 32 characters");
10
+ if (!secret || typeof secret !== "string") {
11
+ throw new Error("Session secret is required");
12
+ }
13
+ if (secret.length < MIN_SECRET_LENGTH) {
14
+ throw new Error(`Session secret must be at least ${MIN_SECRET_LENGTH} characters`);
10
15
  }
11
16
  return new TextEncoder().encode(secret);
12
17
  }
18
+ function validateWalletAddress(address) {
19
+ if (!address || typeof address !== "string") return false;
20
+ const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
21
+ return base58Regex.test(address);
22
+ }
23
+ function validateArticleId(articleId) {
24
+ if (!articleId || typeof articleId !== "string") return false;
25
+ if (articleId.length > 128) return false;
26
+ const safeIdRegex = /^[a-zA-Z0-9_-]+$/;
27
+ return safeIdRegex.test(articleId);
28
+ }
13
29
  async function createSession(walletAddress, articleId, config, siteWide = false) {
30
+ if (!validateWalletAddress(walletAddress)) {
31
+ throw new Error("Invalid wallet address format");
32
+ }
33
+ if (!validateArticleId(articleId)) {
34
+ throw new Error("Invalid article ID format");
35
+ }
36
+ if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 720) {
37
+ throw new Error("Session duration must be between 1 and 720 hours");
38
+ }
14
39
  const sessionId = uuid.v4();
15
40
  const now = Math.floor(Date.now() / 1e3);
16
41
  const expiresAt = now + config.durationHours * 3600;
@@ -18,7 +43,7 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
18
43
  id: sessionId,
19
44
  walletAddress,
20
45
  unlockedArticles: [articleId],
21
- siteWideUnlock: siteWide,
46
+ siteWideUnlock: Boolean(siteWide),
22
47
  createdAt: now,
23
48
  expiresAt
24
49
  };
@@ -26,7 +51,7 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
26
51
  sub: walletAddress,
27
52
  sid: sessionId,
28
53
  articles: session.unlockedArticles,
29
- siteWide,
54
+ siteWide: session.siteWideUnlock,
30
55
  iat: now,
31
56
  exp: expiresAt
32
57
  };
@@ -34,30 +59,39 @@ async function createSession(walletAddress, articleId, config, siteWide = false)
34
59
  return { token, session };
35
60
  }
36
61
  async function validateSession(token, secret) {
62
+ if (!token || typeof token !== "string") {
63
+ return { valid: false, reason: "Invalid token format" };
64
+ }
37
65
  try {
38
66
  const { payload } = await jose.jwtVerify(token, getSecretKey(secret));
39
67
  const sessionPayload = payload;
68
+ if (!sessionPayload.sub || !sessionPayload.sid || !sessionPayload.exp) {
69
+ return { valid: false, reason: "Malformed session payload" };
70
+ }
40
71
  const now = Math.floor(Date.now() / 1e3);
41
72
  if (sessionPayload.exp < now) {
42
73
  return { valid: false, reason: "Session expired" };
43
74
  }
75
+ if (!validateWalletAddress(sessionPayload.sub)) {
76
+ return { valid: false, reason: "Invalid session data" };
77
+ }
44
78
  const session = {
45
79
  id: sessionPayload.sid,
46
80
  walletAddress: sessionPayload.sub,
47
- unlockedArticles: sessionPayload.articles,
48
- siteWideUnlock: sessionPayload.siteWide,
49
- createdAt: sessionPayload.iat,
81
+ unlockedArticles: Array.isArray(sessionPayload.articles) ? sessionPayload.articles : [],
82
+ siteWideUnlock: Boolean(sessionPayload.siteWide),
83
+ createdAt: sessionPayload.iat ?? 0,
50
84
  expiresAt: sessionPayload.exp
51
85
  };
52
86
  return { valid: true, session };
53
87
  } catch (error) {
54
- return {
55
- valid: false,
56
- reason: error instanceof Error ? error.message : "Invalid session"
57
- };
88
+ return { valid: false, reason: "Invalid session" };
58
89
  }
59
90
  }
60
91
  async function addArticleToSession(token, articleId, secret) {
92
+ if (!validateArticleId(articleId)) {
93
+ return null;
94
+ }
61
95
  const validation = await validateSession(token, secret);
62
96
  if (!validation.valid || !validation.session) {
63
97
  return null;
@@ -66,6 +100,9 @@ async function addArticleToSession(token, articleId, secret) {
66
100
  if (session.unlockedArticles.includes(articleId)) {
67
101
  return { token, session };
68
102
  }
103
+ if (session.unlockedArticles.length >= MAX_ARTICLES_PER_SESSION) {
104
+ return null;
105
+ }
69
106
  const updatedArticles = [...session.unlockedArticles, articleId];
70
107
  const payload = {
71
108
  sub: session.walletAddress,
@@ -82,6 +119,9 @@ async function addArticleToSession(token, articleId, secret) {
82
119
  };
83
120
  }
84
121
  async function isArticleUnlocked(token, articleId, secret) {
122
+ if (!validateArticleId(articleId)) {
123
+ return false;
124
+ }
85
125
  const validation = await validateSession(token, secret);
86
126
  if (!validation.valid || !validation.session) {
87
127
  return false;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/session/core.ts"],"names":["uuidv4","SignJWT","jwtVerify"],"mappings":";;;;;;AAQA,SAAS,aAAa,MAAA,EAA4B;AAC9C,EAAA,IAAI,MAAA,CAAO,SAAS,EAAA,EAAI;AACpB,IAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,EACnE;AACA,EAAA,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,CAAA;AAC1C;AAKA,eAAsB,aAAA,CAClB,aAAA,EACA,SAAA,EACA,MAAA,EACA,WAAoB,KAAA,EAC4B;AAChD,EAAA,MAAM,YAAYA,OAAA,EAAO;AACzB,EAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AACxC,EAAA,MAAM,SAAA,GAAY,GAAA,GAAO,MAAA,CAAO,aAAA,GAAgB,IAAA;AAEhD,EAAA,MAAM,OAAA,GAAuB;AAAA,IACzB,EAAA,EAAI,SAAA;AAAA,IACJ,aAAA;AAAA,IACA,gBAAA,EAAkB,CAAC,SAAS,CAAA;AAAA,IAC5B,cAAA,EAAgB,QAAA;AAAA,IAChB,SAAA,EAAW,GAAA;AAAA,IACX;AAAA,GACJ;AAEA,EAAA,MAAM,OAAA,GAA6B;AAAA,IAC/B,GAAA,EAAK,aAAA;AAAA,IACL,GAAA,EAAK,SAAA;AAAA,IACL,UAAU,OAAA,CAAQ,gBAAA;AAAA,IAClB,QAAA;AAAA,IACA,GAAA,EAAK,GAAA;AAAA,IACL,GAAA,EAAK;AAAA,GACT;AAEA,EAAA,MAAM,KAAA,GAAQ,MAAM,IAAIC,YAAA,CAAQ,OAA6C,EACxE,kBAAA,CAAmB,EAAE,GAAA,EAAK,OAAA,EAAS,CAAA,CACnC,aAAY,CACZ,iBAAA,CAAkB,CAAA,EAAG,MAAA,CAAO,aAAa,CAAA,CAAA,CAAG,EAC5C,IAAA,CAAK,YAAA,CAAa,MAAA,CAAO,MAAM,CAAC,CAAA;AAErC,EAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAC5B;AAKA,eAAsB,eAAA,CAClB,OACA,MAAA,EAC0B;AAC1B,EAAA,IAAI;AACA,IAAA,MAAM,EAAE,SAAQ,GAAI,MAAMC,eAAU,KAAA,EAAO,YAAA,CAAa,MAAM,CAAC,CAAA;AAC/D,IAAA,MAAM,cAAA,GAAiB,OAAA;AAEvB,IAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AACxC,IAAA,IAAI,cAAA,CAAe,MAAM,GAAA,EAAK;AAC1B,MAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,iBAAA,EAAkB;AAAA,IACrD;AAEA,IAAA,MAAM,OAAA,GAAuB;AAAA,MACzB,IAAI,cAAA,CAAe,GAAA;AAAA,MACnB,eAAe,cAAA,CAAe,GAAA;AAAA,MAC9B,kBAAkB,cAAA,CAAe,QAAA;AAAA,MACjC,gBAAgB,cAAA,CAAe,QAAA;AAAA,MAC/B,WAAW,cAAA,CAAe,GAAA;AAAA,MAC1B,WAAW,cAAA,CAAe;AAAA,KAC9B;AAEA,IAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAQ;AAAA,EAClC,SAAS,KAAA,EAAO;AACZ,IAAA,OAAO;AAAA,MACH,KAAA,EAAO,KAAA;AAAA,MACP,MAAA,EAAQ,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,KACrD;AAAA,EACJ;AACJ;AAKA,eAAsB,mBAAA,CAClB,KAAA,EACA,SAAA,EACA,MAAA,EACuD;AACvD,EAAA,MAAM,UAAA,GAAa,MAAM,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAA;AACtD,EAAA,IAAI,CAAC,UAAA,CAAW,KAAA,IAAS,CAAC,WAAW,OAAA,EAAS;AAC1C,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,MAAM,UAAU,UAAA,CAAW,OAAA;AAG3B,EAAA,IAAI,OAAA,CAAQ,gBAAA,CAAiB,QAAA,CAAS,SAAS,CAAA,EAAG;AAC9C,IAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAAA,EAC5B;AAEA,EAAA,MAAM,eAAA,GAAkB,CAAC,GAAG,OAAA,CAAQ,kBAAkB,SAAS,CAAA;AAE/D,EAAA,MAAM,OAAA,GAA6B;AAAA,IAC/B,KAAK,OAAA,CAAQ,aAAA;AAAA,IACb,KAAK,OAAA,CAAQ,EAAA;AAAA,IACb,QAAA,EAAU,eAAA;AAAA,IACV,UAAU,OAAA,CAAQ,cAAA;AAAA,IAClB,KAAK,OAAA,CAAQ,SAAA;AAAA,IACb,KAAK,OAAA,CAAQ;AAAA,GACjB;AAEA,EAAA,MAAM,QAAA,GAAW,MAAM,IAAID,YAAA,CAAQ,OAA6C,CAAA,CAC3E,kBAAA,CAAmB,EAAE,GAAA,EAAK,SAAS,CAAA,CACnC,IAAA,CAAK,YAAA,CAAa,MAAM,CAAC,CAAA;AAE9B,EAAA,OAAO;AAAA,IACH,KAAA,EAAO,QAAA;AAAA,IACP,OAAA,EAAS,EAAE,GAAG,OAAA,EAAS,kBAAkB,eAAA;AAAgB,GAC7D;AACJ;AAKA,eAAsB,iBAAA,CAClB,KAAA,EACA,SAAA,EACA,MAAA,EACgB;AAChB,EAAA,MAAM,UAAA,GAAa,MAAM,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAA;AACtD,EAAA,IAAI,CAAC,UAAA,CAAW,KAAA,IAAS,CAAC,WAAW,OAAA,EAAS;AAC1C,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,IAAI,UAAA,CAAW,QAAQ,cAAA,EAAgB;AACnC,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,OAAO,UAAA,CAAW,OAAA,CAAQ,gBAAA,CAAiB,QAAA,CAAS,SAAS,CAAA;AACjE","file":"index.cjs","sourcesContent":["// Session management with JWT (framework-agnostic core)\nimport { SignJWT, jwtVerify } from 'jose';\nimport { v4 as uuidv4 } from 'uuid';\nimport type { SessionData, SessionConfig, SessionValidation, SessionJWTPayload } from '../types';\n\n/**\n * Get the secret key for JWT signing\n */\nfunction getSecretKey(secret: string): Uint8Array {\n if (secret.length < 32) {\n throw new Error('Session secret must be at least 32 characters');\n }\n return new TextEncoder().encode(secret);\n}\n\n/**\n * Create a new session after successful payment\n */\nexport async function createSession(\n walletAddress: string,\n articleId: string,\n config: SessionConfig,\n siteWide: boolean = false\n): Promise<{ token: string; session: SessionData }> {\n const sessionId = uuidv4();\n const now = Math.floor(Date.now() / 1000);\n const expiresAt = now + (config.durationHours * 3600);\n\n const session: SessionData = {\n id: sessionId,\n walletAddress,\n unlockedArticles: [articleId],\n siteWideUnlock: siteWide,\n createdAt: now,\n expiresAt,\n };\n\n const payload: SessionJWTPayload = {\n sub: walletAddress,\n sid: sessionId,\n articles: session.unlockedArticles,\n siteWide,\n iat: now,\n exp: expiresAt,\n };\n\n const token = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(`${config.durationHours}h`)\n .sign(getSecretKey(config.secret));\n\n return { token, session };\n}\n\n/**\n * Validate an existing session token\n */\nexport async function validateSession(\n token: string,\n secret: string\n): Promise<SessionValidation> {\n try {\n const { payload } = await jwtVerify(token, getSecretKey(secret));\n const sessionPayload = payload as unknown as SessionJWTPayload;\n\n const now = Math.floor(Date.now() / 1000);\n if (sessionPayload.exp < now) {\n return { valid: false, reason: 'Session expired' };\n }\n\n const session: SessionData = {\n id: sessionPayload.sid,\n walletAddress: sessionPayload.sub,\n unlockedArticles: sessionPayload.articles,\n siteWideUnlock: sessionPayload.siteWide,\n createdAt: sessionPayload.iat,\n expiresAt: sessionPayload.exp,\n };\n\n return { valid: true, session };\n } catch (error) {\n return {\n valid: false,\n reason: error instanceof Error ? error.message : 'Invalid session',\n };\n }\n}\n\n/**\n * Add an article to an existing session\n */\nexport async function addArticleToSession(\n token: string,\n articleId: string,\n secret: string\n): Promise<{ token: string; session: SessionData } | null> {\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return null;\n }\n\n const session = validation.session;\n\n // Already unlocked\n if (session.unlockedArticles.includes(articleId)) {\n return { token, session };\n }\n\n const updatedArticles = [...session.unlockedArticles, articleId];\n\n const payload: SessionJWTPayload = {\n sub: session.walletAddress,\n sid: session.id,\n articles: updatedArticles,\n siteWide: session.siteWideUnlock,\n iat: session.createdAt,\n exp: session.expiresAt,\n };\n\n const newToken = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(getSecretKey(secret));\n\n return {\n token: newToken,\n session: { ...session, unlockedArticles: updatedArticles },\n };\n}\n\n/**\n * Check if an article is unlocked for a session\n */\nexport async function isArticleUnlocked(\n token: string,\n articleId: string,\n secret: string\n): Promise<boolean> {\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return false;\n }\n\n if (validation.session.siteWideUnlock) {\n return true;\n }\n\n return validation.session.unlockedArticles.includes(articleId);\n}\n"]}
1
+ {"version":3,"sources":["../../src/session/core.ts"],"names":["uuidv4","SignJWT","jwtVerify"],"mappings":";;;;;;AAOA,IAAM,wBAAA,GAA2B,GAAA;AAGjC,IAAM,iBAAA,GAAoB,EAAA;AAM1B,SAAS,aAAa,MAAA,EAA4B;AAC9C,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,EAAU;AACvC,IAAA,MAAM,IAAI,MAAM,4BAA4B,CAAA;AAAA,EAChD;AACA,EAAA,IAAI,MAAA,CAAO,SAAS,iBAAA,EAAmB;AACnC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,iBAAiB,CAAA,WAAA,CAAa,CAAA;AAAA,EACrF;AACA,EAAA,OAAO,IAAI,WAAA,EAAY,CAAE,MAAA,CAAO,MAAM,CAAA;AAC1C;AAMA,SAAS,sBAAsB,OAAA,EAA0B;AACrD,EAAA,IAAI,CAAC,OAAA,IAAW,OAAO,OAAA,KAAY,UAAU,OAAO,KAAA;AAEpD,EAAA,MAAM,WAAA,GAAc,+BAAA;AACpB,EAAA,OAAO,WAAA,CAAY,KAAK,OAAO,CAAA;AACnC;AAMA,SAAS,kBAAkB,SAAA,EAA4B;AACnD,EAAA,IAAI,CAAC,SAAA,IAAa,OAAO,SAAA,KAAc,UAAU,OAAO,KAAA;AAExD,EAAA,IAAI,SAAA,CAAU,MAAA,GAAS,GAAA,EAAK,OAAO,KAAA;AACnC,EAAA,MAAM,WAAA,GAAc,kBAAA;AACpB,EAAA,OAAO,WAAA,CAAY,KAAK,SAAS,CAAA;AACrC;AAMA,eAAsB,aAAA,CAClB,aAAA,EACA,SAAA,EACA,MAAA,EACA,WAAoB,KAAA,EAC4B;AAEhD,EAAA,IAAI,CAAC,qBAAA,CAAsB,aAAa,CAAA,EAAG;AACvC,IAAA,MAAM,IAAI,MAAM,+BAA+B,CAAA;AAAA,EACnD;AACA,EAAA,IAAI,CAAC,iBAAA,CAAkB,SAAS,CAAA,EAAG;AAC/B,IAAA,MAAM,IAAI,MAAM,2BAA2B,CAAA;AAAA,EAC/C;AACA,EAAA,IAAI,CAAC,OAAO,aAAA,IAAiB,MAAA,CAAO,iBAAiB,CAAA,IAAK,MAAA,CAAO,gBAAgB,GAAA,EAAK;AAClF,IAAA,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAAA,EACtE;AAEA,EAAA,MAAM,YAAYA,OAAA,EAAO;AACzB,EAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AACxC,EAAA,MAAM,SAAA,GAAY,GAAA,GAAO,MAAA,CAAO,aAAA,GAAgB,IAAA;AAEhD,EAAA,MAAM,OAAA,GAAuB;AAAA,IACzB,EAAA,EAAI,SAAA;AAAA,IACJ,aAAA;AAAA,IACA,gBAAA,EAAkB,CAAC,SAAS,CAAA;AAAA,IAC5B,cAAA,EAAgB,QAAQ,QAAQ,CAAA;AAAA,IAChC,SAAA,EAAW,GAAA;AAAA,IACX;AAAA,GACJ;AAEA,EAAA,MAAM,OAAA,GAA6B;AAAA,IAC/B,GAAA,EAAK,aAAA;AAAA,IACL,GAAA,EAAK,SAAA;AAAA,IACL,UAAU,OAAA,CAAQ,gBAAA;AAAA,IAClB,UAAU,OAAA,CAAQ,cAAA;AAAA,IAClB,GAAA,EAAK,GAAA;AAAA,IACL,GAAA,EAAK;AAAA,GACT;AAEA,EAAA,MAAM,KAAA,GAAQ,MAAM,IAAIC,YAAA,CAAQ,OAA6C,EACxE,kBAAA,CAAmB,EAAE,GAAA,EAAK,OAAA,EAAS,CAAA,CACnC,aAAY,CACZ,iBAAA,CAAkB,CAAA,EAAG,MAAA,CAAO,aAAa,CAAA,CAAA,CAAG,EAC5C,IAAA,CAAK,YAAA,CAAa,MAAA,CAAO,MAAM,CAAC,CAAA;AAErC,EAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAC5B;AAMA,eAAsB,eAAA,CAClB,OACA,MAAA,EAC0B;AAE1B,EAAA,IAAI,CAAC,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACrC,IAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,sBAAA,EAAuB;AAAA,EAC1D;AAEA,EAAA,IAAI;AACA,IAAA,MAAM,EAAE,SAAQ,GAAI,MAAMC,eAAU,KAAA,EAAO,YAAA,CAAa,MAAM,CAAC,CAAA;AAC/D,IAAA,MAAM,cAAA,GAAiB,OAAA;AAGvB,IAAA,IAAI,CAAC,eAAe,GAAA,IAAO,CAAC,eAAe,GAAA,IAAO,CAAC,eAAe,GAAA,EAAK;AACnE,MAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,2BAAA,EAA4B;AAAA,IAC/D;AAGA,IAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,KAAQ,GAAI,CAAA;AACxC,IAAA,IAAI,cAAA,CAAe,MAAM,GAAA,EAAK;AAC1B,MAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,iBAAA,EAAkB;AAAA,IACrD;AAGA,IAAA,IAAI,CAAC,qBAAA,CAAsB,cAAA,CAAe,GAAG,CAAA,EAAG;AAC5C,MAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,sBAAA,EAAuB;AAAA,IAC1D;AAEA,IAAA,MAAM,OAAA,GAAuB;AAAA,MACzB,IAAI,cAAA,CAAe,GAAA;AAAA,MACnB,eAAe,cAAA,CAAe,GAAA;AAAA,MAC9B,gBAAA,EAAkB,MAAM,OAAA,CAAQ,cAAA,CAAe,QAAQ,CAAA,GAAI,cAAA,CAAe,WAAW,EAAC;AAAA,MACtF,cAAA,EAAgB,OAAA,CAAQ,cAAA,CAAe,QAAQ,CAAA;AAAA,MAC/C,SAAA,EAAW,eAAe,GAAA,IAAO,CAAA;AAAA,MACjC,WAAW,cAAA,CAAe;AAAA,KAC9B;AAEA,IAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAA,EAAQ;AAAA,EAClC,SAAS,KAAA,EAAO;AAEZ,IAAA,OAAO,EAAE,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,iBAAA,EAAkB;AAAA,EACrD;AACJ;AAMA,eAAsB,mBAAA,CAClB,KAAA,EACA,SAAA,EACA,MAAA,EACuD;AAEvD,EAAA,IAAI,CAAC,iBAAA,CAAkB,SAAS,CAAA,EAAG;AAC/B,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,MAAM,UAAA,GAAa,MAAM,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAA;AACtD,EAAA,IAAI,CAAC,UAAA,CAAW,KAAA,IAAS,CAAC,WAAW,OAAA,EAAS;AAC1C,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,MAAM,UAAU,UAAA,CAAW,OAAA;AAG3B,EAAA,IAAI,OAAA,CAAQ,gBAAA,CAAiB,QAAA,CAAS,SAAS,CAAA,EAAG;AAC9C,IAAA,OAAO,EAAE,OAAO,OAAA,EAAQ;AAAA,EAC5B;AAGA,EAAA,IAAI,OAAA,CAAQ,gBAAA,CAAiB,MAAA,IAAU,wBAAA,EAA0B;AAC7D,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,MAAM,eAAA,GAAkB,CAAC,GAAG,OAAA,CAAQ,kBAAkB,SAAS,CAAA;AAE/D,EAAA,MAAM,OAAA,GAA6B;AAAA,IAC/B,KAAK,OAAA,CAAQ,aAAA;AAAA,IACb,KAAK,OAAA,CAAQ,EAAA;AAAA,IACb,QAAA,EAAU,eAAA;AAAA,IACV,UAAU,OAAA,CAAQ,cAAA;AAAA,IAClB,KAAK,OAAA,CAAQ,SAAA;AAAA,IACb,KAAK,OAAA,CAAQ;AAAA,GACjB;AAEA,EAAA,MAAM,QAAA,GAAW,MAAM,IAAID,YAAA,CAAQ,OAA6C,CAAA,CAC3E,kBAAA,CAAmB,EAAE,GAAA,EAAK,SAAS,CAAA,CACnC,IAAA,CAAK,YAAA,CAAa,MAAM,CAAC,CAAA;AAE9B,EAAA,OAAO;AAAA,IACH,KAAA,EAAO,QAAA;AAAA,IACP,OAAA,EAAS,EAAE,GAAG,OAAA,EAAS,kBAAkB,eAAA;AAAgB,GAC7D;AACJ;AAKA,eAAsB,iBAAA,CAClB,KAAA,EACA,SAAA,EACA,MAAA,EACgB;AAChB,EAAA,IAAI,CAAC,iBAAA,CAAkB,SAAS,CAAA,EAAG;AAC/B,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,MAAM,UAAA,GAAa,MAAM,eAAA,CAAgB,KAAA,EAAO,MAAM,CAAA;AACtD,EAAA,IAAI,CAAC,UAAA,CAAW,KAAA,IAAS,CAAC,WAAW,OAAA,EAAS;AAC1C,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,IAAI,UAAA,CAAW,QAAQ,cAAA,EAAgB;AACnC,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,OAAO,UAAA,CAAW,OAAA,CAAQ,gBAAA,CAAiB,QAAA,CAAS,SAAS,CAAA;AACjE","file":"index.cjs","sourcesContent":["// Session management with JWT (framework-agnostic core)\n// SECURITY: Uses jose library with HS256, constant-time validation, input sanitization\nimport { SignJWT, jwtVerify } from 'jose';\nimport { v4 as uuidv4 } from 'uuid';\nimport type { SessionData, SessionConfig, SessionValidation, SessionJWTPayload } from '../types';\n\n// Maximum articles per session to prevent unbounded growth\nconst MAX_ARTICLES_PER_SESSION = 100;\n\n// Minimum secret length for security\nconst MIN_SECRET_LENGTH = 32;\n\n/**\n * Get the secret key for JWT signing\n * SECURITY: Enforces minimum secret length\n */\nfunction getSecretKey(secret: string): Uint8Array {\n if (!secret || typeof secret !== 'string') {\n throw new Error('Session secret is required');\n }\n if (secret.length < MIN_SECRET_LENGTH) {\n throw new Error(`Session secret must be at least ${MIN_SECRET_LENGTH} characters`);\n }\n return new TextEncoder().encode(secret);\n}\n\n/**\n * Validate wallet address format (base58, 32-44 chars)\n * SECURITY: Prevents injection via wallet address field\n */\nfunction validateWalletAddress(address: string): boolean {\n if (!address || typeof address !== 'string') return false;\n // Solana addresses are base58, typically 32-44 characters\n const base58Regex = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;\n return base58Regex.test(address);\n}\n\n/**\n * Validate article ID format\n * SECURITY: Prevents injection via articleId field\n */\nfunction validateArticleId(articleId: string): boolean {\n if (!articleId || typeof articleId !== 'string') return false;\n // Allow alphanumeric, hyphens, underscores, max 128 chars\n if (articleId.length > 128) return false;\n const safeIdRegex = /^[a-zA-Z0-9_-]+$/;\n return safeIdRegex.test(articleId);\n}\n\n/**\n * Create a new session after successful payment\n * SECURITY: Validates inputs, enforces limits\n */\nexport async function createSession(\n walletAddress: string,\n articleId: string,\n config: SessionConfig,\n siteWide: boolean = false\n): Promise<{ token: string; session: SessionData }> {\n // Input validation\n if (!validateWalletAddress(walletAddress)) {\n throw new Error('Invalid wallet address format');\n }\n if (!validateArticleId(articleId)) {\n throw new Error('Invalid article ID format');\n }\n if (!config.durationHours || config.durationHours <= 0 || config.durationHours > 720) {\n throw new Error('Session duration must be between 1 and 720 hours');\n }\n\n const sessionId = uuidv4();\n const now = Math.floor(Date.now() / 1000);\n const expiresAt = now + (config.durationHours * 3600);\n\n const session: SessionData = {\n id: sessionId,\n walletAddress,\n unlockedArticles: [articleId],\n siteWideUnlock: Boolean(siteWide),\n createdAt: now,\n expiresAt,\n };\n\n const payload: SessionJWTPayload = {\n sub: walletAddress,\n sid: sessionId,\n articles: session.unlockedArticles,\n siteWide: session.siteWideUnlock,\n iat: now,\n exp: expiresAt,\n };\n\n const token = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(`${config.durationHours}h`)\n .sign(getSecretKey(config.secret));\n\n return { token, session };\n}\n\n/**\n * Validate an existing session token\n * SECURITY: jose library handles timing-safe comparison internally\n */\nexport async function validateSession(\n token: string,\n secret: string\n): Promise<SessionValidation> {\n // Input validation\n if (!token || typeof token !== 'string') {\n return { valid: false, reason: 'Invalid token format' };\n }\n\n try {\n const { payload } = await jwtVerify(token, getSecretKey(secret));\n const sessionPayload = payload as unknown as SessionJWTPayload;\n\n // Validate required fields exist\n if (!sessionPayload.sub || !sessionPayload.sid || !sessionPayload.exp) {\n return { valid: false, reason: 'Malformed session payload' };\n }\n\n // Check expiration (jose already checks, but double-check)\n const now = Math.floor(Date.now() / 1000);\n if (sessionPayload.exp < now) {\n return { valid: false, reason: 'Session expired' };\n }\n\n // Validate wallet address format from token\n if (!validateWalletAddress(sessionPayload.sub)) {\n return { valid: false, reason: 'Invalid session data' };\n }\n\n const session: SessionData = {\n id: sessionPayload.sid,\n walletAddress: sessionPayload.sub,\n unlockedArticles: Array.isArray(sessionPayload.articles) ? sessionPayload.articles : [],\n siteWideUnlock: Boolean(sessionPayload.siteWide),\n createdAt: sessionPayload.iat ?? 0,\n expiresAt: sessionPayload.exp,\n };\n\n return { valid: true, session };\n } catch (error) {\n // SECURITY: Don't expose internal error details\n return { valid: false, reason: 'Invalid session' };\n }\n}\n\n/**\n * Add an article to an existing session\n * SECURITY: Enforces article limit to prevent token bloat\n */\nexport async function addArticleToSession(\n token: string,\n articleId: string,\n secret: string\n): Promise<{ token: string; session: SessionData } | null> {\n // Validate article ID\n if (!validateArticleId(articleId)) {\n return null;\n }\n\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return null;\n }\n\n const session = validation.session;\n\n // Already unlocked\n if (session.unlockedArticles.includes(articleId)) {\n return { token, session };\n }\n\n // SECURITY: Enforce maximum articles per session\n if (session.unlockedArticles.length >= MAX_ARTICLES_PER_SESSION) {\n return null;\n }\n\n const updatedArticles = [...session.unlockedArticles, articleId];\n\n const payload: SessionJWTPayload = {\n sub: session.walletAddress,\n sid: session.id,\n articles: updatedArticles,\n siteWide: session.siteWideUnlock,\n iat: session.createdAt,\n exp: session.expiresAt,\n };\n\n const newToken = await new SignJWT(payload as unknown as Record<string, unknown>)\n .setProtectedHeader({ alg: 'HS256' })\n .sign(getSecretKey(secret));\n\n return {\n token: newToken,\n session: { ...session, unlockedArticles: updatedArticles },\n };\n}\n\n/**\n * Check if an article is unlocked for a session\n */\nexport async function isArticleUnlocked(\n token: string,\n articleId: string,\n secret: string\n): Promise<boolean> {\n if (!validateArticleId(articleId)) {\n return false;\n }\n\n const validation = await validateSession(token, secret);\n if (!validation.valid || !validation.session) {\n return false;\n }\n\n if (validation.session.siteWideUnlock) {\n return true;\n }\n\n return validation.session.unlockedArticles.includes(articleId);\n}\n"]}
@@ -1 +1,29 @@
1
- export { e as addArticleToSession, d as createSession, i as isArticleUnlocked, v as validateSession } from '../index-DptevtnU.cjs';
1
+ import { a as SessionConfig, S as SessionData, b as SessionValidation } from '../session-D2IoWAWV.cjs';
2
+
3
+ /**
4
+ * Create a new session after successful payment
5
+ * SECURITY: Validates inputs, enforces limits
6
+ */
7
+ declare function createSession(walletAddress: string, articleId: string, config: SessionConfig, siteWide?: boolean): Promise<{
8
+ token: string;
9
+ session: SessionData;
10
+ }>;
11
+ /**
12
+ * Validate an existing session token
13
+ * SECURITY: jose library handles timing-safe comparison internally
14
+ */
15
+ declare function validateSession(token: string, secret: string): Promise<SessionValidation>;
16
+ /**
17
+ * Add an article to an existing session
18
+ * SECURITY: Enforces article limit to prevent token bloat
19
+ */
20
+ declare function addArticleToSession(token: string, articleId: string, secret: string): Promise<{
21
+ token: string;
22
+ session: SessionData;
23
+ } | null>;
24
+ /**
25
+ * Check if an article is unlocked for a session
26
+ */
27
+ declare function isArticleUnlocked(token: string, articleId: string, secret: string): Promise<boolean>;
28
+
29
+ export { addArticleToSession, createSession, isArticleUnlocked, validateSession };
@@ -1 +1,29 @@
1
- export { e as addArticleToSession, d as createSession, i as isArticleUnlocked, v as validateSession } from '../index-DptevtnU.js';
1
+ import { a as SessionConfig, S as SessionData, b as SessionValidation } from '../session-D2IoWAWV.js';
2
+
3
+ /**
4
+ * Create a new session after successful payment
5
+ * SECURITY: Validates inputs, enforces limits
6
+ */
7
+ declare function createSession(walletAddress: string, articleId: string, config: SessionConfig, siteWide?: boolean): Promise<{
8
+ token: string;
9
+ session: SessionData;
10
+ }>;
11
+ /**
12
+ * Validate an existing session token
13
+ * SECURITY: jose library handles timing-safe comparison internally
14
+ */
15
+ declare function validateSession(token: string, secret: string): Promise<SessionValidation>;
16
+ /**
17
+ * Add an article to an existing session
18
+ * SECURITY: Enforces article limit to prevent token bloat
19
+ */
20
+ declare function addArticleToSession(token: string, articleId: string, secret: string): Promise<{
21
+ token: string;
22
+ session: SessionData;
23
+ } | null>;
24
+ /**
25
+ * Check if an article is unlocked for a session
26
+ */
27
+ declare function isArticleUnlocked(token: string, articleId: string, secret: string): Promise<boolean>;
28
+
29
+ export { addArticleToSession, createSession, isArticleUnlocked, validateSession };