holosphere 1.1.21 → 1.3.0-alpha3

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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Nostr-compatible cryptographic utilities for HoloSphere v1.3
3
+ * Provides key generation, encoding, and conversion using secp256k1
4
+ */
5
+
6
+ import { secp256k1 } from '@noble/curves/secp256k1';
7
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
8
+ import { bech32 } from '@scure/base';
9
+
10
+ function generatePrivateKey() {
11
+ const privKey = secp256k1.utils.randomPrivateKey();
12
+ return bytesToHex(privKey);
13
+ }
14
+
15
+ function getPublicKey(privateKeyHex) {
16
+ // Get compressed public key (33 bytes) then extract x-only (32 bytes)
17
+ const pubBytes = secp256k1.getPublicKey(privateKeyHex, true);
18
+ // x-only pubkey is the compressed key minus the prefix byte
19
+ return bytesToHex(pubBytes.slice(1));
20
+ }
21
+
22
+ function getPublicKeyFromBytes(privateKeyBytes) {
23
+ const pubBytes = secp256k1.getPublicKey(privateKeyBytes, true);
24
+ return bytesToHex(pubBytes.slice(1));
25
+ }
26
+
27
+ function hexToNpub(hex) {
28
+ const data = hexToBytes(hex);
29
+ const words = bech32.toWords(data);
30
+ return bech32.encode('npub', words, 1500);
31
+ }
32
+
33
+ function hexToNsec(hex) {
34
+ const data = hexToBytes(hex);
35
+ const words = bech32.toWords(data);
36
+ return bech32.encode('nsec', words, 1500);
37
+ }
38
+
39
+ function npubToHex(npub) {
40
+ const { words } = bech32.decode(npub, 1500);
41
+ const data = bech32.fromWords(words);
42
+ return bytesToHex(new Uint8Array(data));
43
+ }
44
+
45
+ function nsecToHex(nsec) {
46
+ const { words } = bech32.decode(nsec, 1500);
47
+ const data = bech32.fromWords(words);
48
+ return bytesToHex(new Uint8Array(data));
49
+ }
50
+
51
+ function parseNsecOrHex(input) {
52
+ if (!input || typeof input !== 'string') return null;
53
+ input = input.trim();
54
+ if (input.startsWith('nsec1')) {
55
+ try {
56
+ return nsecToHex(input);
57
+ } catch (e) {
58
+ return null;
59
+ }
60
+ }
61
+ // Validate hex
62
+ if (/^[0-9a-f]{64}$/i.test(input)) {
63
+ return input.toLowerCase();
64
+ }
65
+ return null;
66
+ }
67
+
68
+ function parseNpubOrHex(input) {
69
+ if (!input || typeof input !== 'string') return null;
70
+ input = input.trim();
71
+ if (input.startsWith('npub1')) {
72
+ try {
73
+ return npubToHex(input);
74
+ } catch (e) {
75
+ return null;
76
+ }
77
+ }
78
+ if (/^[0-9a-f]{64}$/i.test(input)) {
79
+ return input.toLowerCase();
80
+ }
81
+ return null;
82
+ }
83
+
84
+ function shortenPubKey(pubkey, len = 8) {
85
+ if (!pubkey) return '';
86
+ return pubkey.slice(0, len) + '...' + pubkey.slice(-len);
87
+ }
88
+
89
+ function shortenNpub(npub, len = 8) {
90
+ if (!npub) return '';
91
+ return npub.slice(0, len + 5) + '...' + npub.slice(-len);
92
+ }
93
+
94
+ function generateNonce() {
95
+ const bytes = new Uint8Array(32);
96
+ if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues) {
97
+ globalThis.crypto.getRandomValues(bytes);
98
+ } else {
99
+ // Simple fallback - not cryptographically secure but functional
100
+ for (let i = 0; i < 32; i++) {
101
+ bytes[i] = Math.floor(Math.random() * 256);
102
+ }
103
+ }
104
+ return bytesToHex(bytes);
105
+ }
106
+
107
+ export const nostrUtils = {
108
+ generatePrivateKey,
109
+ getPublicKey,
110
+ getPublicKeyFromBytes,
111
+ parseNsecOrHex,
112
+ parseNpubOrHex,
113
+ hexToNpub,
114
+ hexToNsec,
115
+ npubToHex,
116
+ nsecToHex,
117
+ hexToBytes,
118
+ bytesToHex,
119
+ shortenPubKey,
120
+ shortenNpub,
121
+ generateNonce
122
+ };
123
+
124
+ export { hexToBytes, bytesToHex };
125
+ export default nostrUtils;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "holosphere",
3
- "version": "1.1.21",
3
+ "version": "1.3.0-alpha3",
4
4
  "description": "Holonic Geospatial Communication Infrastructure",
