@zendfi/sdk 0.5.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -4
- package/dist/cache-T5YPC7OK.mjs +9 -0
- package/dist/{chunk-YFOBPGQE.mjs → chunk-3ACJUM6V.mjs} +0 -8
- package/dist/chunk-5O5NAX65.mjs +366 -0
- package/dist/chunk-XERHBDUK.mjs +587 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/device-bound-crypto-VX7SFVHT.mjs +13 -0
- package/dist/express.d.mts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/express.mjs +2 -1
- package/dist/index.d.mts +1062 -3
- package/dist/index.d.ts +1062 -3
- package/dist/index.js +3185 -609
- package/dist/index.mjs +2181 -586
- package/dist/nextjs.d.mts +1 -1
- package/dist/nextjs.d.ts +1 -1
- package/dist/nextjs.mjs +2 -1
- package/dist/{webhook-handler-D5CigE9G.d.mts → webhook-handler-DGBeCWT-.d.mts} +20 -0
- package/dist/{webhook-handler-D5CigE9G.d.ts → webhook-handler-DGBeCWT-.d.ts} +20 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -38,20 +38,20 @@ console.log(payment.payment_url); // Send customer here
|
|
|
38
38
|
|
|
39
39
|
## Features
|
|
40
40
|
|
|
41
|
-
###
|
|
41
|
+
### **Core Payments** (Start Here)
|
|
42
42
|
- **Simple Payments** — QR codes, payment links, instant settlements
|
|
43
43
|
- **Payment Links** — Reusable checkout pages for social/email
|
|
44
44
|
- **Webhooks** — Real-time notifications with auto-verification
|
|
45
45
|
- **Test Mode** — Free devnet testing with no real money
|
|
46
46
|
- **Type-Safe** — Full TypeScript support with auto-completion
|
|
47
47
|
|
|
48
|
-
###
|
|
48
|
+
### **Scale Up** (When You Grow)
|
|
49
49
|
- **Subscriptions** — Recurring billing with trials
|
|
50
50
|
- **Installments** — Buy now, pay later flows
|
|
51
51
|
- **Invoices** — Professional invoicing with email
|
|
52
52
|
- **Payment Splits** — Revenue sharing for marketplaces
|
|
53
53
|
|
|
54
|
-
###
|
|
54
|
+
### **AI-Ready** (Optional Advanced)
|
|
55
55
|
- **Agent Keys** — Scoped API keys for AI with spending limits
|
|
56
56
|
- **Session Keys** — Pre-funded wallets for autonomous payments
|
|
57
57
|
- **Payment Intents** — Two-phase commit for reliable checkout
|
|
@@ -226,7 +226,7 @@ const payment = await zendfi.agent.pay({
|
|
|
226
226
|
|
|
227
227
|
---
|
|
228
228
|
|
|
229
|
-
##
|
|
229
|
+
## Core API Reference
|
|
230
230
|
|
|
231
231
|
### Namespaced APIs
|
|
232
232
|
|
|
@@ -399,6 +399,111 @@ const suggestion = await zendfi.pricing.getSuggestion({
|
|
|
399
399
|
**Supported Countries (27+):**
|
|
400
400
|
Argentina, Australia, Brazil, Canada, China, Colombia, Egypt, France, Germany, Ghana, Hong Kong, Hungary, India, Indonesia, Israel, Japan, Kenya, Mexico, Nigeria, Philippines, Poland, South Africa, Thailand, Turkey, Ukraine, United Kingdom, Vietnam, and more.
|
|
401
401
|
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Optional Helper Utilities
|
|
405
|
+
|
|
406
|
+
Production-ready utilities to simplify common integration patterns. All helpers are **optional**, **tree-shakeable**, and **zero-config**.
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import {
|
|
410
|
+
SessionKeyCache,
|
|
411
|
+
WalletConnector,
|
|
412
|
+
TransactionPoller,
|
|
413
|
+
DevTools
|
|
414
|
+
} from '@zendfi/sdk/helpers';
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Why Use Helpers?
|
|
418
|
+
|
|
419
|
+
- **Optional**: Import only what you need
|
|
420
|
+
- **Tree-shakeable**: Unused code eliminated by bundlers
|
|
421
|
+
- **Zero config**: Sensible defaults, works out of the box
|
|
422
|
+
- **Pluggable**: Bring your own storage/AI/PIN providers
|
|
423
|
+
- **Production-ready**: Full TypeScript types, error handling
|
|
424
|
+
|
|
425
|
+
### Available Helpers
|
|
426
|
+
|
|
427
|
+
| Helper | Purpose | Use Case |
|
|
428
|
+
|--------|---------|----------|
|
|
429
|
+
| `SessionKeyCache` | Cache encrypted session keys | Avoid re-prompting for PIN |
|
|
430
|
+
| `WalletConnector` | Detect & connect Solana wallets | Phantom, Solflare, Backpack |
|
|
431
|
+
| `PaymentIntentParser` | Parse natural language to payments | AI chat interfaces |
|
|
432
|
+
| `PINValidator` | Validate PIN strength | Device-bound security |
|
|
433
|
+
| `TransactionPoller` | Poll for confirmations | Wait for on-chain finality |
|
|
434
|
+
| `RetryStrategy` | Exponential backoff retries | Handle network failures |
|
|
435
|
+
| `SessionKeyLifecycle` | High-level session key manager | One-liner setup |
|
|
436
|
+
| `DevTools` | Debug mode & test utilities | Development & testing |
|
|
437
|
+
|
|
438
|
+
### Session Key Cache
|
|
439
|
+
|
|
440
|
+
Cache encrypted session keys to avoid re-prompting users for their PIN:
|
|
441
|
+
|
|
442
|
+
```typescript
|
|
443
|
+
import { SessionKeyCache, QuickCaches } from '@zendfi/sdk/helpers';
|
|
444
|
+
|
|
445
|
+
// Use presets
|
|
446
|
+
const cache = QuickCaches.persistent(); // 1 hour localStorage
|
|
447
|
+
|
|
448
|
+
// Use with device-bound session keys
|
|
449
|
+
const keypair = await cache.getCached(
|
|
450
|
+
sessionKeyId,
|
|
451
|
+
async () => {
|
|
452
|
+
const pin = await promptUserForPIN();
|
|
453
|
+
return await decryptKeypair(pin);
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Wallet Connector
|
|
459
|
+
|
|
460
|
+
Auto-detect and connect to Solana wallets:
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { WalletConnector } from '@zendfi/sdk/helpers';
|
|
464
|
+
|
|
465
|
+
const wallet = await WalletConnector.detectAndConnect();
|
|
466
|
+
console.log(wallet.address);
|
|
467
|
+
|
|
468
|
+
const signedTx = await wallet.signTransaction(transaction);
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Transaction Polling
|
|
472
|
+
|
|
473
|
+
Wait for confirmations with exponential backoff:
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
import { TransactionPoller } from '@zendfi/sdk/helpers';
|
|
477
|
+
|
|
478
|
+
const poller = new TransactionPoller({ connection: rpcConnection });
|
|
479
|
+
const result = await poller.waitForConfirmation(signature);
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Session Key Lifecycle
|
|
483
|
+
|
|
484
|
+
High-level wrapper for complete session key management:
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import { SessionKeyLifecycle } from '@zendfi/sdk/helpers';
|
|
488
|
+
|
|
489
|
+
const lifecycle = new SessionKeyLifecycle(zendfi, {
|
|
490
|
+
cache: QuickCaches.persistent(),
|
|
491
|
+
autoCleanup: true,
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
await lifecycle.createAndFund({
|
|
495
|
+
userWallet: userAddress,
|
|
496
|
+
agentId: 'my-agent',
|
|
497
|
+
limitUsdc: 100,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
await lifecycle.pay(5.00, 'Coffee');
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**Full documentation:** See [Helper Utilities Guide](https://docs.zendfi.tech/helpers) for complete API reference and examples.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
402
507
|
### Autonomous Delegation
|
|
403
508
|
|
|
404
509
|
Enable agents to make payments without per-transaction approval:
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
1
|
// src/webhook-handler.ts
|
|
9
2
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
10
3
|
var processedWebhooks = /* @__PURE__ */ new Set();
|
|
@@ -147,6 +140,5 @@ async function processWebhook(a, b, c) {
|
|
|
147
140
|
}
|
|
148
141
|
|
|
149
142
|
export {
|
|
150
|
-
__require,
|
|
151
143
|
processWebhook
|
|
152
144
|
};
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
// src/helpers/cache.ts
|
|
2
|
+
var SessionKeyCache = class {
|
|
3
|
+
memoryCache = /* @__PURE__ */ new Map();
|
|
4
|
+
config;
|
|
5
|
+
refreshTimers = /* @__PURE__ */ new Map();
|
|
6
|
+
constructor(config = {}) {
|
|
7
|
+
this.config = {
|
|
8
|
+
storage: config.storage || "memory",
|
|
9
|
+
ttl: config.ttl || 30 * 60 * 1e3,
|
|
10
|
+
// 30 minutes default
|
|
11
|
+
autoRefresh: config.autoRefresh || false,
|
|
12
|
+
namespace: config.namespace || "zendfi_cache",
|
|
13
|
+
debug: config.debug || false
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Get cached keypair or decrypt and cache
|
|
18
|
+
*/
|
|
19
|
+
async getCached(sessionKeyId, decryptFn, options) {
|
|
20
|
+
this.log(`getCached: ${sessionKeyId}`);
|
|
21
|
+
const memoryCached = this.memoryCache.get(sessionKeyId);
|
|
22
|
+
if (memoryCached && Date.now() < memoryCached.expiry) {
|
|
23
|
+
this.log(`Memory cache HIT: ${sessionKeyId}`);
|
|
24
|
+
return memoryCached.keypair;
|
|
25
|
+
}
|
|
26
|
+
if (this.config.storage !== "memory") {
|
|
27
|
+
const persistentCached = await this.getFromStorage(sessionKeyId);
|
|
28
|
+
if (persistentCached && Date.now() < persistentCached.expiry) {
|
|
29
|
+
if (options?.deviceFingerprint && persistentCached.deviceFingerprint) {
|
|
30
|
+
if (options.deviceFingerprint !== persistentCached.deviceFingerprint) {
|
|
31
|
+
this.log(`Device fingerprint mismatch for ${sessionKeyId}`);
|
|
32
|
+
await this.invalidate(sessionKeyId);
|
|
33
|
+
return await this.decryptAndCache(sessionKeyId, decryptFn, options);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
this.log(`Persistent cache HIT: ${sessionKeyId}`);
|
|
37
|
+
this.memoryCache.set(sessionKeyId, persistentCached);
|
|
38
|
+
return persistentCached.keypair;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
this.log(`Cache MISS: ${sessionKeyId}`);
|
|
42
|
+
return await this.decryptAndCache(sessionKeyId, decryptFn, options);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Decrypt keypair and cache it
|
|
46
|
+
*/
|
|
47
|
+
async decryptAndCache(sessionKeyId, decryptFn, options) {
|
|
48
|
+
const keypair = await decryptFn();
|
|
49
|
+
const expiry = Date.now() + this.config.ttl;
|
|
50
|
+
const cached = {
|
|
51
|
+
keypair,
|
|
52
|
+
expiry,
|
|
53
|
+
sessionKeyId,
|
|
54
|
+
deviceFingerprint: options?.deviceFingerprint
|
|
55
|
+
};
|
|
56
|
+
this.memoryCache.set(sessionKeyId, cached);
|
|
57
|
+
if (this.config.storage !== "memory") {
|
|
58
|
+
await this.setInStorage(sessionKeyId, cached);
|
|
59
|
+
}
|
|
60
|
+
if (this.config.autoRefresh) {
|
|
61
|
+
this.setupAutoRefresh(sessionKeyId, decryptFn, options);
|
|
62
|
+
}
|
|
63
|
+
this.log(`Cached: ${sessionKeyId}, expires in ${this.config.ttl}ms`);
|
|
64
|
+
return keypair;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Invalidate cached keypair
|
|
68
|
+
*/
|
|
69
|
+
async invalidate(sessionKeyId) {
|
|
70
|
+
this.log(`Invalidating: ${sessionKeyId}`);
|
|
71
|
+
this.memoryCache.delete(sessionKeyId);
|
|
72
|
+
const timer = this.refreshTimers.get(sessionKeyId);
|
|
73
|
+
if (timer) {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
this.refreshTimers.delete(sessionKeyId);
|
|
76
|
+
}
|
|
77
|
+
if (this.config.storage !== "memory") {
|
|
78
|
+
await this.removeFromStorage(sessionKeyId);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Clear all cached keypairs
|
|
83
|
+
*/
|
|
84
|
+
async clear() {
|
|
85
|
+
this.log("Clearing all cache");
|
|
86
|
+
this.memoryCache.clear();
|
|
87
|
+
for (const timer of this.refreshTimers.values()) {
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
}
|
|
90
|
+
this.refreshTimers.clear();
|
|
91
|
+
if (this.config.storage !== "memory") {
|
|
92
|
+
await this.clearStorage();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get cache statistics
|
|
97
|
+
*/
|
|
98
|
+
getStats() {
|
|
99
|
+
const entries = Array.from(this.memoryCache.entries()).map(([id, cached]) => ({
|
|
100
|
+
sessionKeyId: id,
|
|
101
|
+
expiresIn: Math.max(0, cached.expiry - Date.now())
|
|
102
|
+
}));
|
|
103
|
+
return { size: this.memoryCache.size, entries };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if a session key is cached and valid
|
|
107
|
+
*/
|
|
108
|
+
isCached(sessionKeyId) {
|
|
109
|
+
const cached = this.memoryCache.get(sessionKeyId);
|
|
110
|
+
return cached ? Date.now() < cached.expiry : false;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Update TTL for a cached session key
|
|
114
|
+
*/
|
|
115
|
+
async extendTTL(sessionKeyId, additionalMs) {
|
|
116
|
+
const cached = this.memoryCache.get(sessionKeyId);
|
|
117
|
+
if (!cached) return false;
|
|
118
|
+
cached.expiry += additionalMs;
|
|
119
|
+
this.memoryCache.set(sessionKeyId, cached);
|
|
120
|
+
if (this.config.storage !== "memory") {
|
|
121
|
+
await this.setInStorage(sessionKeyId, cached);
|
|
122
|
+
}
|
|
123
|
+
this.log(`Extended TTL for ${sessionKeyId} by ${additionalMs}ms`);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
// ============================================
|
|
127
|
+
// Storage Backend Implementations
|
|
128
|
+
// ============================================
|
|
129
|
+
async getFromStorage(sessionKeyId) {
|
|
130
|
+
try {
|
|
131
|
+
const key = this.getStorageKey(sessionKeyId);
|
|
132
|
+
if (this.config.storage === "localStorage") {
|
|
133
|
+
const data = localStorage.getItem(key);
|
|
134
|
+
if (!data) return null;
|
|
135
|
+
const parsed = JSON.parse(data);
|
|
136
|
+
return {
|
|
137
|
+
...parsed,
|
|
138
|
+
keypair: this.deserializeKeypair(parsed.keypair)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (this.config.storage === "indexedDB") {
|
|
142
|
+
return await this.getFromIndexedDB(key);
|
|
143
|
+
}
|
|
144
|
+
if (typeof this.config.storage === "object") {
|
|
145
|
+
const data = await this.config.storage.get(key);
|
|
146
|
+
if (!data) return null;
|
|
147
|
+
const parsed = JSON.parse(data);
|
|
148
|
+
return {
|
|
149
|
+
...parsed,
|
|
150
|
+
keypair: this.deserializeKeypair(parsed.keypair)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
this.log(`Error reading from storage: ${error}`);
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
async setInStorage(sessionKeyId, cached) {
|
|
159
|
+
try {
|
|
160
|
+
const key = this.getStorageKey(sessionKeyId);
|
|
161
|
+
const serialized = {
|
|
162
|
+
...cached,
|
|
163
|
+
keypair: this.serializeKeypair(cached.keypair)
|
|
164
|
+
};
|
|
165
|
+
if (this.config.storage === "localStorage") {
|
|
166
|
+
localStorage.setItem(key, JSON.stringify(serialized));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (this.config.storage === "indexedDB") {
|
|
170
|
+
await this.setInIndexedDB(key, serialized);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (typeof this.config.storage === "object") {
|
|
174
|
+
await this.config.storage.set(key, JSON.stringify(serialized));
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
this.log(`Error writing to storage: ${error}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async removeFromStorage(sessionKeyId) {
|
|
181
|
+
try {
|
|
182
|
+
const key = this.getStorageKey(sessionKeyId);
|
|
183
|
+
if (this.config.storage === "localStorage") {
|
|
184
|
+
localStorage.removeItem(key);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (this.config.storage === "indexedDB") {
|
|
188
|
+
await this.removeFromIndexedDB(key);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (typeof this.config.storage === "object") {
|
|
192
|
+
await this.config.storage.remove(key);
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
this.log(`Error removing from storage: ${error}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async clearStorage() {
|
|
199
|
+
try {
|
|
200
|
+
if (this.config.storage === "localStorage") {
|
|
201
|
+
const keysToRemove = [];
|
|
202
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
203
|
+
const key = localStorage.key(i);
|
|
204
|
+
if (key?.startsWith(this.config.namespace)) {
|
|
205
|
+
keysToRemove.push(key);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (this.config.storage === "indexedDB") {
|
|
212
|
+
await this.clearIndexedDB();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (typeof this.config.storage === "object") {
|
|
216
|
+
await this.config.storage.clear();
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
this.log(`Error clearing storage: ${error}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// ============================================
|
|
223
|
+
// IndexedDB Helpers
|
|
224
|
+
// ============================================
|
|
225
|
+
async getFromIndexedDB(key) {
|
|
226
|
+
return new Promise((resolve) => {
|
|
227
|
+
const request = indexedDB.open(this.config.namespace, 1);
|
|
228
|
+
request.onerror = () => resolve(null);
|
|
229
|
+
request.onupgradeneeded = (event) => {
|
|
230
|
+
const db = event.target.result;
|
|
231
|
+
if (!db.objectStoreNames.contains("cache")) {
|
|
232
|
+
db.createObjectStore("cache");
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
request.onsuccess = (event) => {
|
|
236
|
+
const db = event.target.result;
|
|
237
|
+
const transaction = db.transaction(["cache"], "readonly");
|
|
238
|
+
const store = transaction.objectStore("cache");
|
|
239
|
+
const getRequest = store.get(key);
|
|
240
|
+
getRequest.onsuccess = () => {
|
|
241
|
+
resolve(getRequest.result || null);
|
|
242
|
+
};
|
|
243
|
+
getRequest.onerror = () => resolve(null);
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async setInIndexedDB(key, value) {
|
|
248
|
+
return new Promise((resolve, reject) => {
|
|
249
|
+
const request = indexedDB.open(this.config.namespace, 1);
|
|
250
|
+
request.onerror = () => reject(new Error("IndexedDB error"));
|
|
251
|
+
request.onupgradeneeded = (event) => {
|
|
252
|
+
const db = event.target.result;
|
|
253
|
+
if (!db.objectStoreNames.contains("cache")) {
|
|
254
|
+
db.createObjectStore("cache");
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
request.onsuccess = (event) => {
|
|
258
|
+
const db = event.target.result;
|
|
259
|
+
const transaction = db.transaction(["cache"], "readwrite");
|
|
260
|
+
const store = transaction.objectStore("cache");
|
|
261
|
+
store.put(value, key);
|
|
262
|
+
transaction.oncomplete = () => resolve();
|
|
263
|
+
transaction.onerror = () => reject(new Error("IndexedDB transaction error"));
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
async removeFromIndexedDB(key) {
|
|
268
|
+
return new Promise((resolve) => {
|
|
269
|
+
const request = indexedDB.open(this.config.namespace, 1);
|
|
270
|
+
request.onsuccess = (event) => {
|
|
271
|
+
const db = event.target.result;
|
|
272
|
+
const transaction = db.transaction(["cache"], "readwrite");
|
|
273
|
+
const store = transaction.objectStore("cache");
|
|
274
|
+
store.delete(key);
|
|
275
|
+
transaction.oncomplete = () => resolve();
|
|
276
|
+
};
|
|
277
|
+
request.onerror = () => resolve();
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
async clearIndexedDB() {
|
|
281
|
+
return new Promise((resolve) => {
|
|
282
|
+
const request = indexedDB.open(this.config.namespace, 1);
|
|
283
|
+
request.onsuccess = (event) => {
|
|
284
|
+
const db = event.target.result;
|
|
285
|
+
const transaction = db.transaction(["cache"], "readwrite");
|
|
286
|
+
const store = transaction.objectStore("cache");
|
|
287
|
+
store.clear();
|
|
288
|
+
transaction.oncomplete = () => resolve();
|
|
289
|
+
};
|
|
290
|
+
request.onerror = () => resolve();
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
// ============================================
|
|
294
|
+
// Serialization
|
|
295
|
+
// ============================================
|
|
296
|
+
serializeKeypair(keypair) {
|
|
297
|
+
if (keypair && typeof keypair === "object" && "secretKey" in keypair) {
|
|
298
|
+
return {
|
|
299
|
+
type: "solana",
|
|
300
|
+
secretKey: Array.from(keypair.secretKey)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
if (keypair instanceof Uint8Array) {
|
|
304
|
+
return {
|
|
305
|
+
type: "uint8array",
|
|
306
|
+
data: Array.from(keypair)
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return keypair;
|
|
310
|
+
}
|
|
311
|
+
deserializeKeypair(data) {
|
|
312
|
+
if (!data || typeof data !== "object") return data;
|
|
313
|
+
if (data.type === "solana" && data.secretKey) {
|
|
314
|
+
return new Uint8Array(data.secretKey);
|
|
315
|
+
}
|
|
316
|
+
if (data.type === "uint8array" && data.data) {
|
|
317
|
+
return new Uint8Array(data.data);
|
|
318
|
+
}
|
|
319
|
+
return data;
|
|
320
|
+
}
|
|
321
|
+
// ============================================
|
|
322
|
+
// Auto-Refresh
|
|
323
|
+
// ============================================
|
|
324
|
+
setupAutoRefresh(sessionKeyId, decryptFn, options) {
|
|
325
|
+
const existingTimer = this.refreshTimers.get(sessionKeyId);
|
|
326
|
+
if (existingTimer) {
|
|
327
|
+
clearTimeout(existingTimer);
|
|
328
|
+
}
|
|
329
|
+
const refreshIn = Math.max(0, this.config.ttl - 5 * 60 * 1e3);
|
|
330
|
+
const timer = setTimeout(async () => {
|
|
331
|
+
this.log(`Auto-refreshing: ${sessionKeyId}`);
|
|
332
|
+
try {
|
|
333
|
+
await this.decryptAndCache(sessionKeyId, decryptFn, options);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
this.log(`Auto-refresh failed: ${error}`);
|
|
336
|
+
}
|
|
337
|
+
}, refreshIn);
|
|
338
|
+
this.refreshTimers.set(sessionKeyId, timer);
|
|
339
|
+
}
|
|
340
|
+
// ============================================
|
|
341
|
+
// Utilities
|
|
342
|
+
// ============================================
|
|
343
|
+
getStorageKey(sessionKeyId) {
|
|
344
|
+
return `${this.config.namespace}:${sessionKeyId}`;
|
|
345
|
+
}
|
|
346
|
+
log(message) {
|
|
347
|
+
if (this.config.debug) {
|
|
348
|
+
console.log(`[SessionKeyCache] ${message}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
var QuickCaches = {
|
|
353
|
+
/** Memory-only cache (30 minutes) */
|
|
354
|
+
memory: () => new SessionKeyCache({ storage: "memory", ttl: 30 * 60 * 1e3 }),
|
|
355
|
+
/** Persistent cache (1 hour, survives reload) */
|
|
356
|
+
persistent: () => new SessionKeyCache({ storage: "localStorage", ttl: 60 * 60 * 1e3 }),
|
|
357
|
+
/** Long-term cache (24 hours, IndexedDB) */
|
|
358
|
+
longTerm: () => new SessionKeyCache({ storage: "indexedDB", ttl: 24 * 60 * 60 * 1e3, autoRefresh: true }),
|
|
359
|
+
/** Secure cache (5 minutes, memory-only) */
|
|
360
|
+
secure: () => new SessionKeyCache({ storage: "memory", ttl: 5 * 60 * 1e3 })
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
export {
|
|
364
|
+
SessionKeyCache,
|
|
365
|
+
QuickCaches
|
|
366
|
+
};
|