@thru/indexed-db-stamper 0.0.4
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/index.d.ts +182 -0
- package/dist/index.js +299 -0
- package/dist/index.js.map +1 -0
- package/package.json +24 -0
- package/src/account-store.ts +140 -0
- package/src/connected-apps-store.ts +95 -0
- package/src/index.ts +12 -0
- package/src/schema.ts +68 -0
- package/src/types.ts +14 -0
- package/src/wallet-store.ts +139 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +10 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { EncryptedData } from '@thru/crypto';
|
|
2
|
+
import { AddressType, AppMetadata, ConnectedApp } from '@thru/chain-interfaces';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* IndexedDB storage for wallet data
|
|
6
|
+
*/
|
|
7
|
+
declare class WalletStorage {
|
|
8
|
+
private static dbPromise;
|
|
9
|
+
/**
|
|
10
|
+
* Get or create database connection
|
|
11
|
+
*/
|
|
12
|
+
private static getDB;
|
|
13
|
+
/**
|
|
14
|
+
* Save encrypted seed to storage
|
|
15
|
+
* @param encryptedSeed - Encrypted seed data
|
|
16
|
+
* @param walletId - Optional wallet ID (defaults to 'default')
|
|
17
|
+
*/
|
|
18
|
+
static saveEncryptedSeed(encryptedSeed: EncryptedData, walletId?: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Get encrypted seed from storage
|
|
21
|
+
* @param walletId - Wallet ID (defaults to 'default')
|
|
22
|
+
* @returns Encrypted seed data or null if not found
|
|
23
|
+
*/
|
|
24
|
+
static getEncryptedSeed(walletId?: string): Promise<EncryptedData | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Check if wallet exists
|
|
27
|
+
* @param walletId - Wallet ID (defaults to 'default')
|
|
28
|
+
*/
|
|
29
|
+
static walletExists(walletId?: string): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Delete wallet from storage
|
|
32
|
+
* @param walletId - Wallet ID (defaults to 'default')
|
|
33
|
+
*/
|
|
34
|
+
static deleteWallet(walletId?: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Clear all wallet data (use with caution!)
|
|
37
|
+
*/
|
|
38
|
+
static clearAll(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Get storage quota information
|
|
41
|
+
*/
|
|
42
|
+
static getStorageInfo(): Promise<{
|
|
43
|
+
usage: number;
|
|
44
|
+
quota: number;
|
|
45
|
+
available: number;
|
|
46
|
+
} | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Request persistent storage (prevents data from being cleared)
|
|
49
|
+
*/
|
|
50
|
+
static requestPersistentStorage(): Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Check if storage is persistent
|
|
53
|
+
*/
|
|
54
|
+
static isPersistent(): Promise<boolean>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Stored account representation in IndexedDB
|
|
59
|
+
*/
|
|
60
|
+
interface StoredAccount {
|
|
61
|
+
index: number;
|
|
62
|
+
label: string;
|
|
63
|
+
publicKey: string;
|
|
64
|
+
path: string;
|
|
65
|
+
createdAt: Date;
|
|
66
|
+
addressType?: AddressType;
|
|
67
|
+
publicKeyRawBase64?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Account storage management using IndexedDB
|
|
72
|
+
* Stores metadata for each derived account (but NOT private keys)
|
|
73
|
+
*/
|
|
74
|
+
declare class AccountStorage {
|
|
75
|
+
private static dbPromise;
|
|
76
|
+
/**
|
|
77
|
+
* Initialize or get existing database connection
|
|
78
|
+
*/
|
|
79
|
+
private static getDB;
|
|
80
|
+
/**
|
|
81
|
+
* Save a new account to storage
|
|
82
|
+
* @param account - Account metadata to save
|
|
83
|
+
*/
|
|
84
|
+
static saveAccount(account: StoredAccount): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Get all accounts, sorted by index (ascending)
|
|
87
|
+
* @returns Array of stored accounts
|
|
88
|
+
*/
|
|
89
|
+
static getAccounts(): Promise<StoredAccount[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Get a specific account by index
|
|
92
|
+
* @param index - Account index
|
|
93
|
+
* @returns Account or null if not found
|
|
94
|
+
*/
|
|
95
|
+
static getAccount(index: number): Promise<StoredAccount | null>;
|
|
96
|
+
/**
|
|
97
|
+
* Update an account's label
|
|
98
|
+
* @param index - Account index
|
|
99
|
+
* @param label - New label
|
|
100
|
+
*/
|
|
101
|
+
static updateAccountLabel(index: number, label: string): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Get the next available account index
|
|
104
|
+
* @returns Next index (0 if no accounts exist)
|
|
105
|
+
*/
|
|
106
|
+
static getNextAccountIndex(): Promise<number>;
|
|
107
|
+
/**
|
|
108
|
+
* Check if any accounts exist
|
|
109
|
+
* @returns true if at least one account exists
|
|
110
|
+
*/
|
|
111
|
+
static hasAccounts(): Promise<boolean>;
|
|
112
|
+
/**
|
|
113
|
+
* Get total number of accounts
|
|
114
|
+
* @returns Account count
|
|
115
|
+
*/
|
|
116
|
+
static getAccountCount(): Promise<number>;
|
|
117
|
+
/**
|
|
118
|
+
* Clear all accounts (use with caution!)
|
|
119
|
+
* Used primarily for testing or wallet reset
|
|
120
|
+
*/
|
|
121
|
+
static clearAccounts(): Promise<void>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface ConnectedAppUpsert {
|
|
125
|
+
accountId: number;
|
|
126
|
+
appId: string;
|
|
127
|
+
origin: string;
|
|
128
|
+
metadata: AppMetadata;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Storage helper for connected dApps per account
|
|
132
|
+
*/
|
|
133
|
+
declare class ConnectedAppsStorage {
|
|
134
|
+
private static dbPromise;
|
|
135
|
+
private static getDB;
|
|
136
|
+
static upsert(app: ConnectedAppUpsert): Promise<ConnectedApp>;
|
|
137
|
+
static listByAccount(accountId: number): Promise<ConnectedApp[]>;
|
|
138
|
+
static remove(accountId: number, appId: string): Promise<void>;
|
|
139
|
+
static clear(): Promise<void>;
|
|
140
|
+
static get(accountId: number, appId: string): Promise<ConnectedApp | null>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* IndexedDB schema for wallet storage
|
|
145
|
+
*/
|
|
146
|
+
declare const DB_NAME = "thru-wallet";
|
|
147
|
+
declare const DB_VERSION = 2;
|
|
148
|
+
/**
|
|
149
|
+
* Object store names
|
|
150
|
+
*/
|
|
151
|
+
declare enum StoreName {
|
|
152
|
+
WALLET = "wallet",
|
|
153
|
+
SETTINGS = "settings",
|
|
154
|
+
CONNECTED_APPS = "connectedApps"
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Wallet data stored in IndexedDB
|
|
158
|
+
*/
|
|
159
|
+
interface WalletData {
|
|
160
|
+
id: string;
|
|
161
|
+
encryptedSeed: string;
|
|
162
|
+
createdAt: number;
|
|
163
|
+
updatedAt: number;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Settings data
|
|
167
|
+
*/
|
|
168
|
+
interface SettingsData {
|
|
169
|
+
key: string;
|
|
170
|
+
value: any;
|
|
171
|
+
}
|
|
172
|
+
interface ConnectedAppData {
|
|
173
|
+
key: string;
|
|
174
|
+
accountId: number;
|
|
175
|
+
appId: string;
|
|
176
|
+
origin: string;
|
|
177
|
+
metadata: AppMetadata;
|
|
178
|
+
connectedAt: number;
|
|
179
|
+
updatedAt: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { AccountStorage, type ConnectedAppData, ConnectedAppsStorage, DB_NAME, DB_VERSION, type SettingsData, StoreName, type StoredAccount, type WalletData, WalletStorage };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { openDB } from 'idb';
|
|
2
|
+
import { EncryptionService } from '@thru/crypto';
|
|
3
|
+
|
|
4
|
+
// src/wallet-store.ts
|
|
5
|
+
|
|
6
|
+
// src/schema.ts
|
|
7
|
+
var DB_NAME = "thru-wallet";
|
|
8
|
+
var DB_VERSION = 2;
|
|
9
|
+
var StoreName = /* @__PURE__ */ ((StoreName2) => {
|
|
10
|
+
StoreName2["WALLET"] = "wallet";
|
|
11
|
+
StoreName2["SETTINGS"] = "settings";
|
|
12
|
+
StoreName2["CONNECTED_APPS"] = "connectedApps";
|
|
13
|
+
return StoreName2;
|
|
14
|
+
})(StoreName || {});
|
|
15
|
+
function initializeSchema(db) {
|
|
16
|
+
if (!db.objectStoreNames.contains("wallet" /* WALLET */)) {
|
|
17
|
+
const walletStore = db.createObjectStore("wallet" /* WALLET */, { keyPath: "id" });
|
|
18
|
+
walletStore.createIndex("createdAt", "createdAt", { unique: false });
|
|
19
|
+
}
|
|
20
|
+
if (!db.objectStoreNames.contains("settings" /* SETTINGS */)) {
|
|
21
|
+
db.createObjectStore("settings" /* SETTINGS */, { keyPath: "key" });
|
|
22
|
+
}
|
|
23
|
+
if (!db.objectStoreNames.contains("connectedApps" /* CONNECTED_APPS */)) {
|
|
24
|
+
const store = db.createObjectStore("connectedApps" /* CONNECTED_APPS */, { keyPath: "key" });
|
|
25
|
+
store.createIndex("by-account", "accountId", { unique: false });
|
|
26
|
+
store.createIndex("by-updated", "updatedAt", { unique: false });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
var WalletStorage = class {
|
|
30
|
+
/**
|
|
31
|
+
* Get or create database connection
|
|
32
|
+
*/
|
|
33
|
+
static async getDB() {
|
|
34
|
+
if (!this.dbPromise) {
|
|
35
|
+
this.dbPromise = openDB(DB_NAME, DB_VERSION, {
|
|
36
|
+
upgrade(db) {
|
|
37
|
+
initializeSchema(db);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return this.dbPromise;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Save encrypted seed to storage
|
|
45
|
+
* @param encryptedSeed - Encrypted seed data
|
|
46
|
+
* @param walletId - Optional wallet ID (defaults to 'default')
|
|
47
|
+
*/
|
|
48
|
+
static async saveEncryptedSeed(encryptedSeed, walletId = "default") {
|
|
49
|
+
const db = await this.getDB();
|
|
50
|
+
const serialized = EncryptionService.serialize(encryptedSeed);
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
const walletData = {
|
|
53
|
+
id: walletId,
|
|
54
|
+
encryptedSeed: serialized,
|
|
55
|
+
createdAt: now,
|
|
56
|
+
updatedAt: now
|
|
57
|
+
};
|
|
58
|
+
await db.put("wallet" /* WALLET */, walletData);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get encrypted seed from storage
|
|
62
|
+
* @param walletId - Wallet ID (defaults to 'default')
|
|
63
|
+
* @returns Encrypted seed data or null if not found
|
|
64
|
+
*/
|
|
65
|
+
static async getEncryptedSeed(walletId = "default") {
|
|
66
|
+
const db = await this.getDB();
|
|
67
|
+
const walletData = await db.get("wallet" /* WALLET */, walletId);
|
|
68
|
+
if (!walletData) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return EncryptionService.deserialize(walletData.encryptedSeed);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Check if wallet exists
|
|
75
|
+
* @param walletId - Wallet ID (defaults to 'default')
|
|
76
|
+
*/
|
|
77
|
+
static async walletExists(walletId = "default") {
|
|
78
|
+
const db = await this.getDB();
|
|
79
|
+
const walletData = await db.get("wallet" /* WALLET */, walletId);
|
|
80
|
+
return walletData !== void 0;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Delete wallet from storage
|
|
84
|
+
* @param walletId - Wallet ID (defaults to 'default')
|
|
85
|
+
*/
|
|
86
|
+
static async deleteWallet(walletId = "default") {
|
|
87
|
+
const db = await this.getDB();
|
|
88
|
+
await db.delete("wallet" /* WALLET */, walletId);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Clear all wallet data (use with caution!)
|
|
92
|
+
*/
|
|
93
|
+
static async clearAll() {
|
|
94
|
+
const db = await this.getDB();
|
|
95
|
+
await db.clear("wallet" /* WALLET */);
|
|
96
|
+
await db.clear("settings" /* SETTINGS */);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get storage quota information
|
|
100
|
+
*/
|
|
101
|
+
static async getStorageInfo() {
|
|
102
|
+
if (!navigator.storage || !navigator.storage.estimate) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const estimate = await navigator.storage.estimate();
|
|
106
|
+
const usage = estimate.usage || 0;
|
|
107
|
+
const quota = estimate.quota || 0;
|
|
108
|
+
return {
|
|
109
|
+
usage,
|
|
110
|
+
quota,
|
|
111
|
+
available: quota - usage
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Request persistent storage (prevents data from being cleared)
|
|
116
|
+
*/
|
|
117
|
+
static async requestPersistentStorage() {
|
|
118
|
+
if (!navigator.storage || !navigator.storage.persist) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return await navigator.storage.persist();
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if storage is persistent
|
|
125
|
+
*/
|
|
126
|
+
static async isPersistent() {
|
|
127
|
+
if (!navigator.storage || !navigator.storage.persisted) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
return await navigator.storage.persisted();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
WalletStorage.dbPromise = null;
|
|
134
|
+
var DB_NAME2 = "thru-wallet-accounts";
|
|
135
|
+
var DB_VERSION2 = 1;
|
|
136
|
+
var STORE_NAME = "accounts";
|
|
137
|
+
var AccountStorage = class {
|
|
138
|
+
/**
|
|
139
|
+
* Initialize or get existing database connection
|
|
140
|
+
*/
|
|
141
|
+
static async getDB() {
|
|
142
|
+
if (!this.dbPromise) {
|
|
143
|
+
this.dbPromise = openDB(DB_NAME2, DB_VERSION2, {
|
|
144
|
+
upgrade(db) {
|
|
145
|
+
const store = db.createObjectStore(STORE_NAME, {
|
|
146
|
+
keyPath: "index"
|
|
147
|
+
});
|
|
148
|
+
store.createIndex("by-created", "createdAt");
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return this.dbPromise;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Save a new account to storage
|
|
156
|
+
* @param account - Account metadata to save
|
|
157
|
+
*/
|
|
158
|
+
static async saveAccount(account) {
|
|
159
|
+
const db = await this.getDB();
|
|
160
|
+
await db.put(STORE_NAME, account);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get all accounts, sorted by index (ascending)
|
|
164
|
+
* @returns Array of stored accounts
|
|
165
|
+
*/
|
|
166
|
+
static async getAccounts() {
|
|
167
|
+
const db = await this.getDB();
|
|
168
|
+
const accounts = await db.getAll(STORE_NAME);
|
|
169
|
+
return accounts.sort((a, b) => a.index - b.index);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get a specific account by index
|
|
173
|
+
* @param index - Account index
|
|
174
|
+
* @returns Account or null if not found
|
|
175
|
+
*/
|
|
176
|
+
static async getAccount(index) {
|
|
177
|
+
const db = await this.getDB();
|
|
178
|
+
const account = await db.get(STORE_NAME, index);
|
|
179
|
+
return account || null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Update an account's label
|
|
183
|
+
* @param index - Account index
|
|
184
|
+
* @param label - New label
|
|
185
|
+
*/
|
|
186
|
+
static async updateAccountLabel(index, label) {
|
|
187
|
+
const db = await this.getDB();
|
|
188
|
+
const account = await db.get(STORE_NAME, index);
|
|
189
|
+
if (!account) {
|
|
190
|
+
throw new Error(`Account ${index} not found`);
|
|
191
|
+
}
|
|
192
|
+
account.label = label;
|
|
193
|
+
await db.put(STORE_NAME, account);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get the next available account index
|
|
197
|
+
* @returns Next index (0 if no accounts exist)
|
|
198
|
+
*/
|
|
199
|
+
static async getNextAccountIndex() {
|
|
200
|
+
const accounts = await this.getAccounts();
|
|
201
|
+
if (accounts.length === 0) {
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
const maxIndex = Math.max(...accounts.map((a) => a.index));
|
|
205
|
+
return maxIndex + 1;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if any accounts exist
|
|
209
|
+
* @returns true if at least one account exists
|
|
210
|
+
*/
|
|
211
|
+
static async hasAccounts() {
|
|
212
|
+
const db = await this.getDB();
|
|
213
|
+
const count = await db.count(STORE_NAME);
|
|
214
|
+
return count > 0;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get total number of accounts
|
|
218
|
+
* @returns Account count
|
|
219
|
+
*/
|
|
220
|
+
static async getAccountCount() {
|
|
221
|
+
const db = await this.getDB();
|
|
222
|
+
return await db.count(STORE_NAME);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Clear all accounts (use with caution!)
|
|
226
|
+
* Used primarily for testing or wallet reset
|
|
227
|
+
*/
|
|
228
|
+
static async clearAccounts() {
|
|
229
|
+
const db = await this.getDB();
|
|
230
|
+
await db.clear(STORE_NAME);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
AccountStorage.dbPromise = null;
|
|
234
|
+
var STORE_NAME2 = "connectedApps" /* CONNECTED_APPS */;
|
|
235
|
+
function createKey(accountId, appId) {
|
|
236
|
+
return `${accountId}:${appId}`;
|
|
237
|
+
}
|
|
238
|
+
function toDomain(record) {
|
|
239
|
+
return {
|
|
240
|
+
accountId: record.accountId,
|
|
241
|
+
appId: record.appId,
|
|
242
|
+
origin: record.origin,
|
|
243
|
+
metadata: record.metadata,
|
|
244
|
+
connectedAt: record.connectedAt,
|
|
245
|
+
updatedAt: record.updatedAt
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
var ConnectedAppsStorage = class {
|
|
249
|
+
static async getDB() {
|
|
250
|
+
if (!this.dbPromise) {
|
|
251
|
+
this.dbPromise = openDB(DB_NAME, DB_VERSION, {
|
|
252
|
+
upgrade(db) {
|
|
253
|
+
initializeSchema(db);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return this.dbPromise;
|
|
258
|
+
}
|
|
259
|
+
static async upsert(app) {
|
|
260
|
+
const db = await this.getDB();
|
|
261
|
+
const key = createKey(app.accountId, app.appId);
|
|
262
|
+
const existing = await db.get(STORE_NAME2, key);
|
|
263
|
+
const now = Date.now();
|
|
264
|
+
const record = {
|
|
265
|
+
key,
|
|
266
|
+
accountId: app.accountId,
|
|
267
|
+
appId: app.appId,
|
|
268
|
+
origin: app.origin,
|
|
269
|
+
metadata: app.metadata,
|
|
270
|
+
connectedAt: existing?.connectedAt ?? now,
|
|
271
|
+
updatedAt: now
|
|
272
|
+
};
|
|
273
|
+
await db.put(STORE_NAME2, record);
|
|
274
|
+
return toDomain(record);
|
|
275
|
+
}
|
|
276
|
+
static async listByAccount(accountId) {
|
|
277
|
+
const db = await this.getDB();
|
|
278
|
+
const records = await db.getAllFromIndex(STORE_NAME2, "by-account", IDBKeyRange.only(accountId));
|
|
279
|
+
return records.sort((a, b) => b.updatedAt - a.updatedAt).map(toDomain);
|
|
280
|
+
}
|
|
281
|
+
static async remove(accountId, appId) {
|
|
282
|
+
const db = await this.getDB();
|
|
283
|
+
await db.delete(STORE_NAME2, createKey(accountId, appId));
|
|
284
|
+
}
|
|
285
|
+
static async clear() {
|
|
286
|
+
const db = await this.getDB();
|
|
287
|
+
await db.clear(STORE_NAME2);
|
|
288
|
+
}
|
|
289
|
+
static async get(accountId, appId) {
|
|
290
|
+
const db = await this.getDB();
|
|
291
|
+
const record = await db.get(STORE_NAME2, createKey(accountId, appId));
|
|
292
|
+
return record ? toDomain(record) : null;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
ConnectedAppsStorage.dbPromise = null;
|
|
296
|
+
|
|
297
|
+
export { AccountStorage, ConnectedAppsStorage, DB_NAME, DB_VERSION, StoreName, WalletStorage };
|
|
298
|
+
//# sourceMappingURL=index.js.map
|
|
299
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts","../src/wallet-store.ts","../src/account-store.ts","../src/connected-apps-store.ts"],"names":["StoreName","DB_NAME","DB_VERSION","openDB","STORE_NAME"],"mappings":"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","file":"index.js","sourcesContent":["import type { AppMetadata } from '@thru/chain-interfaces';\n\n/**\n * IndexedDB schema for wallet storage\n */\n\nexport const DB_NAME = 'thru-wallet';\nexport const DB_VERSION = 2;\n\n/**\n * Object store names\n */\nexport enum StoreName {\n WALLET = 'wallet',\n SETTINGS = 'settings',\n CONNECTED_APPS = 'connectedApps',\n}\n\n/**\n * Wallet data stored in IndexedDB\n */\nexport interface WalletData {\n id: string;\n encryptedSeed: string; // Serialized EncryptedData\n createdAt: number; // Timestamp\n updatedAt: number; // Timestamp\n}\n\n/**\n * Settings data\n */\nexport interface SettingsData {\n key: string;\n value: any;\n}\n\nexport interface ConnectedAppData {\n key: string; // `${accountId}:${appId}`\n accountId: number;\n appId: string;\n origin: string;\n metadata: AppMetadata;\n connectedAt: number;\n updatedAt: number;\n}\n\n/**\n * Initialize database schema\n */\nexport function initializeSchema(db: IDBDatabase): void {\n // Wallet store\n if (!db.objectStoreNames.contains(StoreName.WALLET)) {\n const walletStore = db.createObjectStore(StoreName.WALLET, { keyPath: 'id' });\n walletStore.createIndex('createdAt', 'createdAt', { unique: false });\n }\n\n // Settings store\n if (!db.objectStoreNames.contains(StoreName.SETTINGS)) {\n db.createObjectStore(StoreName.SETTINGS, { keyPath: 'key' });\n }\n\n // Connected apps store\n if (!db.objectStoreNames.contains(StoreName.CONNECTED_APPS)) {\n const store = db.createObjectStore(StoreName.CONNECTED_APPS, { keyPath: 'key' });\n store.createIndex('by-account', 'accountId', { unique: false });\n store.createIndex('by-updated', 'updatedAt', { unique: false });\n }\n}\n","import { openDB, IDBPDatabase } from 'idb';\nimport { DB_NAME, DB_VERSION, StoreName, WalletData, initializeSchema } from './schema';\nimport { EncryptedData, EncryptionService } from '@thru/crypto';\n\n/**\n * IndexedDB storage for wallet data\n */\nexport class WalletStorage {\n private static dbPromise: Promise<IDBPDatabase> | null = null;\n\n /**\n * Get or create database connection\n */\n private static async getDB(): Promise<IDBPDatabase> {\n if (!this.dbPromise) {\n this.dbPromise = openDB(DB_NAME, DB_VERSION, {\n upgrade(db) {\n initializeSchema(db as any);\n },\n });\n }\n return this.dbPromise;\n }\n\n /**\n * Save encrypted seed to storage\n * @param encryptedSeed - Encrypted seed data\n * @param walletId - Optional wallet ID (defaults to 'default')\n */\n static async saveEncryptedSeed(\n encryptedSeed: EncryptedData,\n walletId: string = 'default'\n ): Promise<void> {\n const db = await this.getDB();\n\n // Serialize encrypted data\n const serialized = EncryptionService.serialize(encryptedSeed);\n\n const now = Date.now();\n const walletData: WalletData = {\n id: walletId,\n encryptedSeed: serialized,\n createdAt: now,\n updatedAt: now,\n };\n\n await db.put(StoreName.WALLET, walletData);\n }\n\n /**\n * Get encrypted seed from storage\n * @param walletId - Wallet ID (defaults to 'default')\n * @returns Encrypted seed data or null if not found\n */\n static async getEncryptedSeed(walletId: string = 'default'): Promise<EncryptedData | null> {\n const db = await this.getDB();\n const walletData = await db.get(StoreName.WALLET, walletId);\n\n if (!walletData) {\n return null;\n }\n\n // Deserialize encrypted data\n return EncryptionService.deserialize(walletData.encryptedSeed);\n }\n\n /**\n * Check if wallet exists\n * @param walletId - Wallet ID (defaults to 'default')\n */\n static async walletExists(walletId: string = 'default'): Promise<boolean> {\n const db = await this.getDB();\n const walletData = await db.get(StoreName.WALLET, walletId);\n return walletData !== undefined;\n }\n\n /**\n * Delete wallet from storage\n * @param walletId - Wallet ID (defaults to 'default')\n */\n static async deleteWallet(walletId: string = 'default'): Promise<void> {\n const db = await this.getDB();\n await db.delete(StoreName.WALLET, walletId);\n }\n\n /**\n * Clear all wallet data (use with caution!)\n */\n static async clearAll(): Promise<void> {\n const db = await this.getDB();\n await db.clear(StoreName.WALLET);\n await db.clear(StoreName.SETTINGS);\n }\n\n /**\n * Get storage quota information\n */\n static async getStorageInfo(): Promise<{\n usage: number;\n quota: number;\n available: number;\n } | null> {\n if (!navigator.storage || !navigator.storage.estimate) {\n return null;\n }\n\n const estimate = await navigator.storage.estimate();\n const usage = estimate.usage || 0;\n const quota = estimate.quota || 0;\n\n return {\n usage,\n quota,\n available: quota - usage,\n };\n }\n\n /**\n * Request persistent storage (prevents data from being cleared)\n */\n static async requestPersistentStorage(): Promise<boolean> {\n if (!navigator.storage || !navigator.storage.persist) {\n return false;\n }\n\n return await navigator.storage.persist();\n }\n\n /**\n * Check if storage is persistent\n */\n static async isPersistent(): Promise<boolean> {\n if (!navigator.storage || !navigator.storage.persisted) {\n return false;\n }\n\n return await navigator.storage.persisted();\n }\n}\n","import { openDB, DBSchema, IDBPDatabase } from 'idb';\nimport { StoredAccount } from './types';\n\n/**\n * IndexedDB schema for account storage\n */\ninterface AccountDB extends DBSchema {\n accounts: {\n key: number; // account index\n value: StoredAccount;\n indexes: {\n 'by-created': Date;\n };\n };\n}\n\nconst DB_NAME = 'thru-wallet-accounts';\nconst DB_VERSION = 1;\nconst STORE_NAME = 'accounts';\n\n/**\n * Account storage management using IndexedDB\n * Stores metadata for each derived account (but NOT private keys)\n */\nexport class AccountStorage {\n private static dbPromise: Promise<IDBPDatabase<AccountDB>> | null = null;\n\n /**\n * Initialize or get existing database connection\n */\n private static async getDB(): Promise<IDBPDatabase<AccountDB>> {\n if (!this.dbPromise) {\n this.dbPromise = openDB<AccountDB>(DB_NAME, DB_VERSION, {\n upgrade(db) {\n // Create accounts object store\n const store = db.createObjectStore(STORE_NAME, {\n keyPath: 'index',\n });\n\n // Create index for sorting by creation date\n store.createIndex('by-created', 'createdAt');\n },\n });\n }\n return this.dbPromise;\n }\n\n /**\n * Save a new account to storage\n * @param account - Account metadata to save\n */\n static async saveAccount(account: StoredAccount): Promise<void> {\n const db = await this.getDB();\n await db.put(STORE_NAME, account);\n }\n\n /**\n * Get all accounts, sorted by index (ascending)\n * @returns Array of stored accounts\n */\n static async getAccounts(): Promise<StoredAccount[]> {\n const db = await this.getDB();\n const accounts = await db.getAll(STORE_NAME);\n\n // Sort by index (0, 1, 2, ...)\n return accounts.sort((a, b) => a.index - b.index);\n }\n\n /**\n * Get a specific account by index\n * @param index - Account index\n * @returns Account or null if not found\n */\n static async getAccount(index: number): Promise<StoredAccount | null> {\n const db = await this.getDB();\n const account = await db.get(STORE_NAME, index);\n return account || null;\n }\n\n /**\n * Update an account's label\n * @param index - Account index\n * @param label - New label\n */\n static async updateAccountLabel(index: number, label: string): Promise<void> {\n const db = await this.getDB();\n const account = await db.get(STORE_NAME, index);\n\n if (!account) {\n throw new Error(`Account ${index} not found`);\n }\n\n account.label = label;\n await db.put(STORE_NAME, account);\n }\n\n /**\n * Get the next available account index\n * @returns Next index (0 if no accounts exist)\n */\n static async getNextAccountIndex(): Promise<number> {\n const accounts = await this.getAccounts();\n\n if (accounts.length === 0) {\n return 0;\n }\n\n // Return highest index + 1\n const maxIndex = Math.max(...accounts.map(a => a.index));\n return maxIndex + 1;\n }\n\n /**\n * Check if any accounts exist\n * @returns true if at least one account exists\n */\n static async hasAccounts(): Promise<boolean> {\n const db = await this.getDB();\n const count = await db.count(STORE_NAME);\n return count > 0;\n }\n\n /**\n * Get total number of accounts\n * @returns Account count\n */\n static async getAccountCount(): Promise<number> {\n const db = await this.getDB();\n return await db.count(STORE_NAME);\n }\n\n /**\n * Clear all accounts (use with caution!)\n * Used primarily for testing or wallet reset\n */\n static async clearAccounts(): Promise<void> {\n const db = await this.getDB();\n await db.clear(STORE_NAME);\n }\n}\n","import { openDB, IDBPDatabase } from 'idb';\nimport {\n DB_NAME,\n DB_VERSION,\n StoreName,\n initializeSchema,\n type ConnectedAppData,\n} from './schema';\nimport type { ConnectedApp, AppMetadata } from '@thru/chain-interfaces';\n\nconst STORE_NAME = StoreName.CONNECTED_APPS;\n\nfunction createKey(accountId: number, appId: string): string {\n return `${accountId}:${appId}`;\n}\n\nfunction toDomain(record: ConnectedAppData): ConnectedApp {\n return {\n accountId: record.accountId,\n appId: record.appId,\n origin: record.origin,\n metadata: record.metadata,\n connectedAt: record.connectedAt,\n updatedAt: record.updatedAt,\n };\n}\n\nexport interface ConnectedAppUpsert {\n accountId: number;\n appId: string;\n origin: string;\n metadata: AppMetadata;\n}\n\n/**\n * Storage helper for connected dApps per account\n */\nexport class ConnectedAppsStorage {\n private static dbPromise: Promise<IDBPDatabase> | null = null;\n\n private static async getDB(): Promise<IDBPDatabase> {\n if (!this.dbPromise) {\n this.dbPromise = openDB(DB_NAME, DB_VERSION, {\n upgrade(db) {\n initializeSchema(db as any);\n },\n });\n }\n return this.dbPromise;\n }\n\n static async upsert(app: ConnectedAppUpsert): Promise<ConnectedApp> {\n const db = await this.getDB();\n const key = createKey(app.accountId, app.appId);\n const existing = (await db.get(STORE_NAME, key)) as ConnectedAppData | undefined;\n const now = Date.now();\n\n const record: ConnectedAppData = {\n key,\n accountId: app.accountId,\n appId: app.appId,\n origin: app.origin,\n metadata: app.metadata,\n connectedAt: existing?.connectedAt ?? now,\n updatedAt: now,\n };\n\n await db.put(STORE_NAME, record);\n return toDomain(record);\n }\n\n static async listByAccount(accountId: number): Promise<ConnectedApp[]> {\n const db = await this.getDB();\n const records = (await db.getAllFromIndex(STORE_NAME, 'by-account', IDBKeyRange.only(accountId))) as ConnectedAppData[];\n return records\n .sort((a, b) => b.updatedAt - a.updatedAt)\n .map(toDomain);\n }\n\n static async remove(accountId: number, appId: string): Promise<void> {\n const db = await this.getDB();\n await db.delete(STORE_NAME, createKey(accountId, appId));\n }\n\n static async clear(): Promise<void> {\n const db = await this.getDB();\n await db.clear(STORE_NAME);\n }\n\n static async get(accountId: number, appId: string): Promise<ConnectedApp | null> {\n const db = await this.getDB();\n const record = (await db.get(STORE_NAME, createKey(accountId, appId))) as ConnectedAppData | undefined;\n return record ? toDomain(record) : null;\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thru/indexed-db-stamper",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"idb": "^8.0.3",
|
|
15
|
+
"@thru/chain-interfaces": "0.0.4",
|
|
16
|
+
"@thru/crypto": "0.0.4"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"lint": "eslint src/",
|
|
22
|
+
"clean": "rm -rf dist"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { openDB, DBSchema, IDBPDatabase } from 'idb';
|
|
2
|
+
import { StoredAccount } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* IndexedDB schema for account storage
|
|
6
|
+
*/
|
|
7
|
+
interface AccountDB extends DBSchema {
|
|
8
|
+
accounts: {
|
|
9
|
+
key: number; // account index
|
|
10
|
+
value: StoredAccount;
|
|
11
|
+
indexes: {
|
|
12
|
+
'by-created': Date;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const DB_NAME = 'thru-wallet-accounts';
|
|
18
|
+
const DB_VERSION = 1;
|
|
19
|
+
const STORE_NAME = 'accounts';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Account storage management using IndexedDB
|
|
23
|
+
* Stores metadata for each derived account (but NOT private keys)
|
|
24
|
+
*/
|
|
25
|
+
export class AccountStorage {
|
|
26
|
+
private static dbPromise: Promise<IDBPDatabase<AccountDB>> | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize or get existing database connection
|
|
30
|
+
*/
|
|
31
|
+
private static async getDB(): Promise<IDBPDatabase<AccountDB>> {
|
|
32
|
+
if (!this.dbPromise) {
|
|
33
|
+
this.dbPromise = openDB<AccountDB>(DB_NAME, DB_VERSION, {
|
|
34
|
+
upgrade(db) {
|
|
35
|
+
// Create accounts object store
|
|
36
|
+
const store = db.createObjectStore(STORE_NAME, {
|
|
37
|
+
keyPath: 'index',
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Create index for sorting by creation date
|
|
41
|
+
store.createIndex('by-created', 'createdAt');
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return this.dbPromise;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Save a new account to storage
|
|
50
|
+
* @param account - Account metadata to save
|
|
51
|
+
*/
|
|
52
|
+
static async saveAccount(account: StoredAccount): Promise<void> {
|
|
53
|
+
const db = await this.getDB();
|
|
54
|
+
await db.put(STORE_NAME, account);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get all accounts, sorted by index (ascending)
|
|
59
|
+
* @returns Array of stored accounts
|
|
60
|
+
*/
|
|
61
|
+
static async getAccounts(): Promise<StoredAccount[]> {
|
|
62
|
+
const db = await this.getDB();
|
|
63
|
+
const accounts = await db.getAll(STORE_NAME);
|
|
64
|
+
|
|
65
|
+
// Sort by index (0, 1, 2, ...)
|
|
66
|
+
return accounts.sort((a, b) => a.index - b.index);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get a specific account by index
|
|
71
|
+
* @param index - Account index
|
|
72
|
+
* @returns Account or null if not found
|
|
73
|
+
*/
|
|
74
|
+
static async getAccount(index: number): Promise<StoredAccount | null> {
|
|
75
|
+
const db = await this.getDB();
|
|
76
|
+
const account = await db.get(STORE_NAME, index);
|
|
77
|
+
return account || null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Update an account's label
|
|
82
|
+
* @param index - Account index
|
|
83
|
+
* @param label - New label
|
|
84
|
+
*/
|
|
85
|
+
static async updateAccountLabel(index: number, label: string): Promise<void> {
|
|
86
|
+
const db = await this.getDB();
|
|
87
|
+
const account = await db.get(STORE_NAME, index);
|
|
88
|
+
|
|
89
|
+
if (!account) {
|
|
90
|
+
throw new Error(`Account ${index} not found`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
account.label = label;
|
|
94
|
+
await db.put(STORE_NAME, account);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the next available account index
|
|
99
|
+
* @returns Next index (0 if no accounts exist)
|
|
100
|
+
*/
|
|
101
|
+
static async getNextAccountIndex(): Promise<number> {
|
|
102
|
+
const accounts = await this.getAccounts();
|
|
103
|
+
|
|
104
|
+
if (accounts.length === 0) {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Return highest index + 1
|
|
109
|
+
const maxIndex = Math.max(...accounts.map(a => a.index));
|
|
110
|
+
return maxIndex + 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if any accounts exist
|
|
115
|
+
* @returns true if at least one account exists
|
|
116
|
+
*/
|
|
117
|
+
static async hasAccounts(): Promise<boolean> {
|
|
118
|
+
const db = await this.getDB();
|
|
119
|
+
const count = await db.count(STORE_NAME);
|
|
120
|
+
return count > 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get total number of accounts
|
|
125
|
+
* @returns Account count
|
|
126
|
+
*/
|
|
127
|
+
static async getAccountCount(): Promise<number> {
|
|
128
|
+
const db = await this.getDB();
|
|
129
|
+
return await db.count(STORE_NAME);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Clear all accounts (use with caution!)
|
|
134
|
+
* Used primarily for testing or wallet reset
|
|
135
|
+
*/
|
|
136
|
+
static async clearAccounts(): Promise<void> {
|
|
137
|
+
const db = await this.getDB();
|
|
138
|
+
await db.clear(STORE_NAME);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { openDB, IDBPDatabase } from 'idb';
|
|
2
|
+
import {
|
|
3
|
+
DB_NAME,
|
|
4
|
+
DB_VERSION,
|
|
5
|
+
StoreName,
|
|
6
|
+
initializeSchema,
|
|
7
|
+
type ConnectedAppData,
|
|
8
|
+
} from './schema';
|
|
9
|
+
import type { ConnectedApp, AppMetadata } from '@thru/chain-interfaces';
|
|
10
|
+
|
|
11
|
+
const STORE_NAME = StoreName.CONNECTED_APPS;
|
|
12
|
+
|
|
13
|
+
function createKey(accountId: number, appId: string): string {
|
|
14
|
+
return `${accountId}:${appId}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function toDomain(record: ConnectedAppData): ConnectedApp {
|
|
18
|
+
return {
|
|
19
|
+
accountId: record.accountId,
|
|
20
|
+
appId: record.appId,
|
|
21
|
+
origin: record.origin,
|
|
22
|
+
metadata: record.metadata,
|
|
23
|
+
connectedAt: record.connectedAt,
|
|
24
|
+
updatedAt: record.updatedAt,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ConnectedAppUpsert {
|
|
29
|
+
accountId: number;
|
|
30
|
+
appId: string;
|
|
31
|
+
origin: string;
|
|
32
|
+
metadata: AppMetadata;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Storage helper for connected dApps per account
|
|
37
|
+
*/
|
|
38
|
+
export class ConnectedAppsStorage {
|
|
39
|
+
private static dbPromise: Promise<IDBPDatabase> | null = null;
|
|
40
|
+
|
|
41
|
+
private static async getDB(): Promise<IDBPDatabase> {
|
|
42
|
+
if (!this.dbPromise) {
|
|
43
|
+
this.dbPromise = openDB(DB_NAME, DB_VERSION, {
|
|
44
|
+
upgrade(db) {
|
|
45
|
+
initializeSchema(db as any);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return this.dbPromise;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static async upsert(app: ConnectedAppUpsert): Promise<ConnectedApp> {
|
|
53
|
+
const db = await this.getDB();
|
|
54
|
+
const key = createKey(app.accountId, app.appId);
|
|
55
|
+
const existing = (await db.get(STORE_NAME, key)) as ConnectedAppData | undefined;
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
|
|
58
|
+
const record: ConnectedAppData = {
|
|
59
|
+
key,
|
|
60
|
+
accountId: app.accountId,
|
|
61
|
+
appId: app.appId,
|
|
62
|
+
origin: app.origin,
|
|
63
|
+
metadata: app.metadata,
|
|
64
|
+
connectedAt: existing?.connectedAt ?? now,
|
|
65
|
+
updatedAt: now,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
await db.put(STORE_NAME, record);
|
|
69
|
+
return toDomain(record);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static async listByAccount(accountId: number): Promise<ConnectedApp[]> {
|
|
73
|
+
const db = await this.getDB();
|
|
74
|
+
const records = (await db.getAllFromIndex(STORE_NAME, 'by-account', IDBKeyRange.only(accountId))) as ConnectedAppData[];
|
|
75
|
+
return records
|
|
76
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
77
|
+
.map(toDomain);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static async remove(accountId: number, appId: string): Promise<void> {
|
|
81
|
+
const db = await this.getDB();
|
|
82
|
+
await db.delete(STORE_NAME, createKey(accountId, appId));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static async clear(): Promise<void> {
|
|
86
|
+
const db = await this.getDB();
|
|
87
|
+
await db.clear(STORE_NAME);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static async get(accountId: number, appId: string): Promise<ConnectedApp | null> {
|
|
91
|
+
const db = await this.getDB();
|
|
92
|
+
const record = (await db.get(STORE_NAME, createKey(accountId, appId))) as ConnectedAppData | undefined;
|
|
93
|
+
return record ? toDomain(record) : null;
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { WalletStorage } from './wallet-store';
|
|
2
|
+
export { AccountStorage } from './account-store';
|
|
3
|
+
export { ConnectedAppsStorage } from './connected-apps-store';
|
|
4
|
+
export {
|
|
5
|
+
type WalletData,
|
|
6
|
+
type SettingsData,
|
|
7
|
+
type ConnectedAppData,
|
|
8
|
+
StoreName,
|
|
9
|
+
DB_NAME,
|
|
10
|
+
DB_VERSION,
|
|
11
|
+
} from './schema';
|
|
12
|
+
export { type StoredAccount } from './types';
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { AppMetadata } from '@thru/chain-interfaces';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IndexedDB schema for wallet storage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const DB_NAME = 'thru-wallet';
|
|
8
|
+
export const DB_VERSION = 2;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Object store names
|
|
12
|
+
*/
|
|
13
|
+
export enum StoreName {
|
|
14
|
+
WALLET = 'wallet',
|
|
15
|
+
SETTINGS = 'settings',
|
|
16
|
+
CONNECTED_APPS = 'connectedApps',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Wallet data stored in IndexedDB
|
|
21
|
+
*/
|
|
22
|
+
export interface WalletData {
|
|
23
|
+
id: string;
|
|
24
|
+
encryptedSeed: string; // Serialized EncryptedData
|
|
25
|
+
createdAt: number; // Timestamp
|
|
26
|
+
updatedAt: number; // Timestamp
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Settings data
|
|
31
|
+
*/
|
|
32
|
+
export interface SettingsData {
|
|
33
|
+
key: string;
|
|
34
|
+
value: any;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ConnectedAppData {
|
|
38
|
+
key: string; // `${accountId}:${appId}`
|
|
39
|
+
accountId: number;
|
|
40
|
+
appId: string;
|
|
41
|
+
origin: string;
|
|
42
|
+
metadata: AppMetadata;
|
|
43
|
+
connectedAt: number;
|
|
44
|
+
updatedAt: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Initialize database schema
|
|
49
|
+
*/
|
|
50
|
+
export function initializeSchema(db: IDBDatabase): void {
|
|
51
|
+
// Wallet store
|
|
52
|
+
if (!db.objectStoreNames.contains(StoreName.WALLET)) {
|
|
53
|
+
const walletStore = db.createObjectStore(StoreName.WALLET, { keyPath: 'id' });
|
|
54
|
+
walletStore.createIndex('createdAt', 'createdAt', { unique: false });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Settings store
|
|
58
|
+
if (!db.objectStoreNames.contains(StoreName.SETTINGS)) {
|
|
59
|
+
db.createObjectStore(StoreName.SETTINGS, { keyPath: 'key' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Connected apps store
|
|
63
|
+
if (!db.objectStoreNames.contains(StoreName.CONNECTED_APPS)) {
|
|
64
|
+
const store = db.createObjectStore(StoreName.CONNECTED_APPS, { keyPath: 'key' });
|
|
65
|
+
store.createIndex('by-account', 'accountId', { unique: false });
|
|
66
|
+
store.createIndex('by-updated', 'updatedAt', { unique: false });
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AddressType } from '@thru/chain-interfaces';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stored account representation in IndexedDB
|
|
5
|
+
*/
|
|
6
|
+
export interface StoredAccount {
|
|
7
|
+
index: number; // BIP44 account index (0, 1, 2, ...)
|
|
8
|
+
label: string; // User-defined name (e.g., "Trading", "NFTs")
|
|
9
|
+
publicKey: string; // Encoded address string (currently base58 SOL; will migrate to Thru)
|
|
10
|
+
path: string; // Full derivation path (m/44'/<coin>'/index'/0')
|
|
11
|
+
createdAt: Date; // Timestamp of account creation
|
|
12
|
+
addressType?: AddressType; // Chain identifier (e.g., 'thru')
|
|
13
|
+
publicKeyRawBase64?: string; // Optional raw 32-byte public key as base64 (for Thru migration)
|
|
14
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { openDB, IDBPDatabase } from 'idb';
|
|
2
|
+
import { DB_NAME, DB_VERSION, StoreName, WalletData, initializeSchema } from './schema';
|
|
3
|
+
import { EncryptedData, EncryptionService } from '@thru/crypto';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* IndexedDB storage for wallet data
|
|
7
|
+
*/
|
|
8
|
+
export class WalletStorage {
|
|
9
|
+
private static dbPromise: Promise<IDBPDatabase> | null = null;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get or create database connection
|
|
13
|
+
*/
|
|
14
|
+
private static async getDB(): Promise<IDBPDatabase> {
|
|
15
|
+
if (!this.dbPromise) {
|
|
16
|
+
this.dbPromise = openDB(DB_NAME, DB_VERSION, {
|
|
17
|
+
upgrade(db) {
|
|
18
|
+
initializeSchema(db as any);
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return this.dbPromise;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Save encrypted seed to storage
|
|
27
|
+
* @param encryptedSeed - Encrypted seed data
|
|
28
|
+
* @param walletId - Optional wallet ID (defaults to 'default')
|
|
29
|
+
*/
|
|
30
|
+
static async saveEncryptedSeed(
|
|
31
|
+
encryptedSeed: EncryptedData,
|
|
32
|
+
walletId: string = 'default'
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
const db = await this.getDB();
|
|
35
|
+
|
|
36
|
+
// Serialize encrypted data
|
|
37
|
+
const serialized = EncryptionService.serialize(encryptedSeed);
|
|
38
|
+
|
|
39
|
+
const now = Date.now();
|
|
40
|
+
const walletData: WalletData = {
|
|
41
|
+
id: walletId,
|
|
42
|
+
encryptedSeed: serialized,
|
|
43
|
+
createdAt: now,
|
|
44
|
+
updatedAt: now,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await db.put(StoreName.WALLET, walletData);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get encrypted seed from storage
|
|
52
|
+
* @param walletId - Wallet ID (defaults to 'default')
|
|
53
|
+
* @returns Encrypted seed data or null if not found
|
|
54
|
+
*/
|
|
55
|
+
static async getEncryptedSeed(walletId: string = 'default'): Promise<EncryptedData | null> {
|
|
56
|
+
const db = await this.getDB();
|
|
57
|
+
const walletData = await db.get(StoreName.WALLET, walletId);
|
|
58
|
+
|
|
59
|
+
if (!walletData) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Deserialize encrypted data
|
|
64
|
+
return EncryptionService.deserialize(walletData.encryptedSeed);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if wallet exists
|
|
69
|
+
* @param walletId - Wallet ID (defaults to 'default')
|
|
70
|
+
*/
|
|
71
|
+
static async walletExists(walletId: string = 'default'): Promise<boolean> {
|
|
72
|
+
const db = await this.getDB();
|
|
73
|
+
const walletData = await db.get(StoreName.WALLET, walletId);
|
|
74
|
+
return walletData !== undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Delete wallet from storage
|
|
79
|
+
* @param walletId - Wallet ID (defaults to 'default')
|
|
80
|
+
*/
|
|
81
|
+
static async deleteWallet(walletId: string = 'default'): Promise<void> {
|
|
82
|
+
const db = await this.getDB();
|
|
83
|
+
await db.delete(StoreName.WALLET, walletId);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Clear all wallet data (use with caution!)
|
|
88
|
+
*/
|
|
89
|
+
static async clearAll(): Promise<void> {
|
|
90
|
+
const db = await this.getDB();
|
|
91
|
+
await db.clear(StoreName.WALLET);
|
|
92
|
+
await db.clear(StoreName.SETTINGS);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get storage quota information
|
|
97
|
+
*/
|
|
98
|
+
static async getStorageInfo(): Promise<{
|
|
99
|
+
usage: number;
|
|
100
|
+
quota: number;
|
|
101
|
+
available: number;
|
|
102
|
+
} | null> {
|
|
103
|
+
if (!navigator.storage || !navigator.storage.estimate) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const estimate = await navigator.storage.estimate();
|
|
108
|
+
const usage = estimate.usage || 0;
|
|
109
|
+
const quota = estimate.quota || 0;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
usage,
|
|
113
|
+
quota,
|
|
114
|
+
available: quota - usage,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Request persistent storage (prevents data from being cleared)
|
|
120
|
+
*/
|
|
121
|
+
static async requestPersistentStorage(): Promise<boolean> {
|
|
122
|
+
if (!navigator.storage || !navigator.storage.persist) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return await navigator.storage.persist();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if storage is persistent
|
|
131
|
+
*/
|
|
132
|
+
static async isPersistent(): Promise<boolean> {
|
|
133
|
+
if (!navigator.storage || !navigator.storage.persisted) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return await navigator.storage.persisted();
|
|
138
|
+
}
|
|
139
|
+
}
|
package/tsconfig.json
ADDED