holosphere 1.1.20 → 2.0.0-alpha1

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 (147) hide show
  1. package/.env.example +36 -0
  2. package/.eslintrc.json +16 -0
  3. package/.prettierrc.json +7 -0
  4. package/LICENSE +162 -38
  5. package/README.md +483 -367
  6. package/bin/holosphere-activitypub.js +158 -0
  7. package/cleanup-test-data.js +204 -0
  8. package/examples/demo.html +1333 -0
  9. package/examples/example-bot.js +197 -0
  10. package/package.json +47 -87
  11. package/scripts/check-bundle-size.js +54 -0
  12. package/scripts/check-quest-ids.js +77 -0
  13. package/scripts/import-holons.js +578 -0
  14. package/scripts/publish-to-relay.js +101 -0
  15. package/scripts/read-example.js +186 -0
  16. package/scripts/relay-diagnostic.js +59 -0
  17. package/scripts/relay-example.js +179 -0
  18. package/scripts/resync-to-relay.js +245 -0
  19. package/scripts/revert-import.js +196 -0
  20. package/scripts/test-hybrid-mode.js +108 -0
  21. package/scripts/test-local-storage.js +63 -0
  22. package/scripts/test-nostr-direct.js +55 -0
  23. package/scripts/test-read-data.js +45 -0
  24. package/scripts/test-write-read.js +63 -0
  25. package/scripts/verify-import.js +95 -0
  26. package/scripts/verify-relay-data.js +139 -0
  27. package/src/ai/aggregation.js +319 -0
  28. package/src/ai/breakdown.js +511 -0
  29. package/src/ai/classifier.js +217 -0
  30. package/src/ai/council.js +228 -0
  31. package/src/ai/embeddings.js +279 -0
  32. package/src/ai/federation-ai.js +324 -0
  33. package/src/ai/h3-ai.js +955 -0
  34. package/src/ai/index.js +112 -0
  35. package/src/ai/json-ops.js +225 -0
  36. package/src/ai/llm-service.js +205 -0
  37. package/src/ai/nl-query.js +223 -0
  38. package/src/ai/relationships.js +353 -0
  39. package/src/ai/schema-extractor.js +218 -0
  40. package/src/ai/spatial.js +293 -0
  41. package/src/ai/tts.js +194 -0
  42. package/src/content/social-protocols.js +168 -0
  43. package/src/core/holosphere.js +273 -0
  44. package/src/crypto/secp256k1.js +259 -0
  45. package/src/federation/discovery.js +334 -0
  46. package/src/federation/hologram.js +1042 -0
  47. package/src/federation/registry.js +386 -0
  48. package/src/hierarchical/upcast.js +110 -0
  49. package/src/index.js +2669 -0
  50. package/src/schema/validator.js +91 -0
  51. package/src/spatial/h3-operations.js +110 -0
  52. package/src/storage/backend-factory.js +125 -0
  53. package/src/storage/backend-interface.js +142 -0
  54. package/src/storage/backends/activitypub/server.js +653 -0
  55. package/src/storage/backends/activitypub-backend.js +272 -0
  56. package/src/storage/backends/gundb-backend.js +233 -0
  57. package/src/storage/backends/nostr-backend.js +136 -0
  58. package/src/storage/filesystem-storage-browser.js +41 -0
  59. package/src/storage/filesystem-storage.js +138 -0
  60. package/src/storage/global-tables.js +81 -0
  61. package/src/storage/gun-async.js +281 -0
  62. package/src/storage/gun-wrapper.js +221 -0
  63. package/src/storage/indexeddb-storage.js +122 -0
  64. package/src/storage/key-storage-simple.js +76 -0
  65. package/src/storage/key-storage.js +136 -0
  66. package/src/storage/memory-storage.js +59 -0
  67. package/src/storage/migration.js +338 -0
  68. package/src/storage/nostr-async.js +811 -0
  69. package/src/storage/nostr-client.js +939 -0
  70. package/src/storage/nostr-wrapper.js +211 -0
  71. package/src/storage/outbox-queue.js +208 -0
  72. package/src/storage/persistent-storage.js +109 -0
  73. package/src/storage/sync-service.js +164 -0
  74. package/src/subscriptions/manager.js +142 -0
  75. package/test-ai-real-api.js +202 -0
  76. package/tests/unit/ai/aggregation.test.js +295 -0
  77. package/tests/unit/ai/breakdown.test.js +446 -0
  78. package/tests/unit/ai/classifier.test.js +294 -0
  79. package/tests/unit/ai/council.test.js +262 -0
  80. package/tests/unit/ai/embeddings.test.js +384 -0
  81. package/tests/unit/ai/federation-ai.test.js +344 -0
  82. package/tests/unit/ai/h3-ai.test.js +458 -0
  83. package/tests/unit/ai/index.test.js +304 -0
  84. package/tests/unit/ai/json-ops.test.js +307 -0
  85. package/tests/unit/ai/llm-service.test.js +390 -0
  86. package/tests/unit/ai/nl-query.test.js +383 -0
  87. package/tests/unit/ai/relationships.test.js +311 -0
  88. package/tests/unit/ai/schema-extractor.test.js +384 -0
  89. package/tests/unit/ai/spatial.test.js +279 -0
  90. package/tests/unit/ai/tts.test.js +279 -0
  91. package/tests/unit/content.test.js +332 -0
  92. package/tests/unit/contract/core.test.js +88 -0
  93. package/tests/unit/contract/crypto.test.js +198 -0
  94. package/tests/unit/contract/data.test.js +223 -0
  95. package/tests/unit/contract/federation.test.js +181 -0
  96. package/tests/unit/contract/hierarchical.test.js +113 -0
  97. package/tests/unit/contract/schema.test.js +114 -0
  98. package/tests/unit/contract/social.test.js +217 -0
  99. package/tests/unit/contract/spatial.test.js +110 -0
  100. package/tests/unit/contract/subscriptions.test.js +128 -0
  101. package/tests/unit/contract/utils.test.js +159 -0
  102. package/tests/unit/core.test.js +152 -0
  103. package/tests/unit/crypto.test.js +328 -0
  104. package/tests/unit/federation.test.js +234 -0
  105. package/tests/unit/gun-async.test.js +252 -0
  106. package/tests/unit/hierarchical.test.js +399 -0
  107. package/tests/unit/integration/scenario-01-geographic-storage.test.js +74 -0
  108. package/tests/unit/integration/scenario-02-federation.test.js +76 -0
  109. package/tests/unit/integration/scenario-03-subscriptions.test.js +102 -0
  110. package/tests/unit/integration/scenario-04-validation.test.js +129 -0
  111. package/tests/unit/integration/scenario-05-hierarchy.test.js +125 -0
  112. package/tests/unit/integration/scenario-06-social.test.js +135 -0
  113. package/tests/unit/integration/scenario-07-persistence.test.js +130 -0
  114. package/tests/unit/integration/scenario-08-authorization.test.js +161 -0
  115. package/tests/unit/integration/scenario-09-cross-dimensional.test.js +139 -0
  116. package/tests/unit/integration/scenario-10-cross-holosphere-capabilities.test.js +357 -0
  117. package/tests/unit/integration/scenario-11-cross-holosphere-federation.test.js +410 -0
  118. package/tests/unit/integration/scenario-12-capability-federated-read.test.js +719 -0
  119. package/tests/unit/performance/benchmark.test.js +85 -0
  120. package/tests/unit/schema.test.js +213 -0
  121. package/tests/unit/spatial.test.js +158 -0
  122. package/tests/unit/storage.test.js +195 -0
  123. package/tests/unit/subscriptions.test.js +328 -0
  124. package/tests/unit/test-data-permanence-debug.js +197 -0
  125. package/tests/unit/test-data-permanence.js +340 -0
  126. package/tests/unit/test-key-persistence-fixed.js +148 -0
  127. package/tests/unit/test-key-persistence.js +172 -0
  128. package/tests/unit/test-relay-permanence.js +376 -0
  129. package/tests/unit/test-second-node.js +95 -0
  130. package/tests/unit/test-simple-write.js +89 -0
  131. package/vite.config.js +49 -0
  132. package/vitest.config.js +20 -0
  133. package/FEDERATION.md +0 -213
  134. package/compute.js +0 -298
  135. package/content.js +0 -980
  136. package/federation.js +0 -1234
  137. package/global.js +0 -736
  138. package/hexlib.js +0 -335
  139. package/hologram.js +0 -183
  140. package/holosphere-bundle.esm.js +0 -33256
  141. package/holosphere-bundle.js +0 -33287
  142. package/holosphere-bundle.min.js +0 -39
  143. package/holosphere.d.ts +0 -601
  144. package/holosphere.js +0 -719
  145. package/node.js +0 -246
  146. package/schema.js +0 -139
  147. package/utils.js +0 -302