5
5
  "main": "holosphere.js",
6
6
  "module": "holosphere.js",
@@ -20,10 +20,12 @@
20
20
  "author": "Roberto Valenti",
21
21
  "license": "GPL-3.0-or-later",
22
22
  "dependencies": {
23
+ "@noble/curves": "^1.8.0",
24
+ "@noble/hashes": "^1.8.0",
25
+ "@scure/base": "^1.2.0",
23
26
  "ajv": "^8.12.0",
24
27
  "gun": "^0.2020.1240",
25
- "h3-js": "^4.1.0",
26
- "openai": "^4.85.1"
28
+ "h3-js": "^4.1.0"
27
29
  },
28
30
  "devDependencies": {
29
31
  "esbuild": "^0.25.12",
@@ -53,6 +55,10 @@
53
55
  "compute.js",
54
56
  "utils.js",
55
57
  "hexlib.js",
58
+ "nostr-utils-shim.js",
59
+ "subscriptions-shim.js",
60
+ "registry-shim.js",
61
+ "handshake-shim.js",
56
62
  "README.md",
57
63
  "LICENSE",
58
64
  "FEDERATION.md"
@@ -99,6 +105,9 @@
99
105
  "peerDependenciesMeta": {
100
106
  "openai": {
101
107
  "optional": true
108
+ },
109
+ "gun": {
110
+ "optional": false
102
111
  }
103
112
  }
104
113
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * GunDB-based registry for HoloSphere v1.3
3
+ * Provides capability storage for federation handshakes
4
+ */
5
+
6
+ export const registry = {
7
+ /**
8
+ * Store an inbound capability for a holon
9
+ * @param {object} client - The HoloSphere instance
10
+ * @param {string} appName - Application name
11
+ * @param {string} holonId - Holon identifier
12
+ * @param {object} capability - Capability data to store
13
+ * @returns {Promise<{ success: boolean }>}
14
+ */
15
+ async storeInboundCapability(client, appName, holonId, capability) {
16
+ try {
17
+ const gun = client.gun || client;
18
+ const id = (capability.token || 'cap') + '_' + Date.now();
19
+ const payload = JSON.stringify({ ...capability, id, storedAt: Date.now() });
20
+
21
+ return new Promise((resolve) => {
22
+ gun.get(appName).get('_capabilities').get(holonId).get(id).put(payload, (ack) => {
23
+ if (ack.err) {
24
+ console.warn('[registry] Failed to store capability:', ack.err);
25
+ resolve({ success: false, error: ack.err });
26
+ } else {
27
+ resolve({ success: true, id });
28
+ }
29
+ });
30
+ });
31
+ } catch (error) {
32
+ console.error('[registry] Error storing capability:', error);
33
+ return { success: false, error: error.message };
34
+ }
35
+ },
36
+
37
+ /**
38
+ * Get inbound capabilities for a holon
39
+ * @param {object} client - The HoloSphere instance
40
+ * @param {string} appName - Application name
41
+ * @param {string} holonId - Holon identifier
42
+ * @returns {Promise<Array<object>>}
43
+ */
44
+ async getInboundCapabilities(client, appName, holonId) {
45
+ try {
46
+ const gun = client.gun || client;
47
+ return new Promise((resolve) => {
48
+ const caps = [];
49
+ const capPath = gun.get(appName).get('_capabilities').get(holonId);
50
+
51
+ capPath.once((data) => {
52
+ if (!data) {
53
+ resolve([]);
54
+ return;
55
+ }
56
+
57
+ const keys = Object.keys(data).filter(k => k !== '_');
58
+ if (keys.length === 0) {
59
+ resolve([]);
60
+ return;
61
+ }
62
+
63
+ let received = 0;
64
+ capPath.map().once((itemData, key) => {
65
+ received++;
66
+ if (itemData && key !== '_') {
67
+ try {
68
+ const parsed = typeof itemData === 'string' ? JSON.parse(itemData) : itemData;
69
+ caps.push(parsed);
70
+ } catch (e) {
71
+ // skip unparseable
72
+ }
73
+ }
74
+ if (received >= keys.length) {
75
+ resolve(caps);
76
+ }
77
+ });
78
+ });
79
+ });
80
+ } catch (error) {
81
+ console.error('[registry] Error getting capabilities:', error);
82
+ return [];
83
+ }
84
+ },
85
+
86
+ /**
87
+ * Remove an inbound capability
88
+ * @param {object} client - The HoloSphere instance
89
+ * @param {string} appName - Application name
90
+ * @param {string} holonId - Holon identifier
91
+ * @param {string} capabilityId - Capability ID to remove
92
+ * @returns {Promise<boolean>}
93
+ */
94
+ async removeInboundCapability(client, appName, holonId, capabilityId) {
95
+ try {
96
+ const gun = client.gun || client;
97
+ return new Promise((resolve) => {
98
+ gun.get(appName).get('_capabilities').get(holonId).get(capabilityId).put(null, (ack) => {
99
+ resolve(!ack.err);
100
+ });
101
+ });
102
+ } catch (error) {
103
+ console.error('[registry] Error removing capability:', error);
104
+ return false;
105
+ }
106
+ }
107
+ };
108
+
109
+ export default registry;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * GunDB-based subscription wrapper for HoloSphere v1.3
3
+ * Provides v2-compatible subscription API backed by Gun.js
4
+ */
5
+
6
+ /**
7
+ * Build a lens path string
8
+ * @param {string} appName - Application name
9
+ * @param {string} holonId - Holon identifier
10
+ * @param {string} lens - Lens name
11
+ * @returns {string} Combined path
12
+ */
13
+ export function buildLensPath(appName, holonId, lens) {
14
+ return `${appName}/${holonId}/${lens}`;
15
+ }
16
+
17
+ /**
18
+ * Parse a lens path into its components
19
+ * @param {string} path - The path to parse
20
+ * @returns {{ appName: string, holonId: string, lens: string } | null}
21
+ */
22
+ function parseLensPath(path) {
23
+ const parts = path.split('/');
24
+ if (parts.length < 3) return null;
25
+ return {
26
+ appName: parts[0],
27
+ holonId: parts[1],
28
+ lens: parts.slice(2).join('/')
29
+ };
30
+ }
31
+
32
+ export const subscriptions = {
33
+ /**
34
+ * Create a subscription to a GunDB path
35
+ * @param {object} client - The HoloSphere instance
36
+ * @param {string} path - The lens path (appName/holonId/lens)
37
+ * @param {function} callback - Callback for data changes
38
+ * @param {object} [options] - Subscription options
39
+ * @param {boolean} [options.realtimeOnly] - Only fire for new changes (not initial data)
40
+ * @param {boolean} [options.resolveHolograms] - Whether to resolve hologram references
41
+ * @param {string} [options.appname] - Application name override
42
+ * @returns {{ unsubscribe: () => void, stop: () => void }}
43
+ */
44
+ createSubscription(client, path, callback, options = {}) {
45
+ const parsed = parseLensPath(path);
46
+ if (!parsed) {
47
+ console.warn('[subscriptions] Invalid path:', path);
48
+ return { unsubscribe: () => {}, stop: () => {} };
49
+ }
50
+
51
+ const gun = client.gun || client;
52
+ const dataPath = gun.get(parsed.appName).get(parsed.holonId).get(parsed.lens);
53
+ let active = true;
54
+ let initialLoadComplete = false;
55
+
56
+ // Track initial data load for realtimeOnly mode
57
+ if (options.realtimeOnly) {
58
+ // First do a .once() pass to mark initial data as loaded
59
+ dataPath.once(() => {
60
+ setTimeout(() => { initialLoadComplete = true; }, 100);
61
+ });
62
+ } else {
63
+ initialLoadComplete = true;
64
+ }
65
+
66
+ const handler = dataPath.map().on(async (data, key) => {
67
+ if (!active) return;
68
+ if (options.realtimeOnly && !initialLoadComplete) return;
69
+ if (!data || key === '_') return;
70
+
71
+ try {
72
+ let parsed;
73
+ if (typeof data === 'string') {
74
+ try {
75
+ parsed = JSON.parse(data);
76
+ } catch (e) {
77
+ parsed = data;
78
+ }
79
+ } else {
80
+ // Clean Gun metadata
81
+ const cleaned = { ...data };
82
+ delete cleaned._;
83
+ parsed = cleaned;
84
+ }
85
+
86
+ // Resolve holograms if requested and client supports it
87
+ if (options.resolveHolograms && client.isHologram && client.isHologram(parsed)) {
88
+ try {
89
+ const resolved = await client.resolveHologram(parsed, { followHolograms: true });
90
+ if (resolved && resolved !== parsed) {
91
+ callback(resolved, key);
92
+ return;
93
+ }
94
+ } catch (e) {
95
+ console.warn('[subscriptions] Failed to resolve hologram:', e);
96
+ }
97
+ }
98
+
99
+ callback(parsed, key);
100
+ } catch (e) {
101
+ console.warn('[subscriptions] Error processing subscription data:', e);
102
+ }
103
+ });
104
+
105
+ const unsub = () => {
106
+ active = false;
107
+ dataPath.off();
108
+ };
109
+
110
+ return {
111
+ unsubscribe: unsub,
112
+ stop: unsub
113
+ };
114
+ }
115
+ };
116
+
117
+ export default { subscriptions, buildLensPath };