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.
- package/content.js +79 -62
- package/federation.js +201 -305
- package/global.js +119 -49
- package/handshake-shim.js +321 -0
- package/hologram.js +38 -14
- package/holosphere-bundle.esm.js +5689 -9042
- package/holosphere-bundle.js +5686 -9043
- package/holosphere-bundle.min.js +28 -30
- package/holosphere.d.ts +197 -474
- package/holosphere.js +260 -406
- package/nostr-utils-shim.js +125 -0
- package/package.json +12 -3
- package/registry-shim.js +109 -0
- package/subscriptions-shim.js +117 -0
|
@@ -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.
|
|
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
|
}
|
package/registry-shim.js
ADDED
|
@@ -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 };
|