shogun-core 3.3.1 → 3.3.2
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/ship/examples/ephemeral-cli.js +234 -0
- package/dist/ship/examples/identity-cli.js +503 -0
- package/dist/ship/examples/messenger-cli.js +745 -0
- package/dist/ship/examples/stealth-cli.js +433 -0
- package/dist/ship/examples/storage-cli.js +615 -0
- package/dist/ship/examples/vault-cli.js +444 -0
- package/dist/ship/examples/wallet-cli.js +767 -0
- package/dist/ship/implementation/SHIP_00.js +478 -0
- package/dist/ship/implementation/SHIP_01.js +433 -0
- package/dist/ship/implementation/SHIP_02.js +1366 -0
- package/dist/ship/implementation/SHIP_03.js +855 -0
- package/dist/ship/implementation/SHIP_04.js +589 -0
- package/dist/ship/implementation/SHIP_05.js +1064 -0
- package/dist/ship/implementation/SHIP_06.js +350 -0
- package/dist/ship/implementation/SHIP_07.js +635 -0
- package/dist/ship/index.js +17 -0
- package/dist/ship/interfaces/ISHIP_00.js +135 -0
- package/dist/ship/interfaces/ISHIP_01.js +128 -0
- package/dist/ship/interfaces/ISHIP_02.js +57 -0
- package/dist/ship/interfaces/ISHIP_03.js +61 -0
- package/dist/ship/interfaces/ISHIP_04.js +62 -0
- package/dist/ship/interfaces/ISHIP_05.js +59 -0
- package/dist/ship/interfaces/ISHIP_06.js +144 -0
- package/dist/ship/interfaces/ISHIP_07.js +194 -0
- package/dist/types/ship/examples/ephemeral-cli.d.ts +13 -0
- package/dist/types/ship/examples/identity-cli.d.ts +40 -0
- package/dist/types/ship/examples/messenger-cli.d.ts +37 -0
- package/dist/types/ship/examples/stealth-cli.d.ts +31 -0
- package/dist/types/ship/examples/storage-cli.d.ts +48 -0
- package/dist/types/ship/examples/vault-cli.d.ts +13 -0
- package/dist/types/ship/examples/wallet-cli.d.ts +131 -0
- package/dist/types/ship/implementation/SHIP_00.d.ts +113 -0
- package/dist/types/ship/implementation/SHIP_01.d.ts +80 -0
- package/dist/types/ship/implementation/SHIP_02.d.ts +297 -0
- package/dist/types/ship/implementation/SHIP_03.d.ts +127 -0
- package/dist/types/ship/implementation/SHIP_04.d.ts +76 -0
- package/dist/types/ship/implementation/SHIP_05.d.ts +70 -0
- package/dist/types/ship/implementation/SHIP_06.d.ts +66 -0
- package/dist/types/ship/implementation/SHIP_07.d.ts +101 -0
- package/dist/types/ship/index.d.ts +14 -0
- package/dist/types/ship/interfaces/ISHIP_00.d.ts +410 -0
- package/dist/types/ship/interfaces/ISHIP_01.d.ts +343 -0
- package/dist/types/ship/interfaces/ISHIP_02.d.ts +470 -0
- package/dist/types/ship/interfaces/ISHIP_03.d.ts +295 -0
- package/dist/types/ship/interfaces/ISHIP_04.d.ts +245 -0
- package/dist/types/ship/interfaces/ISHIP_05.d.ts +234 -0
- package/dist/types/ship/interfaces/ISHIP_06.d.ts +370 -0
- package/dist/types/ship/interfaces/ISHIP_07.d.ts +522 -0
- package/package.json +1 -1
- /package/dist/{config → src/config}/simplified-config.js +0 -0
- /package/dist/{core.js → src/core.js} +0 -0
- /package/dist/{examples → src/examples}/api-test.js +0 -0
- /package/dist/{examples → src/examples}/simple-api-test.js +0 -0
- /package/dist/{gundb → src/gundb}/api.js +0 -0
- /package/dist/{gundb → src/gundb}/crypto.js +0 -0
- /package/dist/{gundb → src/gundb}/db.js +0 -0
- /package/dist/{gundb → src/gundb}/derive.js +0 -0
- /package/dist/{gundb → src/gundb}/errors.js +0 -0
- /package/dist/{gundb → src/gundb}/index.js +0 -0
- /package/dist/{gundb → src/gundb}/rxjs.js +0 -0
- /package/dist/{gundb → src/gundb}/types.js +0 -0
- /package/dist/{index.js → src/index.js} +0 -0
- /package/dist/{interfaces → src/interfaces}/common.js +0 -0
- /package/dist/{interfaces → src/interfaces}/events.js +0 -0
- /package/dist/{interfaces → src/interfaces}/plugin.js +0 -0
- /package/dist/{interfaces → src/interfaces}/shogun.js +0 -0
- /package/dist/{managers → src/managers}/AuthManager.js +0 -0
- /package/dist/{managers → src/managers}/CoreInitializer.js +0 -0
- /package/dist/{managers → src/managers}/EventManager.js +0 -0
- /package/dist/{managers → src/managers}/PluginManager.js +0 -0
- /package/dist/{migration-test.js → src/migration-test.js} +0 -0
- /package/dist/{plugins → src/plugins}/base.js +0 -0
- /package/dist/{plugins → src/plugins}/index.js +0 -0
- /package/dist/{plugins → src/plugins}/nostr/index.js +0 -0
- /package/dist/{plugins → src/plugins}/nostr/nostrConnector.js +0 -0
- /package/dist/{plugins → src/plugins}/nostr/nostrConnectorPlugin.js +0 -0
- /package/dist/{plugins → src/plugins}/nostr/nostrSigner.js +0 -0
- /package/dist/{plugins → src/plugins}/nostr/types.js +0 -0
- /package/dist/{plugins → src/plugins}/oauth/index.js +0 -0
- /package/dist/{plugins → src/plugins}/oauth/oauthConnector.js +0 -0
- /package/dist/{plugins → src/plugins}/oauth/oauthPlugin.js +0 -0
- /package/dist/{plugins → src/plugins}/oauth/types.js +0 -0
- /package/dist/{plugins → src/plugins}/web3/index.js +0 -0
- /package/dist/{plugins → src/plugins}/web3/types.js +0 -0
- /package/dist/{plugins → src/plugins}/web3/web3Connector.js +0 -0
- /package/dist/{plugins → src/plugins}/web3/web3ConnectorPlugin.js +0 -0
- /package/dist/{plugins → src/plugins}/web3/web3Signer.js +0 -0
- /package/dist/{plugins → src/plugins}/webauthn/index.js +0 -0
- /package/dist/{plugins → src/plugins}/webauthn/types.js +0 -0
- /package/dist/{plugins → src/plugins}/webauthn/webauthn.js +0 -0
- /package/dist/{plugins → src/plugins}/webauthn/webauthnPlugin.js +0 -0
- /package/dist/{plugins → src/plugins}/webauthn/webauthnSigner.js +0 -0
- /package/dist/{storage → src/storage}/storage.js +0 -0
- /package/dist/{types → src/types}/events.js +0 -0
- /package/dist/{types → src/types}/shogun.js +0 -0
- /package/dist/{utils → src/utils}/errorHandler.js +0 -0
- /package/dist/{utils → src/utils}/eventEmitter.js +0 -0
- /package/dist/{utils → src/utils}/validation.js +0 -0
- /package/dist/types/{config → src/config}/simplified-config.d.ts +0 -0
- /package/dist/types/{core.d.ts → src/core.d.ts} +0 -0
- /package/dist/types/{examples → src/examples}/api-test.d.ts +0 -0
- /package/dist/types/{examples → src/examples}/simple-api-test.d.ts +0 -0
- /package/dist/types/{gundb → src/gundb}/api.d.ts +0 -0
- /package/dist/types/{gundb → src/gundb}/crypto.d.ts +0 -0
- /package/dist/types/{gundb → src/gundb}/db.d.ts +0 -0
- /package/dist/types/{gundb → src/gundb}/derive.d.ts +0 -0
- /package/dist/types/{gundb → src/gundb}/errors.d.ts +0 -0
- /package/dist/types/{gundb → src/gundb}/index.d.ts +0 -0
- /package/dist/types/{gundb → src/gundb}/rxjs.d.ts +0 -0
- /package/dist/types/{gundb → src/gundb}/types.d.ts +0 -0
- /package/dist/types/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/types/{interfaces → src/interfaces}/common.d.ts +0 -0
- /package/dist/types/{interfaces → src/interfaces}/events.d.ts +0 -0
- /package/dist/types/{interfaces → src/interfaces}/plugin.d.ts +0 -0
- /package/dist/types/{interfaces → src/interfaces}/shogun.d.ts +0 -0
- /package/dist/types/{managers → src/managers}/AuthManager.d.ts +0 -0
- /package/dist/types/{managers → src/managers}/CoreInitializer.d.ts +0 -0
- /package/dist/types/{managers → src/managers}/EventManager.d.ts +0 -0
- /package/dist/types/{managers → src/managers}/PluginManager.d.ts +0 -0
- /package/dist/types/{migration-test.d.ts → src/migration-test.d.ts} +0 -0
- /package/dist/types/{plugins → src/plugins}/base.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/nostr/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/nostr/nostrConnector.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/nostr/nostrConnectorPlugin.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/nostr/nostrSigner.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/nostr/types.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/oauth/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/oauth/oauthConnector.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/oauth/oauthPlugin.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/oauth/types.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/web3/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/web3/types.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/web3/web3Connector.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/web3/web3ConnectorPlugin.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/web3/web3Signer.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/webauthn/index.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/webauthn/types.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/webauthn/webauthn.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/webauthn/webauthnPlugin.d.ts +0 -0
- /package/dist/types/{plugins → src/plugins}/webauthn/webauthnSigner.d.ts +0 -0
- /package/dist/types/{storage → src/storage}/storage.d.ts +0 -0
- /package/dist/types/{types → src/types}/events.d.ts +0 -0
- /package/dist/types/{types → src/types}/shogun.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/errorHandler.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/eventEmitter.d.ts +0 -0
- /package/dist/types/{utils → src/utils}/validation.d.ts +0 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SHIP-07: Secure Vault Implementation
|
|
4
|
+
*
|
|
5
|
+
* Vault crittografato decentralizzato che dipende da SHIP-00 per l'identità.
|
|
6
|
+
*
|
|
7
|
+
* Dipendenze:
|
|
8
|
+
* - SHIP-00 (Identity & Authentication) - per gestione utenti e chiavi
|
|
9
|
+
* - GunDB - per storage decentralizzato P2P
|
|
10
|
+
* - SEA - per crittografia AES-256-GCM
|
|
11
|
+
*
|
|
12
|
+
* Ispirato a: https://github.com/draeder/gunsafe
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SHIP_07 = void 0;
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// IMPLEMENTATION
|
|
18
|
+
// ============================================================================
|
|
19
|
+
/**
|
|
20
|
+
* SHIP-07 Reference Implementation
|
|
21
|
+
*
|
|
22
|
+
* Questa implementazione dipende da ISHIP_00 per tutte le operazioni di identità.
|
|
23
|
+
* Si concentra esclusivamente sulla logica del vault crittografato.
|
|
24
|
+
*/
|
|
25
|
+
class SHIP_07 {
|
|
26
|
+
/**
|
|
27
|
+
* Constructor
|
|
28
|
+
* @param identity ISHIP_00 instance for identity operations
|
|
29
|
+
* @param vaultNodeName Optional custom vault node name
|
|
30
|
+
*/
|
|
31
|
+
constructor(identity, vaultNodeName) {
|
|
32
|
+
this.initialized = false;
|
|
33
|
+
// Gun nodes
|
|
34
|
+
this.vaultNode = null;
|
|
35
|
+
this.recordsNode = null;
|
|
36
|
+
this.metadataNode = null;
|
|
37
|
+
if (!identity.isLoggedIn()) {
|
|
38
|
+
throw new Error("User must be authenticated via SHIP-00 before using SHIP-07");
|
|
39
|
+
}
|
|
40
|
+
this.identity = identity;
|
|
41
|
+
this.vaultNodeName = vaultNodeName || SHIP_07.DEFAULT_NODE_NAME;
|
|
42
|
+
console.log("✅ SHIP-07 initialized");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get identity provider
|
|
46
|
+
*/
|
|
47
|
+
getIdentity() {
|
|
48
|
+
return this.identity;
|
|
49
|
+
}
|
|
50
|
+
// ========================================================================
|
|
51
|
+
// INITIALIZATION
|
|
52
|
+
// ========================================================================
|
|
53
|
+
/**
|
|
54
|
+
* Initialize vault
|
|
55
|
+
*/
|
|
56
|
+
async initialize() {
|
|
57
|
+
if (this.initialized) {
|
|
58
|
+
console.warn("⚠️ Vault already initialized");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
// Get Gun instance from identity
|
|
63
|
+
const shogun = this.identity.getShogun();
|
|
64
|
+
if (!shogun || !shogun.db) {
|
|
65
|
+
throw new Error("Cannot access ShogunCore from identity");
|
|
66
|
+
}
|
|
67
|
+
const gun = shogun.db.gun;
|
|
68
|
+
if (!gun) {
|
|
69
|
+
throw new Error("Cannot access GunDB");
|
|
70
|
+
}
|
|
71
|
+
// Get user node
|
|
72
|
+
const userNode = gun.user();
|
|
73
|
+
if (!userNode || !userNode.is) {
|
|
74
|
+
throw new Error("User not authenticated in Gun");
|
|
75
|
+
}
|
|
76
|
+
// Setup vault nodes
|
|
77
|
+
this.vaultNode = userNode.get(this.vaultNodeName);
|
|
78
|
+
this.recordsNode = this.vaultNode.get("records");
|
|
79
|
+
this.metadataNode = this.vaultNode.get("metadata");
|
|
80
|
+
// Initialize metadata
|
|
81
|
+
const existingMetadata = await this.metadataNode.then();
|
|
82
|
+
if (!existingMetadata || !existingMetadata.version) {
|
|
83
|
+
await this.metadataNode.put({
|
|
84
|
+
version: SHIP_07.VAULT_VERSION,
|
|
85
|
+
created: Date.now().toString(),
|
|
86
|
+
recordCount: "0",
|
|
87
|
+
}).then();
|
|
88
|
+
console.log("📦 Vault metadata initialized");
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log("📦 Existing vault found");
|
|
92
|
+
}
|
|
93
|
+
this.initialized = true;
|
|
94
|
+
console.log("✅ Vault initialized successfully");
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error("❌ Error initializing vault:", error);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Check if vault is initialized
|
|
103
|
+
*/
|
|
104
|
+
isInitialized() {
|
|
105
|
+
return this.initialized;
|
|
106
|
+
}
|
|
107
|
+
// ========================================================================
|
|
108
|
+
// CRUD OPERATIONS
|
|
109
|
+
// ========================================================================
|
|
110
|
+
/**
|
|
111
|
+
* Store encrypted record in vault
|
|
112
|
+
*/
|
|
113
|
+
async put(name, data, metadata) {
|
|
114
|
+
if (!this.initialized) {
|
|
115
|
+
return { success: false, error: "Vault not initialized" };
|
|
116
|
+
}
|
|
117
|
+
if (!name || name.trim() === "") {
|
|
118
|
+
return { success: false, error: "Record name cannot be empty" };
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
// Get crypto and key pair
|
|
122
|
+
const shogun = this.identity.getShogun();
|
|
123
|
+
const crypto = shogun?.db?.crypto;
|
|
124
|
+
const pair = this.identity.getKeyPair();
|
|
125
|
+
if (!crypto || !pair) {
|
|
126
|
+
return { success: false, error: "Cannot access encryption" };
|
|
127
|
+
}
|
|
128
|
+
// Encrypt data
|
|
129
|
+
const dataString = JSON.stringify(data);
|
|
130
|
+
const encryptedData = await crypto.encrypt(dataString, pair.epriv);
|
|
131
|
+
// Encrypt metadata if provided
|
|
132
|
+
let encryptedMetadata;
|
|
133
|
+
if (metadata) {
|
|
134
|
+
const metadataString = JSON.stringify(metadata);
|
|
135
|
+
encryptedMetadata = await crypto.encrypt(metadataString, pair.epriv);
|
|
136
|
+
}
|
|
137
|
+
// Create encrypted record
|
|
138
|
+
const record = {
|
|
139
|
+
data: encryptedData,
|
|
140
|
+
created: Date.now().toString(),
|
|
141
|
+
updated: Date.now().toString(),
|
|
142
|
+
deleted: false,
|
|
143
|
+
metadata: encryptedMetadata,
|
|
144
|
+
};
|
|
145
|
+
// Store in vault
|
|
146
|
+
await this.recordsNode.get(name).put(record).then();
|
|
147
|
+
// Update vault metadata
|
|
148
|
+
await this.updateRecordCount();
|
|
149
|
+
console.log(`✅ Record stored: ${name}`);
|
|
150
|
+
return {
|
|
151
|
+
success: true,
|
|
152
|
+
recordName: name,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.error("❌ Error storing record:", error);
|
|
157
|
+
return {
|
|
158
|
+
success: false,
|
|
159
|
+
error: error.message,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Retrieve and decrypt record from vault
|
|
165
|
+
*/
|
|
166
|
+
async get(name, options) {
|
|
167
|
+
if (!this.initialized) {
|
|
168
|
+
console.error("❌ Vault not initialized");
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
// Retrieve encrypted record
|
|
173
|
+
const encryptedRecord = await this.recordsNode.get(name).then();
|
|
174
|
+
if (!encryptedRecord || !encryptedRecord.data) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
// Check if deleted (unless includeDeleted)
|
|
178
|
+
if (encryptedRecord.deleted && !options?.includeDeleted) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
// Get crypto and key pair
|
|
182
|
+
const shogun = this.identity.getShogun();
|
|
183
|
+
const crypto = shogun?.db?.crypto;
|
|
184
|
+
const pair = this.identity.getKeyPair();
|
|
185
|
+
if (!crypto || !pair) {
|
|
186
|
+
console.error("❌ Cannot access encryption");
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
// Decrypt data
|
|
190
|
+
const decryptedDataString = await crypto.decrypt(encryptedRecord.data, pair.epriv);
|
|
191
|
+
// Try to parse JSON, if it fails, use the string as-is
|
|
192
|
+
let decryptedData;
|
|
193
|
+
try {
|
|
194
|
+
decryptedData = JSON.parse(decryptedDataString);
|
|
195
|
+
}
|
|
196
|
+
catch (parseError) {
|
|
197
|
+
// If JSON.parse fails, the data is likely a plain string
|
|
198
|
+
// This can happen if the data was already a string value
|
|
199
|
+
decryptedData = decryptedDataString;
|
|
200
|
+
}
|
|
201
|
+
// Decrypt metadata if present
|
|
202
|
+
let decryptedMetadata;
|
|
203
|
+
if (encryptedRecord.metadata) {
|
|
204
|
+
try {
|
|
205
|
+
const metadataString = await crypto.decrypt(encryptedRecord.metadata, pair.epriv);
|
|
206
|
+
decryptedMetadata = JSON.parse(metadataString);
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
// Metadata decryption failed, continue without it
|
|
210
|
+
console.warn("⚠️ Could not decrypt metadata");
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Create vault record
|
|
214
|
+
const vaultRecord = {
|
|
215
|
+
name,
|
|
216
|
+
data: decryptedData,
|
|
217
|
+
created: parseInt(encryptedRecord.created),
|
|
218
|
+
updated: parseInt(encryptedRecord.updated),
|
|
219
|
+
deleted: encryptedRecord.deleted,
|
|
220
|
+
metadata: decryptedMetadata,
|
|
221
|
+
};
|
|
222
|
+
return vaultRecord;
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.error("❌ Error retrieving record:", error);
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Delete record from vault (soft delete)
|
|
231
|
+
*/
|
|
232
|
+
async delete(name) {
|
|
233
|
+
if (!this.initialized) {
|
|
234
|
+
return { success: false, error: "Vault not initialized" };
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
if (name) {
|
|
238
|
+
// Delete specific record (soft delete)
|
|
239
|
+
const existingRecord = await this.recordsNode.get(name).then();
|
|
240
|
+
if (!existingRecord) {
|
|
241
|
+
return { success: false, error: `Record ${name} not found` };
|
|
242
|
+
}
|
|
243
|
+
// Mark as deleted
|
|
244
|
+
await this.recordsNode.get(name).get("deleted").put(true).then();
|
|
245
|
+
await this.recordsNode.get(name).get("updated").put(Date.now().toString()).then();
|
|
246
|
+
console.log(`🗑️ Record soft-deleted: ${name}`);
|
|
247
|
+
return {
|
|
248
|
+
success: true,
|
|
249
|
+
recordName: name,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
// Delete all records (soft delete)
|
|
254
|
+
const allRecords = await this.list({ includeDeleted: false });
|
|
255
|
+
let deletedCount = 0;
|
|
256
|
+
for (const recordName of allRecords) {
|
|
257
|
+
const result = await this.delete(recordName);
|
|
258
|
+
if (result.success) {
|
|
259
|
+
deletedCount++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
console.log(`🗑️ ${deletedCount} records soft-deleted`);
|
|
263
|
+
return {
|
|
264
|
+
success: true,
|
|
265
|
+
recordCount: deletedCount,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
console.error("❌ Error deleting record:", error);
|
|
271
|
+
return {
|
|
272
|
+
success: false,
|
|
273
|
+
error: error.message,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* List all record names in vault
|
|
279
|
+
*/
|
|
280
|
+
async list(options) {
|
|
281
|
+
if (!this.initialized) {
|
|
282
|
+
console.error("❌ Vault not initialized");
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
return new Promise((resolve) => {
|
|
287
|
+
const recordNames = [];
|
|
288
|
+
this.recordsNode.map().once(async (record, key) => {
|
|
289
|
+
// Skip metadata
|
|
290
|
+
if (!record || typeof record !== "object" || key === "_") {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
// Skip deleted records (unless includeDeleted)
|
|
294
|
+
if (record.deleted && !options?.includeDeleted) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
// Apply filters if provided
|
|
298
|
+
if (options?.filterByType || options?.filterByTag) {
|
|
299
|
+
try {
|
|
300
|
+
// Need to decrypt metadata to filter
|
|
301
|
+
const shogun = this.identity.getShogun();
|
|
302
|
+
const crypto = shogun?.db?.crypto;
|
|
303
|
+
const pair = this.identity.getKeyPair();
|
|
304
|
+
if (crypto && pair && record.metadata) {
|
|
305
|
+
const metadataString = await crypto.decrypt(record.metadata, pair.epriv);
|
|
306
|
+
const metadata = JSON.parse(metadataString);
|
|
307
|
+
// Filter by type
|
|
308
|
+
if (options.filterByType && metadata.type !== options.filterByType) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// Filter by tag
|
|
312
|
+
if (options.filterByTag) {
|
|
313
|
+
if (!metadata.tags || !metadata.tags.includes(options.filterByTag)) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
// Decryption failed, skip
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
recordNames.push(key);
|
|
325
|
+
});
|
|
326
|
+
// Wait for Gun to return all records
|
|
327
|
+
setTimeout(() => {
|
|
328
|
+
// Sort if requested
|
|
329
|
+
if (options?.sortBy) {
|
|
330
|
+
// For now, just sort by name
|
|
331
|
+
recordNames.sort();
|
|
332
|
+
if (options.sortDesc) {
|
|
333
|
+
recordNames.reverse();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
resolve(recordNames);
|
|
337
|
+
}, 1000);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
console.error("❌ Error listing records:", error);
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Check if record exists
|
|
347
|
+
*/
|
|
348
|
+
async exists(name) {
|
|
349
|
+
const record = await this.get(name);
|
|
350
|
+
return record !== null;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Update existing record
|
|
354
|
+
*/
|
|
355
|
+
async update(name, data) {
|
|
356
|
+
if (!this.initialized) {
|
|
357
|
+
return { success: false, error: "Vault not initialized" };
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
// Check if record exists
|
|
361
|
+
const existingRecord = await this.get(name);
|
|
362
|
+
if (!existingRecord) {
|
|
363
|
+
return { success: false, error: `Record ${name} not found` };
|
|
364
|
+
}
|
|
365
|
+
// Keep existing metadata
|
|
366
|
+
return await this.put(name, data, existingRecord.metadata);
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
console.error("❌ Error updating record:", error);
|
|
370
|
+
return {
|
|
371
|
+
success: false,
|
|
372
|
+
error: error.message,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// ========================================================================
|
|
377
|
+
// BACKUP & RESTORE
|
|
378
|
+
// ========================================================================
|
|
379
|
+
/**
|
|
380
|
+
* Export entire vault (encrypted)
|
|
381
|
+
*/
|
|
382
|
+
async export(password, options) {
|
|
383
|
+
if (!this.initialized) {
|
|
384
|
+
throw new Error("Vault not initialized");
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
// Get all records
|
|
388
|
+
const recordNames = await this.list({
|
|
389
|
+
includeDeleted: options?.includeDeleted || false,
|
|
390
|
+
filterByTag: options?.filterByTag,
|
|
391
|
+
filterByType: options?.filterByType,
|
|
392
|
+
});
|
|
393
|
+
const records = {};
|
|
394
|
+
for (const name of recordNames) {
|
|
395
|
+
const record = await this.get(name, {
|
|
396
|
+
includeDeleted: options?.includeDeleted || false,
|
|
397
|
+
});
|
|
398
|
+
if (record) {
|
|
399
|
+
records[name] = record;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Create export data
|
|
403
|
+
const exportData = {
|
|
404
|
+
version: SHIP_07.VAULT_VERSION,
|
|
405
|
+
exportedAt: Date.now(),
|
|
406
|
+
exportedBy: this.identity.getCurrentUser()?.pub,
|
|
407
|
+
recordCount: Object.keys(records).length,
|
|
408
|
+
records,
|
|
409
|
+
};
|
|
410
|
+
// Serialize to JSON
|
|
411
|
+
const jsonString = options?.pretty
|
|
412
|
+
? JSON.stringify(exportData, null, 2)
|
|
413
|
+
: JSON.stringify(exportData);
|
|
414
|
+
// Optionally encrypt with password
|
|
415
|
+
if (password) {
|
|
416
|
+
const crypto = this.identity.shogun?.db?.crypto;
|
|
417
|
+
if (!crypto) {
|
|
418
|
+
throw new Error("Cannot access crypto");
|
|
419
|
+
}
|
|
420
|
+
const encryptedJson = await crypto.encrypt(jsonString, password);
|
|
421
|
+
const base64 = Buffer.from(encryptedJson).toString("base64");
|
|
422
|
+
console.log(`✅ Vault exported (encrypted, ${base64.length} chars)`);
|
|
423
|
+
return base64;
|
|
424
|
+
}
|
|
425
|
+
// Otherwise, return as base64
|
|
426
|
+
const base64 = Buffer.from(jsonString).toString("base64");
|
|
427
|
+
console.log(`✅ Vault exported (${base64.length} chars)`);
|
|
428
|
+
return base64;
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
console.error("❌ Error exporting vault:", error);
|
|
432
|
+
throw error;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Import vault from backup
|
|
437
|
+
*/
|
|
438
|
+
async import(backupData, password, options) {
|
|
439
|
+
if (!this.initialized) {
|
|
440
|
+
return { success: false, error: "Vault not initialized" };
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
// Decode base64
|
|
444
|
+
let jsonString = Buffer.from(backupData, "base64").toString("utf-8");
|
|
445
|
+
// Decrypt if password provided
|
|
446
|
+
if (password) {
|
|
447
|
+
const crypto = this.identity.shogun?.db?.crypto;
|
|
448
|
+
if (!crypto) {
|
|
449
|
+
return { success: false, error: "Cannot access crypto" };
|
|
450
|
+
}
|
|
451
|
+
jsonString = await crypto.decrypt(jsonString, password);
|
|
452
|
+
}
|
|
453
|
+
// Parse JSON
|
|
454
|
+
const importData = JSON.parse(jsonString);
|
|
455
|
+
// Validate version
|
|
456
|
+
if (importData.version !== SHIP_07.VAULT_VERSION) {
|
|
457
|
+
console.warn(`⚠️ Version mismatch: ${importData.version} vs ${SHIP_07.VAULT_VERSION}`);
|
|
458
|
+
}
|
|
459
|
+
// Import records
|
|
460
|
+
let importedCount = 0;
|
|
461
|
+
let skippedCount = 0;
|
|
462
|
+
for (const [name, record] of Object.entries(importData.records)) {
|
|
463
|
+
const vaultRecord = record;
|
|
464
|
+
// Skip deleted records if requested
|
|
465
|
+
if (options?.skipDeleted && vaultRecord.deleted) {
|
|
466
|
+
skippedCount++;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
// Check if exists (if merge mode)
|
|
470
|
+
const exists = await this.exists(name);
|
|
471
|
+
if (exists) {
|
|
472
|
+
if (options?.overwrite) {
|
|
473
|
+
// Overwrite existing
|
|
474
|
+
await this.put(name, vaultRecord.data, vaultRecord.metadata);
|
|
475
|
+
importedCount++;
|
|
476
|
+
}
|
|
477
|
+
else if (!options?.merge) {
|
|
478
|
+
// Skip if not merge and not overwrite
|
|
479
|
+
skippedCount++;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
// Merge mode: skip existing
|
|
484
|
+
skippedCount++;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
// Import new record
|
|
490
|
+
await this.put(name, vaultRecord.data, vaultRecord.metadata);
|
|
491
|
+
importedCount++;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
console.log(`✅ Vault imported: ${importedCount} records (${skippedCount} skipped)`);
|
|
495
|
+
return {
|
|
496
|
+
success: true,
|
|
497
|
+
recordCount: importedCount,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
console.error("❌ Error importing vault:", error);
|
|
502
|
+
return {
|
|
503
|
+
success: false,
|
|
504
|
+
error: error.message,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// ========================================================================
|
|
509
|
+
// UTILITIES
|
|
510
|
+
// ========================================================================
|
|
511
|
+
/**
|
|
512
|
+
* Get vault statistics
|
|
513
|
+
*/
|
|
514
|
+
async getStats() {
|
|
515
|
+
if (!this.initialized) {
|
|
516
|
+
throw new Error("Vault not initialized");
|
|
517
|
+
}
|
|
518
|
+
try {
|
|
519
|
+
const allRecords = await this.list({ includeDeleted: true });
|
|
520
|
+
const activeRecords = await this.list({ includeDeleted: false });
|
|
521
|
+
// Get metadata
|
|
522
|
+
const metadata = await this.metadataNode.then();
|
|
523
|
+
const stats = {
|
|
524
|
+
totalRecords: allRecords.length,
|
|
525
|
+
activeRecords: activeRecords.length,
|
|
526
|
+
deletedRecords: allRecords.length - activeRecords.length,
|
|
527
|
+
totalSize: 0, // TODO: Calculate actual size
|
|
528
|
+
created: metadata?.created ? parseInt(metadata.created) : Date.now(),
|
|
529
|
+
lastModified: Date.now(),
|
|
530
|
+
recordsByType: {},
|
|
531
|
+
};
|
|
532
|
+
// Count by type
|
|
533
|
+
for (const name of activeRecords) {
|
|
534
|
+
const record = await this.get(name);
|
|
535
|
+
if (record && record.metadata?.type) {
|
|
536
|
+
const type = record.metadata.type;
|
|
537
|
+
stats.recordsByType[type] = (stats.recordsByType[type] || 0) + 1;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return stats;
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
console.error("❌ Error getting stats:", error);
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Clear all records (soft delete all)
|
|
549
|
+
*/
|
|
550
|
+
async clear() {
|
|
551
|
+
return await this.delete(); // Delete without name = delete all
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Compact vault (remove deleted records permanently)
|
|
555
|
+
*/
|
|
556
|
+
async compact() {
|
|
557
|
+
if (!this.initialized) {
|
|
558
|
+
return { success: false, error: "Vault not initialized" };
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
// Get all deleted records
|
|
562
|
+
const allRecords = await this.list({ includeDeleted: true });
|
|
563
|
+
let compactedCount = 0;
|
|
564
|
+
for (const name of allRecords) {
|
|
565
|
+
const record = await this.get(name, { includeDeleted: true });
|
|
566
|
+
if (record && record.deleted) {
|
|
567
|
+
// Permanently remove
|
|
568
|
+
await this.recordsNode.get(name).put(null).then();
|
|
569
|
+
compactedCount++;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// Update metadata
|
|
573
|
+
await this.updateRecordCount();
|
|
574
|
+
console.log(`✅ Vault compacted: ${compactedCount} records permanently removed`);
|
|
575
|
+
return {
|
|
576
|
+
success: true,
|
|
577
|
+
recordCount: compactedCount,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
console.error("❌ Error compacting vault:", error);
|
|
582
|
+
return {
|
|
583
|
+
success: false,
|
|
584
|
+
error: error.message,
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Search records by content
|
|
590
|
+
*/
|
|
591
|
+
async search(query) {
|
|
592
|
+
if (!this.initialized) {
|
|
593
|
+
return [];
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
const allRecords = await this.list({ includeDeleted: false });
|
|
597
|
+
const matches = [];
|
|
598
|
+
for (const name of allRecords) {
|
|
599
|
+
const record = await this.get(name);
|
|
600
|
+
if (record) {
|
|
601
|
+
// Search in data (converted to string)
|
|
602
|
+
const dataString = JSON.stringify(record.data).toLowerCase();
|
|
603
|
+
const queryLower = query.toLowerCase();
|
|
604
|
+
if (dataString.includes(queryLower) || name.toLowerCase().includes(queryLower)) {
|
|
605
|
+
matches.push(name);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return matches;
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
console.error("❌ Error searching records:", error);
|
|
613
|
+
return [];
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
// ========================================================================
|
|
617
|
+
// PRIVATE HELPERS
|
|
618
|
+
// ========================================================================
|
|
619
|
+
/**
|
|
620
|
+
* Update record count in metadata
|
|
621
|
+
*/
|
|
622
|
+
async updateRecordCount() {
|
|
623
|
+
try {
|
|
624
|
+
const activeRecords = await this.list({ includeDeleted: false });
|
|
625
|
+
await this.metadataNode.get("recordCount").put(activeRecords.length.toString()).then();
|
|
626
|
+
}
|
|
627
|
+
catch (error) {
|
|
628
|
+
console.error("❌ Error updating record count:", error);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
exports.SHIP_07 = SHIP_07;
|
|
633
|
+
// Constants
|
|
634
|
+
SHIP_07.VAULT_VERSION = "1.0.0";
|
|
635
|
+
SHIP_07.DEFAULT_NODE_NAME = "vault";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SHIP_06 = exports.SHIP_05 = exports.SHIP_04 = exports.SHIP_03 = exports.SHIP_02 = exports.SHIP_01 = exports.SHIP_00 = void 0;
|
|
4
|
+
var SHIP_00_1 = require("../ship/implementation/SHIP_00");
|
|
5
|
+
Object.defineProperty(exports, "SHIP_00", { enumerable: true, get: function () { return SHIP_00_1.SHIP_00; } });
|
|
6
|
+
var SHIP_01_1 = require("../ship/implementation/SHIP_01");
|
|
7
|
+
Object.defineProperty(exports, "SHIP_01", { enumerable: true, get: function () { return SHIP_01_1.SHIP_01; } });
|
|
8
|
+
var SHIP_02_1 = require("../ship/implementation/SHIP_02");
|
|
9
|
+
Object.defineProperty(exports, "SHIP_02", { enumerable: true, get: function () { return SHIP_02_1.SHIP_02; } });
|
|
10
|
+
var SHIP_03_1 = require("../ship/implementation/SHIP_03");
|
|
11
|
+
Object.defineProperty(exports, "SHIP_03", { enumerable: true, get: function () { return SHIP_03_1.SHIP_03; } });
|
|
12
|
+
var SHIP_04_1 = require("../ship/implementation/SHIP_04");
|
|
13
|
+
Object.defineProperty(exports, "SHIP_04", { enumerable: true, get: function () { return SHIP_04_1.SHIP_04; } });
|
|
14
|
+
var SHIP_05_1 = require("../ship/implementation/SHIP_05");
|
|
15
|
+
Object.defineProperty(exports, "SHIP_05", { enumerable: true, get: function () { return SHIP_05_1.SHIP_05; } });
|
|
16
|
+
var SHIP_06_1 = require("../ship/implementation/SHIP_06");
|
|
17
|
+
Object.defineProperty(exports, "SHIP_06", { enumerable: true, get: function () { return SHIP_06_1.SHIP_06; } });
|