shogun-core 3.3.0 → 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.
Files changed (36) hide show
  1. package/dist/browser/shogun-core.js +83301 -148719
  2. package/dist/browser/shogun-core.js.map +1 -1
  3. package/dist/ship/examples/ephemeral-cli.js +234 -0
  4. package/dist/ship/examples/identity-cli.js +503 -0
  5. package/dist/ship/examples/stealth-cli.js +433 -0
  6. package/dist/ship/examples/storage-cli.js +615 -0
  7. package/dist/ship/examples/vault-cli.js +444 -0
  8. package/dist/ship/implementation/SHIP_04.js +589 -0
  9. package/dist/ship/implementation/SHIP_05.js +1064 -0
  10. package/dist/ship/implementation/SHIP_06.js +350 -0
  11. package/dist/ship/implementation/SHIP_07.js +635 -0
  12. package/dist/ship/index.js +17 -0
  13. package/dist/ship/interfaces/ISHIP_04.js +62 -0
  14. package/dist/ship/interfaces/ISHIP_05.js +59 -0
  15. package/dist/ship/interfaces/ISHIP_06.js +144 -0
  16. package/dist/ship/interfaces/ISHIP_07.js +194 -0
  17. package/dist/src/index.js +1 -15
  18. package/dist/types/ship/examples/ephemeral-cli.d.ts +13 -0
  19. package/dist/types/ship/examples/identity-cli.d.ts +40 -0
  20. package/dist/types/ship/examples/stealth-cli.d.ts +31 -0
  21. package/dist/types/ship/examples/storage-cli.d.ts +48 -0
  22. package/dist/types/ship/examples/vault-cli.d.ts +13 -0
  23. package/dist/types/ship/implementation/SHIP_04.d.ts +76 -0
  24. package/dist/types/ship/implementation/SHIP_05.d.ts +70 -0
  25. package/dist/types/ship/implementation/SHIP_06.d.ts +66 -0
  26. package/dist/types/ship/implementation/SHIP_07.d.ts +101 -0
  27. package/dist/types/ship/index.d.ts +14 -0
  28. package/dist/types/ship/interfaces/ISHIP_04.d.ts +245 -0
  29. package/dist/types/ship/interfaces/ISHIP_05.d.ts +234 -0
  30. package/dist/types/ship/interfaces/ISHIP_06.d.ts +370 -0
  31. package/dist/types/ship/interfaces/ISHIP_07.d.ts +522 -0
  32. package/dist/types/src/index.d.ts +0 -10
  33. package/dist/types/src/types/shogun.d.ts +2 -0
  34. package/package.json +1 -1
  35. package/dist/browser/_e6ae.shogun-core.js +0 -14
  36. package/dist/browser/_e6ae.shogun-core.js.map +0 -1