@@ -0,0 +1,1333 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HoloSphere - Interactive Demo</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
16
+ line-height: 1.6;
17
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
18
+ min-height: 100vh;
19
+ padding: 20px;
20
+ }
21
+
22
+ .container {
23
+ max-width: 1400px;
24
+ margin: 0 auto;
25
+ background: rgba(255, 255, 255, 0.95);
26
+ border-radius: 20px;
27
+ padding: 30px;
28
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
29
+ }
30
+
31
+ h1 {
32
+ color: #333;
33
+ margin-bottom: 10px;
34
+ font-size: 2.5em;
35
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
36
+ -webkit-background-clip: text;
37
+ -webkit-text-fill-color: transparent;
38
+ }
39
+
40
+ .subtitle {
41
+ color: #666;
42
+ margin-bottom: 30px;
43
+ font-size: 1.1em;
44
+ }
45
+
46
+ .tabs {
47
+ display: flex;
48
+ gap: 10px;
49
+ margin-bottom: 30px;
50
+ border-bottom: 2px solid #e0e0e0;
51
+ flex-wrap: wrap;
52
+ }
53
+
54
+ .tab {
55
+ padding: 12px 24px;
56
+ background: none;
57
+ border: none;
58
+ cursor: pointer;
59
+ color: #666;
60
+ font-size: 1em;
61
+ transition: all 0.3s;
62
+ border-bottom: 3px solid transparent;
63
+ margin-bottom: -2px;
64
+ }
65
+
66
+ .tab:hover {
67
+ color: #667eea;
68
+ }
69
+
70
+ .tab.active {
71
+ color: #667eea;
72
+ border-bottom-color: #667eea;
73
+ font-weight: 600;
74
+ }
75
+
76
+ .tab-content {
77
+ display: none;
78
+ animation: fadeIn 0.5s;
79
+ }
80
+
81
+ .tab-content.active {
82
+ display: block;
83
+ }
84
+
85
+ @keyframes fadeIn {
86
+ from { opacity: 0; transform: translateY(10px); }
87
+ to { opacity: 1; transform: translateY(0); }
88
+ }
89
+
90
+ .demo-section {
91
+ background: #f8f9fa;
92
+ padding: 20px;
93
+ border-radius: 10px;
94
+ margin-bottom: 20px;
95
+ }
96
+
97
+ .demo-section h3 {
98
+ color: #333;
99
+ margin-bottom: 15px;
100
+ font-size: 1.3em;
101
+ }
102
+
103
+ .demo-section h4 {
104
+ color: #555;
105
+ margin-top: 15px;
106
+ margin-bottom: 10px;
107
+ }
108
+
109
+ .input-group {
110
+ display: flex;
111
+ gap: 10px;
112
+ margin-bottom: 15px;
113
+ flex-wrap: wrap;
114
+ }
115
+
116
+ input[type="text"],
117
+ input[type="number"],
118
+ textarea,
119
+ select {
120
+ padding: 10px 15px;
121
+ border: 2px solid #e0e0e0;
122
+ border-radius: 8px;
123
+ font-size: 1em;
124
+ transition: border-color 0.3s;
125
+ flex: 1;
126
+ min-width: 200px;
127
+ }
128
+
129
+ input[type="text"]:focus,
130
+ input[type="number"]:focus,
131
+ textarea:focus,
132
+ select:focus {
133
+ outline: none;
134
+ border-color: #667eea;
135
+ }
136
+
137
+ textarea {
138
+ min-height: 100px;
139
+ resize: vertical;
140
+ font-family: 'Monaco', 'Menlo', monospace;
141
+ font-size: 0.9em;
142
+ }
143
+
144
+ button {
145
+ padding: 10px 20px;
146
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
147
+ color: white;
148
+ border: none;
149
+ border-radius: 8px;
150
+ cursor: pointer;
151
+ font-size: 1em;
152
+ transition: transform 0.2s, box-shadow 0.2s;
153
+ }
154
+
155
+ button:hover {
156
+ transform: translateY(-2px);
157
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
158
+ }
159
+
160
+ button:active {
161
+ transform: translateY(0);
162
+ }
163
+
164
+ button:disabled {
165
+ opacity: 0.5;
166
+ cursor: not-allowed;
167
+ }
168
+
169
+ .output {
170
+ background: #1e1e1e;
171
+ color: #fff;
172
+ padding: 15px;
173
+ border-radius: 8px;
174
+ margin-top: 15px;
175
+ font-family: 'Monaco', 'Menlo', monospace;
176
+ font-size: 0.9em;
177
+ min-height: 100px;
178
+ max-height: 400px;
179
+ overflow-y: auto;
180
+ }
181
+
182
+ .output pre {
183
+ margin: 0;
184
+ white-space: pre-wrap;
185
+ word-wrap: break-word;
186
+ }
187
+
188
+ .status {
189
+ padding: 8px 15px;
190
+ border-radius: 5px;
191
+ margin-top: 10px;
192
+ font-weight: 500;
193
+ }
194
+
195
+ .status.success {
196
+ background: #d4edda;
197
+ color: #155724;
198
+ border: 1px solid #c3e6cb;
199
+ }
200
+
201
+ .status.error {
202
+ background: #f8d7da;
203
+ color: #721c24;
204
+ border: 1px solid #f5c6cb;
205
+ }
206
+
207
+ .status.info {
208
+ background: #d1ecf1;
209
+ color: #0c5460;
210
+ border: 1px solid #bee5eb;
211
+ }
212
+
213
+ .feature-grid {
214
+ display: grid;
215
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
216
+ gap: 20px;
217
+ margin-top: 20px;
218
+ }
219
+
220
+ .feature-card {
221
+ background: white;
222
+ padding: 20px;
223
+ border-radius: 10px;
224
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
225
+ }
226
+
227
+ .feature-card h4 {
228
+ color: #667eea;
229
+ margin-bottom: 10px;
230
+ }
231
+
232
+ .code-example {
233
+ background: #2d2d2d;
234
+ color: #f8f8f2;
235
+ padding: 15px;
236
+ border-radius: 8px;
237
+ margin: 10px 0;
238
+ overflow-x: auto;
239
+ }
240
+
241
+ .code-example code {
242
+ font-family: 'Monaco', 'Menlo', monospace;
243
+ font-size: 0.9em;
244
+ }
245
+
246
+ .metrics-grid {
247
+ display: grid;
248
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
249
+ gap: 15px;
250
+ margin-top: 20px;
251
+ }
252
+
253
+ .metric-card {
254
+ background: white;
255
+ padding: 15px;
256
+ border-radius: 8px;
257
+ text-align: center;
258
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
259
+ }
260
+
261
+ .metric-value {
262
+ font-size: 2em;
263
+ font-weight: bold;
264
+ color: #667eea;
265
+ }
266
+
267
+ .metric-label {
268
+ color: #666;
269
+ font-size: 0.9em;
270
+ margin-top: 5px;
271
+ }
272
+
273
+ .map-container {
274
+ height: 400px;
275
+ border-radius: 8px;
276
+ overflow: hidden;
277
+ margin-top: 15px;
278
+ background: #e0e0e0;
279
+ position: relative;
280
+ }
281
+
282
+ #map {
283
+ width: 100%;
284
+ height: 100%;
285
+ }
286
+
287
+ .connection-status {
288
+ display: inline-block;
289
+ width: 10px;
290
+ height: 10px;
291
+ border-radius: 50%;
292
+ margin-right: 5px;
293
+ }
294
+
295
+ .connection-status.connected {
296
+ background: #28a745;
297
+ animation: pulse 2s infinite;
298
+ }
299
+
300
+ .connection-status.disconnected {
301
+ background: #dc3545;
302
+ }
303
+
304
+ @keyframes pulse {
305
+ 0% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.7); }
306
+ 70% { box-shadow: 0 0 0 10px rgba(40, 167, 69, 0); }
307
+ 100% { box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); }
308
+ }
309
+
310
+ .loader {
311
+ border: 3px solid #f3f3f3;
312
+ border-top: 3px solid #667eea;
313
+ border-radius: 50%;
314
+ width: 30px;
315
+ height: 30px;
316
+ animation: spin 1s linear infinite;
317
+ display: inline-block;
318
+ margin: 20px auto;
319
+ }
320
+
321
+ @keyframes spin {
322
+ 0% { transform: rotate(0deg); }
323
+ 100% { transform: rotate(360deg); }
324
+ }
325
+ </style>
326
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
327
+ </head>
328
+ <body>
329
+ <div class="container">
330
+ <h1>HoloSphere Interactive Demo</h1>
331
+ <p class="subtitle">Holonic Geospatial Communication Infrastructure - Explore all features interactively</p>
332
+
333
+ <div class="connection-status" id="connectionStatus"></div>
334
+ <span id="connectionText">Initializing...</span>
335
+
336
+ <div style="margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 10px; display: flex; align-items: center; gap: 15px;">
337
+ <label style="display: flex; align-items: center; gap: 10px; cursor: pointer; font-weight: 500;">
338
+ <input type="checkbox" id="useRealRelays" style="width: 20px; height: 20px; cursor: pointer;">
339
+ <span>Use Real Nostr Relays (Publish to actual network)</span>
340
+ </label>
341
+ <button onclick="toggleRelayMode()" style="padding: 8px 16px;">Apply & Reconnect</button>
342
+ <span id="relayModeStatus" style="margin-left: auto; font-size: 0.9em; color: #666;"></span>
343
+ </div>
344
+
345
+ <div class="tabs">
346
+ <button class="tab active" onclick="showTab('overview')">Overview</button>
347
+ <button class="tab" onclick="showTab('spatial')">Spatial Operations</button>
348
+ <button class="tab" onclick="showTab('data')">Data Operations</button>
349
+ <button class="tab" onclick="showTab('schema')">Schema Validation</button>
350
+ <button class="tab" onclick="showTab('federation')">Federation</button>
351
+ <button class="tab" onclick="showTab('social')">Social Protocols</button>
352
+ <button class="tab" onclick="showTab('crypto')">Cryptography</button>
353
+ <button class="tab" onclick="showTab('subscriptions')">Subscriptions</button>
354
+ <button class="tab" onclick="showTab('metrics')">Metrics</button>
355
+ </div>
356
+
357
+ <!-- Overview Tab -->
358
+ <div id="overview" class="tab-content active">
359
+ <div class="demo-section">
360
+ <h3>Welcome to HoloSphere</h3>
361
+ <p>HoloSphere is a powerful holonic geospatial communication infrastructure that combines H3 hexagonal indexing with distributed P2P storage.</p>
362
+
363
+ <div class="feature-grid">
364
+ <div class="feature-card">
365
+ <h4>🗺️ Geospatial Indexing</h4>
366
+ <p>Uses H3 hexagonal grid system for efficient spatial data organization and querying.</p>
367
+ </div>
368
+ <div class="feature-card">
369
+ <h4>🔗 Federation</h4>
370
+ <p>Connect and synchronize data across different holons with flexible federation modes.</p>
371
+ </div>
372
+ <div class="feature-card">
373
+ <h4>📱 Social Protocols</h4>
374
+ <p>Native support for Nostr and ActivityPub social protocols.</p>
375
+ </div>
376
+ <div class="feature-card">
377
+ <h4>🔐 Cryptography</h4>
378
+ <p>Built-in secp256k1 cryptography for signing and verification.</p>
379
+ </div>
380
+ <div class="feature-card">
381
+ <h4>✅ Schema Validation</h4>
382
+ <p>JSON Schema validation for ensuring data integrity.</p>
383
+ </div>
384
+ <div class="feature-card">
385
+ <h4>📊 Real-time Updates</h4>
386
+ <p>Subscribe to data changes with real-time notifications.</p>
387
+ </div>
388
+ </div>
389
+
390
+ <h4>Quick Start Example</h4>
391
+ <div class="code-example">
392
+ <code>// Initialize HoloSphere
393
+ const hs = new HoloSphere({
394
+ appName: 'my-app',
395
+ relays: ['wss://relay.example.com']
396
+ });
397
+
398
+ // Convert coordinates to H3 holon
399
+ const holon = await hs.toHolon(37.7749, -122.4194, 9);
400
+
401
+ // Write data to a holon (multiple ways!)
402
+ await hs.write(holon, 'events', { title: 'SF Meetup' });
403
+ await hs.put(holon, 'events', { title: 'SF Meetup' }); // REST-style
404
+ await hs.save(holon, 'events', { title: 'SF Meetup' }); // User-friendly
405
+
406
+ // Read data from holon (multiple ways!)
407
+ const events = await hs.read(holon, 'events');
408
+ const events = await hs.get(holon, 'events'); // REST-style
409
+ const events = await hs.load(holon, 'events'); // User-friendly</code>
410
+ </div>
411
+
412
+ <div class="status info" style="margin-top: 15px;">
413
+ <strong>💡 API Aliases Available!</strong><br>
414
+ Use <code>put()/get()</code> for REST-style, <code>save()/load()</code> for user-friendly,
415
+ or <code>write()/read()</code> for database-style. They all work the same way!
416
+ <br>See <a href="API_ALIASES.md" target="_blank">API_ALIASES.md</a> for full details.
417
+ </div>
418
+ </div>
419
+ </div>
420
+
421
+ <!-- Spatial Operations Tab -->
422
+ <div id="spatial" class="tab-content">
423
+ <div class="demo-section">
424
+ <h3>Spatial Operations with H3</h3>
425
+ <p>Convert geographic coordinates to H3 hexagons and explore their hierarchy.</p>
426
+
427
+ <h4>Convert Coordinates to Holon</h4>
428
+ <div class="input-group">
429
+ <input type="number" id="lat" placeholder="Latitude" value="37.7749" step="0.0001">
430
+ <input type="number" id="lng" placeholder="Longitude" value="-122.4194" step="0.0001">
431
+ <input type="number" id="resolution" placeholder="Resolution (0-15)" value="9" min="0" max="15">
432
+ <button onclick="convertToHolon()">Convert to Holon</button>
433
+ </div>
434
+
435
+ <div id="holonResult" class="output"></div>
436
+
437
+ <div id="map" class="map-container"></div>
438
+
439
+ <h4>Holon Hierarchy</h4>
440
+ <div class="input-group">
441
+ <input type="text" id="holonId" placeholder="Holon ID (H3 index)">
442
+ <button onclick="getParents()">Get Parents</button>
443
+ <button onclick="getChildren()">Get Children</button>
444
+ </div>
445
+
446
+ <div id="hierarchyResult" class="output"></div>
447
+ </div>
448
+ </div>
449
+
450
+ <!-- Data Operations Tab -->
451
+ <div id="data" class="tab-content">
452
+ <div class="demo-section">
453
+ <h3>Data Operations</h3>
454
+ <p>Store, retrieve, update, and delete data in holons using lens-based organization.</p>
455
+
456
+ <h4>Write Data</h4>
457
+ <div class="input-group">
458
+ <input type="text" id="writeHolon" placeholder="Holon ID">
459
+ <input type="text" id="writeLens" placeholder="Lens Name" value="demo">
460
+ </div>
461
+ <textarea id="writeData" placeholder='Data (JSON format, e.g., {"title": "Test", "value": 123})'>{
462
+ "title": "Demo Event",
463
+ "description": "This is a test event",
464
+ "timestamp": null
465
+ }</textarea>
466
+ <button onclick="writeData()">Write Data</button>
467
+
468
+ <h4>Read Data</h4>
469
+ <div class="input-group">
470
+ <input type="text" id="readHolon" placeholder="Holon ID">
471
+ <input type="text" id="readLens" placeholder="Lens Name" value="demo">
472
+ <input type="text" id="readDataId" placeholder="Data ID (optional)">
473
+ <button onclick="readData()">Read Data</button>
474
+ </div>
475
+
476
+ <h4>Update Data</h4>
477
+ <div class="input-group">
478
+ <input type="text" id="updateHolon" placeholder="Holon ID">
479
+ <input type="text" id="updateLens" placeholder="Lens Name" value="demo">
480
+ <input type="text" id="updateDataId" placeholder="Data ID">
481
+ </div>
482
+ <textarea id="updateData" placeholder='Updates (JSON format)'>{
483
+ "status": "updated"
484
+ }</textarea>
485
+ <button onclick="updateData()">Update Data</button>
486
+
487
+ <h4>Delete Data</h4>
488
+ <div class="input-group">
489
+ <input type="text" id="deleteHolon" placeholder="Holon ID">
490
+ <input type="text" id="deleteLens" placeholder="Lens Name" value="demo">
491
+ <input type="text" id="deleteDataId" placeholder="Data ID">
492
+ <button onclick="deleteData()">Delete Data</button>
493
+ </div>
494
+
495
+ <div id="dataResult" class="output"></div>
496
+ </div>
497
+ </div>
498
+
499
+ <!-- Schema Validation Tab -->
500
+ <div id="schema" class="tab-content">
501
+ <div class="demo-section">
502
+ <h3>Schema Validation</h3>
503
+ <p>Define and enforce JSON schemas for data consistency.</p>
504
+
505
+ <h4>Set Schema for Lens</h4>
506
+ <div class="input-group">
507
+ <input type="text" id="schemaLens" placeholder="Lens Name" value="validated">
508
+ <label><input type="checkbox" id="strictMode"> Strict Mode</label>
509
+ </div>
510
+ <textarea id="schemaDefinition" placeholder="JSON Schema">{
511
+ "type": "object",
512
+ "properties": {
513
+ "title": { "type": "string" },
514
+ "value": { "type": "number", "minimum": 0 },
515
+ "tags": { "type": "array", "items": { "type": "string" } }
516
+ },
517
+ "required": ["title", "value"]
518
+ }</textarea>
519
+ <button onclick="setSchema()">Set Schema</button>
520
+
521
+ <h4>Test Validation</h4>
522
+ <textarea id="testData" placeholder="Test data to validate">{
523
+ "title": "Test Item",
524
+ "value": 42,
525
+ "tags": ["demo", "test"]
526
+ }</textarea>
527
+ <button onclick="testValidation()">Validate Data</button>
528
+
529
+ <div id="schemaResult" class="output"></div>
530
+ </div>
531
+ </div>
532
+
533
+ <!-- Federation Tab -->
534
+ <div id="federation" class="tab-content">
535
+ <div class="demo-section">
536
+ <h3>Federation</h3>
537
+ <p>Connect holons and synchronize data across them.</p>
538
+
539
+ <h4>Setup Federation</h4>
540
+ <div class="input-group">
541
+ <input type="text" id="sourceHolon" placeholder="Source Holon ID">
542
+ <input type="text" id="targetHolon" placeholder="Target Holon ID">
543
+ <input type="text" id="federationLens" placeholder="Lens Name" value="federated">
544
+ </div>
545
+ <div class="input-group">
546
+ <select id="federationDirection">
547
+ <option value="bidirectional">Bidirectional</option>
548
+ <option value="outbound">Outbound Only</option>
549
+ <option value="inbound">Inbound Only</option>
550
+ </select>
551
+ <select id="federationMode">
552
+ <option value="reference">Reference (Hologram)</option>
553
+ <option value="copy">Copy</option>
554
+ </select>
555
+ <button onclick="setupFederation()">Setup Federation</button>
556
+ </div>
557
+
558
+ <h4>Get Federated Data</h4>
559
+ <div class="input-group">
560
+ <input type="text" id="fedHolon" placeholder="Holon ID">
561
+ <input type="text" id="fedLens" placeholder="Lens Name" value="federated">
562
+ <label><input type="checkbox" id="resolveHolograms" checked> Resolve Holograms</label>
563
+ <button onclick="getFederatedData()">Get Federated Data</button>
564
+ </div>
565
+
566
+ <div id="federationResult" class="output"></div>
567
+ </div>
568
+ </div>
569
+
570
+ <!-- Social Protocols Tab -->
571
+ <div id="social" class="tab-content">
572
+ <div class="demo-section">
573
+ <h3>Social Protocols</h3>
574
+ <p>Publish and query content using Nostr and ActivityPub protocols.</p>
575
+
576
+ <div id="socialProtocolsBanner" class="status info">
577
+ <strong>🌐 Connected to Real Nostr Relays!</strong><br>
578
+ Your Nostr events will be published to the real Nostr network and can be seen by others using Nostr clients.
579
+ </div>
580
+
581
+ <h4>Publish Nostr Event</h4>
582
+ <div class="input-group">
583
+ <input type="text" id="nostrHolon" placeholder="Holon ID">
584
+ </div>
585
+ <textarea id="nostrEvent" placeholder="Nostr event (JSON)">{
586
+ "kind": 1,
587
+ "created_at": null,
588
+ "content": "Hello from HoloSphere!",
589
+ "tags": []
590
+ }</textarea>
591
+ <button onclick="publishNostr()">Publish Nostr Event</button>
592
+
593
+ <h4>Publish ActivityPub Object</h4>
594
+ <div class="input-group">
595
+ <input type="text" id="apHolon" placeholder="Holon ID">
596
+ </div>
597
+ <textarea id="apObject" placeholder="ActivityPub object (JSON)">{
598
+ "@context": "https://www.w3.org/ns/activitystreams",
599
+ "type": "Note",
600
+ "content": "Hello from the Fediverse!",
601
+ "to": ["https://www.w3.org/ns/activitystreams#Public"]
602
+ }</textarea>
603
+ <button onclick="publishActivityPub()">Publish ActivityPub</button>
604
+
605
+ <h4>Query Social Data</h4>
606
+ <div class="input-group">
607
+ <input type="text" id="socialHolon" placeholder="Holon ID">
608
+ <select id="socialProtocol">
609
+ <option value="">All Protocols</option>
610
+ <option value="nostr">Nostr Only</option>
611
+ <option value="activitypub">ActivityPub Only</option>
612
+ </select>
613
+ <button onclick="querySocial()">Query Social Data</button>
614
+ </div>
615
+
616
+ <div id="socialResult" class="output"></div>
617
+ </div>
618
+ </div>
619
+
620
+ <!-- Cryptography Tab -->
621
+ <div id="crypto" class="tab-content">
622
+ <div class="demo-section">
623
+ <h3>Cryptography</h3>
624
+ <p>Sign, verify, and manage capability tokens using secp256k1.</p>
625
+
626
+ <h4>Generate Keys</h4>
627
+ <button onclick="generateKeys()">Generate New Key Pair</button>
628
+ <div id="keysResult" class="output"></div>
629
+
630
+ <h4>Sign & Verify</h4>
631
+ <textarea id="signContent" placeholder="Content to sign">Hello, this is a test message!</textarea>
632
+ <div class="input-group">
633
+ <input type="text" id="privateKey" placeholder="Private Key (hex)">
634
+ <button onclick="signContent()">Sign Content</button>
635
+ </div>
636
+
637
+ <div class="input-group">
638
+ <input type="text" id="signature" placeholder="Signature">
639
+ <input type="text" id="publicKey" placeholder="Public Key">
640
+ <button onclick="verifySignature()">Verify Signature</button>
641
+ </div>
642
+
643
+ <h4>Capability Tokens</h4>
644
+ <div class="input-group">
645
+ <input type="text" id="capPermissions" placeholder="Permissions (comma-separated)" value="read,write">
646
+ <input type="text" id="capScope" placeholder="Scope (JSON)" value='{"holonId": "*", "lensName": "demo"}'>
647
+ <input type="text" id="capRecipient" placeholder="Recipient Public Key">
648
+ <button onclick="issueCapability()">Issue Capability</button>
649
+ </div>
650
+
651
+ <div id="cryptoResult" class="output"></div>
652
+ </div>
653
+ </div>
654
+
655
+ <!-- Subscriptions Tab -->
656
+ <div id="subscriptions" class="tab-content">
657
+ <div class="demo-section">
658
+ <h3>Real-time Subscriptions</h3>
659
+ <p>Subscribe to data changes and receive real-time updates.</p>
660
+
661
+ <h4>Create Subscription</h4>
662
+ <div class="input-group">
663
+ <input type="text" id="subHolon" placeholder="Holon ID">
664
+ <input type="text" id="subLens" placeholder="Lens Name" value="realtime">
665
+ <button onclick="createSubscription()">Subscribe</button>
666
+ <button onclick="unsubscribe()" disabled id="unsubBtn">Unsubscribe</button>
667
+ </div>
668
+
669
+ <h4>Test Real-time Updates</h4>
670
+ <p>After subscribing, write data to see real-time updates:</p>
671
+ <div class="input-group">
672
+ <input type="text" id="testMessage" placeholder="Test message" value="Real-time test">
673
+ <button onclick="sendTestMessage()">Send Test Message</button>
674
+ </div>
675
+
676
+ <h4>Subscription Events</h4>
677
+ <div id="subscriptionEvents" class="output"></div>
678
+ </div>
679
+ </div>
680
+
681
+ <!-- Metrics Tab -->
682
+ <div id="metrics" class="tab-content">
683
+ <div class="demo-section">
684
+ <h3>Performance Metrics</h3>
685
+ <p>Monitor HoloSphere performance and usage statistics.</p>
686
+
687
+ <button onclick="refreshMetrics()">Refresh Metrics</button>
688
+
689
+ <div class="metrics-grid" id="metricsGrid">
690
+ <div class="metric-card">
691
+ <div class="metric-value" id="metricReads">0</div>
692
+ <div class="metric-label">Total Reads</div>
693
+ </div>
694
+ <div class="metric-card">
695
+ <div class="metric-value" id="metricWrites">0</div>
696
+ <div class="metric-label">Total Writes</div>
697
+ </div>
698
+ <div class="metric-card">
699
+ <div class="metric-value" id="metricSubs">0</div>
700
+ <div class="metric-label">Active Subscriptions</div>
701
+ </div>
702
+ <div class="metric-card">
703
+ <div class="metric-value" id="metricFeds">0</div>
704
+ <div class="metric-label">Federations</div>
705
+ </div>
706
+ <div class="metric-card">
707
+ <div class="metric-value" id="metricAvgRead">0</div>
708
+ <div class="metric-label">Avg Read Time (ms)</div>
709
+ </div>
710
+ <div class="metric-card">
711
+ <div class="metric-value" id="metricAvgWrite">0</div>
712
+ <div class="metric-label">Avg Write Time (ms)</div>
713
+ </div>
714
+ </div>
715
+
716
+ <h4>Raw Metrics Data</h4>
717
+ <div id="metricsResult" class="output"></div>
718
+ </div>
719
+ </div>
720
+ </div>
721
+
722
+ <script type="module">
723
+ // Import HoloSphere from the built ES module
724
+ import { HoloSphere } from './dist/esm/holosphere.js';
725
+
726
+ // Make it globally available for the demo functions
727
+ window.HoloSphere = HoloSphere;
728
+
729
+ // Initialize HoloSphere instance
730
+ let hs;
731
+ let currentSubscription = null;
732
+ let map = null;
733
+ let currentHexLayer = null;
734
+
735
+ // Real relay configuration
736
+ const REAL_RELAYS = [
737
+ 'wss://relay.holons.io',
738
+ 'wss://nos.lol',
739
+ 'wss://relay.nostr.band',
740
+ 'wss://nostr.wine',
741
+ 'wss://relay.snort.social'
742
+ ];
743
+
744
+ async function initHoloSphere() {
745
+ try {
746
+ // Check if user wants real relays
747
+ const useRealRelays = document.getElementById('useRealRelays').checked;
748
+ const relays = useRealRelays ? REAL_RELAYS : [];
749
+
750
+ // Initialize with demo configuration
751
+ hs = new HoloSphere({
752
+ appName: 'holosphere-demo',
753
+ relays: relays
754
+ });
755
+
756
+ window.hs = hs; // Make available globally for debugging
757
+
758
+ // Update connection status
759
+ document.getElementById('connectionStatus').className = 'connection-status connected';
760
+
761
+ if (useRealRelays) {
762
+ document.getElementById('connectionText').textContent = 'HoloSphere Initialized (Connected to 5 Nostr relays)';
763
+ document.getElementById('relayModeStatus').innerHTML = '🌐 <strong>Real Network Mode</strong>';
764
+ console.log('Connected to Nostr relays:', hs.config.relays);
765
+ } else {
766
+ document.getElementById('connectionText').textContent = 'HoloSphere Initialized (Local Storage Only)';
767
+ document.getElementById('relayModeStatus').innerHTML = '💾 <strong>Local Only Mode</strong>';
768
+ console.log('Using local storage only');
769
+ }
770
+
771
+ console.log('HoloSphere initialized successfully');
772
+
773
+ // Initialize the map
774
+ initMap();
775
+
776
+ // Set some demo data
777
+ await setupDemoData();
778
+
779
+ } catch (error) {
780
+ console.error('Failed to initialize HoloSphere:', error);
781
+ document.getElementById('connectionStatus').className = 'connection-status disconnected';
782
+ document.getElementById('connectionText').textContent = 'Initialization Failed';
783
+ }
784
+ }
785
+
786
+ async function setupDemoData() {
787
+ try {
788
+ // Convert San Francisco coordinates to holon
789
+ const sfHolon = await hs.toHolon(37.7749, -122.4194, 9);
790
+
791
+ // Write some demo data
792
+ await hs.write(sfHolon, 'demo', {
793
+ title: 'San Francisco Demo',
794
+ description: 'Welcome to HoloSphere!',
795
+ timestamp: Date.now()
796
+ });
797
+
798
+ console.log('Demo data setup complete');
799
+ } catch (error) {
800
+ console.error('Error setting up demo data:', error);
801
+ }
802
+ }
803
+
804
+ // Toggle relay mode
805
+ window.toggleRelayMode = async function() {
806
+ const checkbox = document.getElementById('useRealRelays');
807
+
808
+ // Save preference to localStorage
809
+ localStorage.setItem('holosphere-use-real-relays', checkbox.checked);
810
+
811
+ // Reinitialize HoloSphere
812
+ await initHoloSphere();
813
+
814
+ // Update social protocols info banner
815
+ updateSocialProtocolsBanner();
816
+
817
+ alert(`Switched to ${checkbox.checked ? 'Real Network' : 'Local Only'} mode. HoloSphere has been reinitialized.`);
818
+ };
819
+
820
+ // Update social protocols banner based on relay mode
821
+ function updateSocialProtocolsBanner() {
822
+ const useRealRelays = document.getElementById('useRealRelays').checked;
823
+ const banner = document.getElementById('socialProtocolsBanner');
824
+
825
+ if (useRealRelays) {
826
+ banner.className = 'status info';
827
+ banner.innerHTML = `<strong>🌐 Connected to Real Nostr Relays!</strong><br>
828
+ Your Nostr events will be published to the real Nostr network and can be seen by others using Nostr clients.
829
+ `;
830
+ } else {
831
+ banner.className = 'status success';
832
+ banner.innerHTML = `<strong>💾 Local Storage Mode</strong><br>
833
+ Your Nostr events are stored locally only and will NOT be published to the Nostr network.
834
+ Perfect for testing without broadcasting to the real network.`;
835
+ }
836
+ }
837
+
838
+ // Load saved preference on page load
839
+ function loadRelayPreference() {
840
+ const saved = localStorage.getItem('holosphere-use-real-relays');
841
+ const checkbox = document.getElementById('useRealRelays');
842
+
843
+ // Default to local only (unchecked) for safety
844
+ checkbox.checked = saved === 'true';
845
+ }
846
+
847
+ // Tab switching
848
+ window.showTab = function(tabName) {
849
+ // Hide all tabs
850
+ document.querySelectorAll('.tab-content').forEach(tab => {
851
+ tab.classList.remove('active');
852
+ });
853
+
854
+ // Remove active class from all tab buttons
855
+ document.querySelectorAll('.tab').forEach(button => {
856
+ button.classList.remove('active');
857
+ });
858
+
859
+ // Show selected tab
860
+ document.getElementById(tabName).classList.add('active');
861
+
862
+ // Add active class to clicked button
863
+ event.target.classList.add('active');
864
+ };
865
+
866
+ // Spatial Operations
867
+ window.convertToHolon = async function() {
868
+ try {
869
+ const lat = parseFloat(document.getElementById('lat').value);
870
+ const lng = parseFloat(document.getElementById('lng').value);
871
+ const resolution = parseInt(document.getElementById('resolution').value);
872
+
873
+ const holon = await hs.toHolon(lat, lng, resolution);
874
+
875
+ const output = document.getElementById('holonResult');
876
+ output.innerHTML = `<pre>Holon ID: ${holon}
877
+ Resolution: ${resolution}
878
+ Coordinates: ${lat}, ${lng}
879
+
880
+ Valid H3 Index: ${hs.isValidH3(holon)}</pre>`;
881
+
882
+ // Update the holon input field for hierarchy operations
883
+ document.getElementById('holonId').value = holon;
884
+
885
+ // Show on map
886
+ if (map) {
887
+ showHexagonOnMap(holon, lat, lng);
888
+ }
889
+
890
+ } catch (error) {
891
+ showError('holonResult', error);
892
+ }
893
+ };
894
+
895
+ window.getParents = async function() {
896
+ try {
897
+ const holonId = document.getElementById('holonId').value;
898
+ const parents = await hs.getParents(holonId, 0);
899
+
900
+ const output = document.getElementById('hierarchyResult');
901
+ output.innerHTML = `<pre>Parents of ${holonId}:\n${JSON.stringify(parents, null, 2)}</pre>`;
902
+ } catch (error) {
903
+ showError('hierarchyResult', error);
904
+ }
905
+ };
906
+
907
+ window.getChildren = async function() {
908
+ try {
909
+ const holonId = document.getElementById('holonId').value;
910
+ const children = await hs.getChildren(holonId);
911
+
912
+ const output = document.getElementById('hierarchyResult');
913
+ output.innerHTML = `<pre>Children of ${holonId}:\n${JSON.stringify(children, null, 2)}</pre>`;
914
+ } catch (error) {
915
+ showError('hierarchyResult', error);
916
+ }
917
+ };
918
+
919
+ // Data Operations
920
+ window.writeData = async function() {
921
+ try {
922
+ const holon = document.getElementById('writeHolon').value;
923
+ const lens = document.getElementById('writeLens').value;
924
+ const data = JSON.parse(document.getElementById('writeData').value);
925
+
926
+ // Add timestamp if null
927
+ if (data.timestamp === null) {
928
+ data.timestamp = Date.now();
929
+ }
930
+
931
+ const result = await hs.write(holon, lens, data);
932
+
933
+ const output = document.getElementById('dataResult');
934
+ output.innerHTML = `<pre>Write successful: ${result}
935
+ Data ID: ${data.id}
936
+ Timestamp: ${new Date(data._meta?.timestamp || data.timestamp).toISOString()}</pre>`;
937
+ } catch (error) {
938
+ showError('dataResult', error);
939
+ }
940
+ };
941
+
942
+ window.readData = async function() {
943
+ try {
944
+ const holon = document.getElementById('readHolon').value;
945
+ const lens = document.getElementById('readLens').value;
946
+ const dataId = document.getElementById('readDataId').value || null;
947
+
948
+ const data = await hs.read(holon, lens, dataId);
949
+
950
+ const output = document.getElementById('dataResult');
951
+ output.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
952
+ } catch (error) {
953
+ showError('dataResult', error);
954
+ }
955
+ };
956
+
957
+ window.updateData = async function() {
958
+ try {
959
+ const holon = document.getElementById('updateHolon').value;
960
+ const lens = document.getElementById('updateLens').value;
961
+ const dataId = document.getElementById('updateDataId').value;
962
+ const updates = JSON.parse(document.getElementById('updateData').value);
963
+
964
+ const result = await hs.update(holon, lens, dataId, updates);
965
+
966
+ const output = document.getElementById('dataResult');
967
+ output.innerHTML = `<pre>Update successful: ${result}</pre>`;
968
+ } catch (error) {
969
+ showError('dataResult', error);
970
+ }
971
+ };
972
+
973
+ window.deleteData = async function() {
974
+ try {
975
+ const holon = document.getElementById('deleteHolon').value;
976
+ const lens = document.getElementById('deleteLens').value;
977
+ const dataId = document.getElementById('deleteDataId').value;
978
+
979
+ const result = await hs.delete(holon, lens, dataId);
980
+
981
+ const output = document.getElementById('dataResult');
982
+ output.innerHTML = `<pre>Delete successful: ${result}</pre>`;
983
+ } catch (error) {
984
+ showError('dataResult', error);
985
+ }
986
+ };
987
+
988
+ // Schema Operations
989
+ window.setSchema = async function() {
990
+ try {
991
+ const lens = document.getElementById('schemaLens').value;
992
+ const schema = JSON.parse(document.getElementById('schemaDefinition').value);
993
+ const strict = document.getElementById('strictMode').checked;
994
+
995
+ await hs.setSchema(lens, schema, strict);
996
+
997
+ const output = document.getElementById('schemaResult');
998
+ output.innerHTML = `<pre>Schema set successfully for lens: ${lens}
999
+ Strict mode: ${strict}</pre>`;
1000
+ } catch (error) {
1001
+ showError('schemaResult', error);
1002
+ }
1003
+ };
1004
+
1005
+ window.testValidation = async function() {
1006
+ try {
1007
+ const lens = document.getElementById('schemaLens').value;
1008
+ const data = JSON.parse(document.getElementById('testData').value);
1009
+
1010
+ // Try to write with schema validation
1011
+ const holon = await hs.toHolon(37.7749, -122.4194, 9); // Demo holon
1012
+ const result = await hs.write(holon, lens, data);
1013
+
1014
+ const output = document.getElementById('schemaResult');
1015
+ output.innerHTML = `<pre>Validation passed! Data written successfully.
1016
+ Result: ${result}</pre>`;
1017
+ } catch (error) {
1018
+ showError('schemaResult', error);
1019
+ }
1020
+ };
1021
+
1022
+ // Federation Operations
1023
+ window.setupFederation = async function() {
1024
+ try {
1025
+ const sourceHolon = document.getElementById('sourceHolon').value;
1026
+ const targetHolon = document.getElementById('targetHolon').value;
1027
+ const lens = document.getElementById('federationLens').value;
1028
+ const direction = document.getElementById('federationDirection').value;
1029
+ const mode = document.getElementById('federationMode').value;
1030
+
1031
+ const result = await hs.federate(sourceHolon, targetHolon, lens, {
1032
+ direction,
1033
+ mode
1034
+ });
1035
+
1036
+ const output = document.getElementById('federationResult');
1037
+ output.innerHTML = `<pre>Federation setup successful: ${result}
1038
+ Source: ${sourceHolon}
1039
+ Target: ${targetHolon}
1040
+ Lens: ${lens}
1041
+ Direction: ${direction}
1042
+ Mode: ${mode}</pre>`;
1043
+ } catch (error) {
1044
+ showError('federationResult', error);
1045
+ }
1046
+ };
1047
+
1048
+ window.getFederatedData = async function() {
1049
+ try {
1050
+ const holon = document.getElementById('fedHolon').value;
1051
+ const lens = document.getElementById('fedLens').value;
1052
+ const resolveHolograms = document.getElementById('resolveHolograms').checked;
1053
+
1054
+ const data = await hs.getFederatedData(holon, lens, { resolveHolograms });
1055
+
1056
+ const output = document.getElementById('federationResult');
1057
+ output.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
1058
+ } catch (error) {
1059
+ showError('federationResult', error);
1060
+ }
1061
+ };
1062
+
1063
+ // Social Protocol Operations
1064
+ window.publishNostr = async function() {
1065
+ try {
1066
+ const holon = document.getElementById('nostrHolon').value;
1067
+ const event = JSON.parse(document.getElementById('nostrEvent').value);
1068
+
1069
+ // Add timestamp if null
1070
+ if (event.created_at === null) {
1071
+ event.created_at = Math.floor(Date.now() / 1000);
1072
+ }
1073
+
1074
+ const result = await hs.publishNostr(event, holon);
1075
+
1076
+ const output = document.getElementById('socialResult');
1077
+ output.innerHTML = `<pre>Nostr event published: ${result}
1078
+ Event ID: ${event.id}
1079
+ Created at: ${new Date(event.created_at * 1000).toISOString()}</pre>`;
1080
+ } catch (error) {
1081
+ showError('socialResult', error);
1082
+ }
1083
+ };
1084
+
1085
+ window.publishActivityPub = async function() {
1086
+ try {
1087
+ const holon = document.getElementById('apHolon').value;
1088
+ const object = JSON.parse(document.getElementById('apObject').value);
1089
+
1090
+ const result = await hs.publishActivityPub(object, holon);
1091
+
1092
+ const output = document.getElementById('socialResult');
1093
+ output.innerHTML = `<pre>ActivityPub object published: ${result}</pre>`;
1094
+ } catch (error) {
1095
+ showError('socialResult', error);
1096
+ }
1097
+ };
1098
+
1099
+ window.querySocial = async function() {
1100
+ try {
1101
+ const holon = document.getElementById('socialHolon').value;
1102
+ const protocol = document.getElementById('socialProtocol').value || undefined;
1103
+
1104
+ const data = await hs.querySocial(holon, { protocol });
1105
+
1106
+ const output = document.getElementById('socialResult');
1107
+ output.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
1108
+ } catch (error) {
1109
+ showError('socialResult', error);
1110
+ }
1111
+ };
1112
+
1113
+ // Cryptography Operations
1114
+ window.generateKeys = async function() {
1115
+ try {
1116
+ // Generate a random private key
1117
+ const privateKey = crypto.getRandomValues(new Uint8Array(32));
1118
+ const privateKeyHex = Array.from(privateKey, b => b.toString(16).padStart(2, '0')).join('');
1119
+
1120
+ // Get public key
1121
+ const publicKey = await hs.getPublicKey(privateKeyHex);
1122
+
1123
+ const output = document.getElementById('keysResult');
1124
+ output.innerHTML = `<pre>Private Key: ${privateKeyHex}
1125
+ Public Key: ${publicKey}
1126
+
1127
+ ⚠️ Keep your private key secret!</pre>`;
1128
+
1129
+ // Auto-fill the fields
1130
+ document.getElementById('privateKey').value = privateKeyHex;
1131
+ document.getElementById('publicKey').value = publicKey;
1132
+ } catch (error) {
1133
+ showError('keysResult', error);
1134
+ }
1135
+ };
1136
+
1137
+ window.signContent = async function() {
1138
+ try {
1139
+ const content = document.getElementById('signContent').value;
1140
+ const privateKey = document.getElementById('privateKey').value;
1141
+
1142
+ const signature = await hs.sign(content, privateKey);
1143
+
1144
+ const output = document.getElementById('cryptoResult');
1145
+ output.innerHTML = `<pre>Content: ${content}
1146
+ Signature: ${signature}</pre>`;
1147
+
1148
+ // Auto-fill signature field
1149
+ document.getElementById('signature').value = signature;
1150
+ } catch (error) {
1151
+ showError('cryptoResult', error);
1152
+ }
1153
+ };
1154
+
1155
+ window.verifySignature = async function() {
1156
+ try {
1157
+ const content = document.getElementById('signContent').value;
1158
+ const signature = document.getElementById('signature').value;
1159
+ const publicKey = document.getElementById('publicKey').value;
1160
+
1161
+ const valid = await hs.verify(content, signature, publicKey);
1162
+
1163
+ const output = document.getElementById('cryptoResult');
1164
+ output.innerHTML = `<pre>Signature verification: ${valid ? '✅ Valid' : '❌ Invalid'}
1165
+ Content: ${content}
1166
+ Signature: ${signature}
1167
+ Public Key: ${publicKey}</pre>`;
1168
+ } catch (error) {
1169
+ showError('cryptoResult', error);
1170
+ }
1171
+ };
1172
+
1173
+ window.issueCapability = async function() {
1174
+ try {
1175
+ const permissions = document.getElementById('capPermissions').value.split(',').map(p => p.trim());
1176
+ const scope = JSON.parse(document.getElementById('capScope').value);
1177
+ const recipient = document.getElementById('capRecipient').value;
1178
+
1179
+ const token = await hs.issueCapability(permissions, scope, recipient, {
1180
+ expiresIn: 3600 // 1 hour
1181
+ });
1182
+
1183
+ const output = document.getElementById('cryptoResult');
1184
+ output.innerHTML = `<pre>Capability Token Issued:
1185
+ ${JSON.stringify(token, null, 2)}</pre>`;
1186
+ } catch (error) {
1187
+ showError('cryptoResult', error);
1188
+ }
1189
+ };
1190
+
1191
+ // Subscription Operations
1192
+ window.createSubscription = function() {
1193
+ try {
1194
+ const holon = document.getElementById('subHolon').value;
1195
+ const lens = document.getElementById('subLens').value;
1196
+
1197
+ // Clear previous events
1198
+ document.getElementById('subscriptionEvents').innerHTML = '<pre>Waiting for events...</pre>';
1199
+
1200
+ // Create subscription
1201
+ currentSubscription = hs.subscribe(holon, lens, (data, key) => {
1202
+ const eventsDiv = document.getElementById('subscriptionEvents');
1203
+ const timestamp = new Date().toISOString();
1204
+ const event = `[${timestamp}] Data change in ${holon}/${lens}:
1205
+ ${JSON.stringify(data, null, 2)}
1206
+
1207
+ `;
1208
+ eventsDiv.innerHTML = '<pre>' + event + eventsDiv.innerText + '</pre>';
1209
+ });
1210
+
1211
+ // Enable/disable buttons
1212
+ document.getElementById('unsubBtn').disabled = false;
1213
+
1214
+ showSuccess('subscriptionEvents', 'Subscription created successfully');
1215
+ } catch (error) {
1216
+ showError('subscriptionEvents', error);
1217
+ }
1218
+ };
1219
+
1220
+ window.unsubscribe = function() {
1221
+ if (currentSubscription) {
1222
+ currentSubscription.unsubscribe();
1223
+ currentSubscription = null;
1224
+
1225
+ document.getElementById('unsubBtn').disabled = true;
1226
+ showSuccess('subscriptionEvents', 'Unsubscribed successfully');
1227
+ }
1228
+ };
1229
+
1230
+ window.sendTestMessage = async function() {
1231
+ try {
1232
+ const holon = document.getElementById('subHolon').value;
1233
+ const lens = document.getElementById('subLens').value;
1234
+ const message = document.getElementById('testMessage').value;
1235
+
1236
+ await hs.write(holon, lens, {
1237
+ type: 'test',
1238
+ message: message,
1239
+ timestamp: Date.now()
1240
+ });
1241
+
1242
+ showSuccess('subscriptionEvents', 'Test message sent');
1243
+ } catch (error) {
1244
+ showError('subscriptionEvents', error);
1245
+ }
1246
+ };
1247
+
1248
+ // Metrics Operations
1249
+ window.refreshMetrics = function() {
1250
+ try {
1251
+ const metrics = hs.metrics();
1252
+
1253
+ // Update metric cards
1254
+ document.getElementById('metricReads').textContent = metrics.reads || 0;
1255
+ document.getElementById('metricWrites').textContent = metrics.writes || 0;
1256
+ document.getElementById('metricSubs').textContent = metrics.subscriptions || 0;
1257
+ document.getElementById('metricFeds').textContent = metrics.federations || 0;
1258
+ document.getElementById('metricAvgRead').textContent =
1259
+ metrics.avgReadTime ? metrics.avgReadTime.toFixed(2) : '0';
1260
+ document.getElementById('metricAvgWrite').textContent =
1261
+ metrics.avgWriteTime ? metrics.avgWriteTime.toFixed(2) : '0';
1262
+
1263
+ // Show raw data
1264
+ document.getElementById('metricsResult').innerHTML =
1265
+ `<pre>${JSON.stringify(metrics, null, 2)}</pre>`;
1266
+ } catch (error) {
1267
+ showError('metricsResult', error);
1268
+ }
1269
+ };
1270
+
1271
+ // Map initialization
1272
+ function initMap() {
1273
+ // Check if Leaflet is loaded
1274
+ if (typeof L === 'undefined') {
1275
+ console.warn('Leaflet not loaded, skipping map initialization');
1276
+ return;
1277
+ }
1278
+
1279
+ // Initialize the map
1280
+ map = L.map('map').setView([37.7749, -122.4194], 10);
1281
+
1282
+ // Add tile layer
1283
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
1284
+ attribution: '© OpenStreetMap contributors'
1285
+ }).addTo(map);
1286
+ }
1287
+
1288
+ function showHexagonOnMap(holonId, lat, lng) {
1289
+ if (!map) return;
1290
+
1291
+ // Clear previous layer
1292
+ if (currentHexLayer) {
1293
+ map.removeLayer(currentHexLayer);
1294
+ }
1295
+
1296
+ // For demo purposes, show a marker at the center
1297
+ // In production, you'd use h3-js to get the hex boundary
1298
+ currentHexLayer = L.marker([lat, lng])
1299
+ .addTo(map)
1300
+ .bindPopup(`Holon: ${holonId}`)
1301
+ .openPopup();
1302
+
1303
+ // Center the map
1304
+ map.setView([lat, lng], 12);
1305
+ }
1306
+
1307
+ // Helper functions
1308
+ function showError(elementId, error) {
1309
+ const output = document.getElementById(elementId);
1310
+ output.innerHTML = `<pre style="color: #ff4444;">Error: ${error.message}
1311
+ ${error.stack || ''}</pre>`;
1312
+ console.error(error);
1313
+ }
1314
+
1315
+ function showSuccess(elementId, message) {
1316
+ const output = document.getElementById(elementId);
1317
+ const current = output.innerHTML;
1318
+ output.innerHTML = `<pre style="color: #44ff44;">${message}</pre>
1319
+ ${current}`;
1320
+ }
1321
+
1322
+ // Initialize on page load
1323
+ window.addEventListener('DOMContentLoaded', async () => {
1324
+ loadRelayPreference();
1325
+ await initHoloSphere();
1326
+ updateSocialProtocolsBanner();
1327
+ });
1328
+ </script>
1329
+
1330
+ <!-- Load Leaflet for map visualization -->
1331
+ <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
1332
+ </body>
1333
+ </html>