@@ -0,0 +1,350 @@
1
+ "use strict";
2
+ /**
3
+ * SHIP-06: Ephemeral P2P Messaging Implementation
4
+ *
5
+ * Two modes:
6
+ * 1. Standalone: new SHIP_06(gunPeers[], roomId) - NO authentication!
7
+ * - Uses ShogunCore internally with silent: true, disableAutoRecall: true
8
+ * - Zero logs, zero storage, pure relay communication
9
+ * - Room hashed with Web Crypto API SHA-256 for deterministic IDs
10
+ *
11
+ * 2. With Identity: new SHIP_06(ISHIP_00, roomId) - Authenticated sessions
12
+ * - Uses existing Gun instance from SHIP-00
13
+ * - All ShogunCore features available
14
+ *
15
+ * Architecture:
16
+ * - Gun Relay for P2P communication (no WebRTC complexity!)
17
+ * - SEA for ephemeral key generation and ECDH encryption
18
+ * - Pure relay mode: radisk: false, localStorage: false, multicast: false
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.SHIP_06 = void 0;
22
+ const core_1 = require("../../src/core"); // ← Import diretto, NON da index!
23
+ // ============================================================================
24
+ // IMPLEMENTATION - STANDALONE VERSION (No ShogunCore dependency!)
25
+ // ============================================================================
26
+ class SHIP_06 {
27
+ // Constructor overload: with identity OR standalone
28
+ constructor(identityOrPeers, roomId, config) {
29
+ this.identity = null;
30
+ // State
31
+ this.connected = false;
32
+ this.swarmId = "";
33
+ this.myAddress = "";
34
+ this.myPair = null;
35
+ // Gun nodes
36
+ this.gun = null;
37
+ this.sea = null;
38
+ this.roomNode = null;
39
+ this.presenceNode = null;
40
+ this.messagesNode = null;
41
+ // Peers
42
+ this.peers = new Map();
43
+ // Event handlers
44
+ this.messageHandlers = [];
45
+ this.encryptedMessageHandlers = [];
46
+ this.peerSeenHandlers = [];
47
+ this.peerLeftHandlers = [];
48
+ // Heartbeat & cleanup
49
+ this.heartbeatInterval = null;
50
+ this.processedMessages = new Set();
51
+ this.roomId = roomId;
52
+ this.config = {
53
+ debug: config?.debug || false,
54
+ timeout: 30000,
55
+ };
56
+ if (Array.isArray(identityOrPeers)) {
57
+ // STANDALONE MODE - ShogunCore with silent mode
58
+ const shogunCore = new core_1.ShogunCore({
59
+ gunOptions: {
60
+ peers: identityOrPeers,
61
+ radisk: false,
62
+ localStorage: false,
63
+ multicast: false,
64
+ axe: false,
65
+ },
66
+ silent: true,
67
+ disableAutoRecall: true,
68
+ });
69
+ this.gun = shogunCore.db.gun;
70
+ this.sea = shogunCore.db.sea;
71
+ this.identity = null;
72
+ }
73
+ else {
74
+ // WITH IDENTITY MODE - use existing Gun from SHIP-00
75
+ if (!identityOrPeers.isLoggedIn()) {
76
+ throw new Error("User must be authenticated via SHIP-00");
77
+ }
78
+ this.identity = identityOrPeers;
79
+ const shogun = identityOrPeers.getShogun();
80
+ this.gun = shogun.db.gun;
81
+ }
82
+ }
83
+ getIdentity() {
84
+ if (!this.identity) {
85
+ throw new Error("No identity - SHIP-06 running in standalone mode");
86
+ }
87
+ return this.identity;
88
+ }
89
+ async connect() {
90
+ if (this.connected)
91
+ return;
92
+ // Generate ephemeral pair
93
+ this.myPair = await this.sea.pair();
94
+ if (!this.myPair) {
95
+ throw new Error("Failed to generate SEA pair");
96
+ }
97
+ this.myAddress = this.myPair.pub.substring(0, 16);
98
+ // Hash room ID DETERMINISTICAMENTE - Simple SHA256 hash
99
+ if (this.identity) {
100
+ const shogun = this.identity.getShogun();
101
+ this.swarmId = await shogun.db.crypto.hashText(this.roomId);
102
+ }
103
+ else {
104
+ // Standalone: use simple deterministic hash
105
+ // SEA.work with different calls produces different results, so we use a simple hash
106
+ const encoder = new TextEncoder();
107
+ const data = encoder.encode(this.roomId);
108
+ // Use Web Crypto API for deterministic SHA-256 hash
109
+ if (typeof crypto !== "undefined" && crypto.subtle) {
110
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
111
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
112
+ this.swarmId = hashArray
113
+ .map((b) => b.toString(16).padStart(2, "0"))
114
+ .join("");
115
+ }
116
+ else {
117
+ // Fallback: simple deterministic hash
118
+ let hash = "";
119
+ for (let i = 0; i < this.roomId.length; i++) {
120
+ hash += this.roomId.charCodeAt(i).toString(16);
121
+ }
122
+ this.swarmId = hash;
123
+ }
124
+ }
125
+ if (this.config.debug) {
126
+ console.log(`🔑 Room ID: "${this.roomId}"`);
127
+ console.log(`🔒 Swarm ID (hashed): ${this.swarmId.substring(0, 32)}...`);
128
+ }
129
+ // Setup Gun nodes
130
+ this.roomNode = this.gun.get("ephemeral").get(this.swarmId);
131
+ this.presenceNode = this.roomNode.get("presence");
132
+ this.messagesNode = this.roomNode.get("messages");
133
+ // Announce presence
134
+ await this.announcePresence();
135
+ // Start listening
136
+ this.listenForPeers();
137
+ this.listenForMessages();
138
+ // Start heartbeat
139
+ this.startHeartbeat();
140
+ this.connected = true;
141
+ }
142
+ disconnect() {
143
+ if (this.heartbeatInterval) {
144
+ clearInterval(this.heartbeatInterval);
145
+ }
146
+ if (this.presenceNode && this.myAddress) {
147
+ this.presenceNode.get(this.myAddress).put(null);
148
+ }
149
+ this.connected = false;
150
+ }
151
+ isConnected() {
152
+ return this.connected;
153
+ }
154
+ getSwarmId() {
155
+ return this.swarmId;
156
+ }
157
+ getAddress() {
158
+ return this.myAddress;
159
+ }
160
+ // ========================================================================
161
+ // PRESENCE
162
+ // ========================================================================
163
+ async announcePresence() {
164
+ if (!this.myPair)
165
+ return;
166
+ const presenceData = {
167
+ address: this.myAddress,
168
+ pub: this.myPair.pub,
169
+ epub: this.myPair.epub,
170
+ timestamp: Date.now(),
171
+ };
172
+ if (this.config.debug) {
173
+ console.log(`📡 Announcing presence: ${this.myAddress}`);
174
+ }
175
+ await this.presenceNode.get(this.myAddress).put(presenceData);
176
+ }
177
+ startHeartbeat() {
178
+ this.heartbeatInterval = setInterval(() => {
179
+ if (this.myPair) {
180
+ this.presenceNode.get(this.myAddress).put({
181
+ address: this.myAddress,
182
+ pub: this.myPair.pub,
183
+ epub: this.myPair.epub,
184
+ timestamp: Date.now(),
185
+ });
186
+ }
187
+ }, 3000);
188
+ }
189
+ listenForPeers() {
190
+ if (this.config.debug) {
191
+ console.log(`👂 Listening for peers on: ephemeral/${this.swarmId.substring(0, 20)}...`);
192
+ }
193
+ this.presenceNode.map().on((data, address) => {
194
+ if (!data || !address || address === "_" || address === this.myAddress) {
195
+ return;
196
+ }
197
+ if (!data.pub || !data.epub || !data.timestamp) {
198
+ return;
199
+ }
200
+ const age = Date.now() - (data.timestamp || 0);
201
+ const isOnline = age < 10000;
202
+ if (!this.peers.has(address)) {
203
+ this.peers.set(address, {
204
+ address,
205
+ pubKey: data.pub,
206
+ epub: data.epub,
207
+ pub: data.pub,
208
+ connectedAt: data.timestamp,
209
+ });
210
+ if (isOnline) {
211
+ this.peerSeenHandlers.forEach((h) => h(address));
212
+ }
213
+ }
214
+ else {
215
+ const peer = this.peers.get(address);
216
+ if (peer && data.timestamp > peer.connectedAt) {
217
+ peer.connectedAt = data.timestamp;
218
+ peer.pubKey = data.pub;
219
+ peer.epub = data.epub;
220
+ peer.pub = data.pub;
221
+ }
222
+ }
223
+ });
224
+ }
225
+ // ========================================================================
226
+ // MESSAGING
227
+ // ========================================================================
228
+ async sendBroadcast(message) {
229
+ if (!this.connected || !this.myPair) {
230
+ throw new Error("Not connected");
231
+ }
232
+ if (this.peers.size === 0) {
233
+ console.warn("⚠️ No peers connected");
234
+ return;
235
+ }
236
+ for (const [address, peer] of this.peers.entries()) {
237
+ try {
238
+ const secret = await this.sea.secret(peer.epub, this.myPair);
239
+ const encrypted = await this.sea.encrypt(message, secret);
240
+ const msgId = `${Date.now()}-${this.myAddress}-${Math.random().toString(36).substring(2, 9)}`;
241
+ await this.messagesNode.get(msgId).put({
242
+ from: this.myAddress,
243
+ fromPub: this.myPair.pub,
244
+ fromEpub: this.myPair.epub,
245
+ to: address,
246
+ content: encrypted,
247
+ timestamp: Date.now(),
248
+ type: "broadcast",
249
+ });
250
+ }
251
+ catch (error) {
252
+ console.error(` ❌ Error sending to ${address.substring(0, 8)}:`, error);
253
+ }
254
+ }
255
+ }
256
+ async sendDirect(peerAddress, message) {
257
+ const peer = this.peers.get(peerAddress);
258
+ if (!peer)
259
+ throw new Error(`Peer ${peerAddress} not found`);
260
+ if (!this.myPair)
261
+ throw new Error("No SEA pair");
262
+ const secret = await this.sea.secret(peer.epub, this.myPair);
263
+ const encrypted = await this.sea.encrypt(message, secret);
264
+ const msgId = `${Date.now()}-${this.myAddress}-${Math.random().toString(36).substring(2, 9)}`;
265
+ await this.messagesNode.get(msgId).put({
266
+ from: this.myAddress,
267
+ fromPub: this.myPair.pub,
268
+ fromEpub: this.myPair.epub,
269
+ to: peerAddress,
270
+ content: encrypted,
271
+ timestamp: Date.now(),
272
+ type: "direct",
273
+ });
274
+ }
275
+ listenForMessages() {
276
+ this.messagesNode.map().on(async (data, msgId) => {
277
+ if (!data || msgId === "_" || this.processedMessages.has(msgId))
278
+ return;
279
+ if (data.to !== this.myAddress || data.from === this.myAddress)
280
+ return;
281
+ this.processedMessages.add(msgId);
282
+ console.log(`\n📬 MESSAGE RECEIVED!`);
283
+ console.log(` From: ${data.from.substring(0, 12)}...`);
284
+ try {
285
+ let peer = this.peers.get(data.from);
286
+ if (!peer && data.fromPub && data.fromEpub) {
287
+ peer = {
288
+ address: data.from,
289
+ pubKey: data.fromPub,
290
+ epub: data.fromEpub,
291
+ pub: data.fromPub,
292
+ connectedAt: data.timestamp,
293
+ };
294
+ this.peers.set(data.from, peer);
295
+ }
296
+ if (!peer || !this.myPair)
297
+ return;
298
+ const senderEpub = data.fromEpub || peer.epub;
299
+ const secret = await this.sea.secret(senderEpub, this.myPair);
300
+ const decrypted = await this.sea.decrypt(data.content, secret);
301
+ if (!decrypted)
302
+ return;
303
+ console.log(` Content: "${decrypted}"`);
304
+ const ephemeralMsg = {
305
+ from: data.from,
306
+ fromPubKey: data.fromPub || peer.pubKey || "",
307
+ content: decrypted,
308
+ timestamp: data.timestamp,
309
+ type: data.type,
310
+ };
311
+ this.messageHandlers.forEach((h) => h(ephemeralMsg));
312
+ }
313
+ catch (error) {
314
+ if (this.config.debug) {
315
+ console.error(` ❌ Error:`, error);
316
+ }
317
+ }
318
+ });
319
+ }
320
+ // ========================================================================
321
+ // EVENT HANDLERS
322
+ // ========================================================================
323
+ onMessage(callback) {
324
+ this.messageHandlers.push(callback);
325
+ }
326
+ onPeerSeen(callback) {
327
+ this.peerSeenHandlers.push(callback);
328
+ }
329
+ onPeerLeft(callback) {
330
+ this.peerLeftHandlers.push(callback);
331
+ }
332
+ onEncryptedMessage(callback) {
333
+ this.encryptedMessageHandlers.push(callback);
334
+ }
335
+ getPeers() {
336
+ return Array.from(this.peers.keys());
337
+ }
338
+ getPeerInfo(address) {
339
+ return this.peers.get(address) || null;
340
+ }
341
+ async getEphemeralPair() {
342
+ if (!this.myPair)
343
+ throw new Error("No ephemeral pair");
344
+ return this.myPair;
345
+ }
346
+ async setEphemeralPair(pair) {
347
+ this.myPair = pair;
348
+ }
349
+ }
350
+ exports.SHIP_06 = SHIP_06;