Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 43 additions & 58 deletions client/src/components/GunTest.vue
Original file line number Diff line number Diff line change
@@ -1,73 +1,58 @@
<template>
<div>
<p>Interests:</p>
<span v-for="interest of interests" class="mx-2">
{{interest.interests}}
</span>

<p>--------</p>
<input
placeholder="Interest title"
/>
<button @click="createInterest">Save</button>
<div class="mb-4 p-3 bg-blue-100 dark:bg-blue-900 rounded">
<strong>Your Peer ID:</strong> {{ myPeerId }}
</div>

<div class="mt-4">
<h3>Connected Peers:</h3>
<div v-if="peers.length === 0">No peers connected</div>
<div v-else>
<div v-for="(peer, index) in peers" :key="index" class="my-1">
Peer {{ index + 1 }}: {{ peer }}
</div>
</div>
</div>

<div class="mt-4">
<h3>Data Overview:</h3>
<button @click="generateDataOverview('test1')" class="mb-2">Generate Data Overview</button>
<pre v-if="dataOverview" class="p-2 bg-gray-100 dark:bg-gray-800 rounded overflow-auto">{{ dataOverview }}</pre>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import gun from '@/services/gun'
import { Peers } from '@/services/peers'

const interests = ref([])
const newInterest = ref('')

const chat = gun.get('interests')

onMounted(() => {
gun.get('test1').map().once(async (data) => {
if (data) {
interests.value.push(data);
await relations(data);
}
});
// Create refs for reactive state
const peers = ref<string[]>([])
const dataOverview = ref<string>('')

});
// Initialize Peers instance
const peerService = new Peers(gun, 'peer-registry')
const myPeerId = ref<string>(peerService.getPeerId())

const relations = async (interest: Object) => {
if (interest.interests) {
gun.get(interest.interests).map((value, key) => {
console.log(key, value);
gun.get('test1/Football').map((x, y) => console.log(x, y))
});
}
// Generate data overview
const generateDataOverview = async (key: string) => {
dataOverview.value = 'Loading data overview...';
dataOverview.value = await peerService.generateDataOverview(key);
}

const getInterest = async (value: string) => {
return new Promise((resolve, reject) => {
gun.get('test1').get(value).once((ack) => {
resolve(ack);
});
onMounted(async () => {
// Register our peer and start periodic announcements with a callback
await peerService.startPeriodicAnnouncements(15000, async () => {
const directPeers = peerService.getConnectedPeers();
const registryPeers = await peerService.getActivePeers();
peers.value = [...directPeers, ...registryPeers];
});
}

const createInterest = async () => {
const newInterests = ['Soccer', 'Football', 'Swimming', 'Coding', 'Surfing'];
for (let interest of newInterests) {
gun.get('test1')
.get(interest)
.put({
id: interest,
title: interest,
})
}
const football = await getInterest('Football');
console.log(football);

gun.get('test1')
.get('Soccer')
.get('interests')
.get('likes')
.set(football)
})

console.log('interests created')
}
onBeforeUnmount(() => {
// Clean up resources
peerService.stopPeriodicAnnouncements();
peerService.destroy();
})
</script>
179 changes: 179 additions & 0 deletions client/src/services/peers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
export class Peers {
private gun: any;
private peerRegistry: any;
private peerId: string;
private announcementInterval?: number;

constructor(gun: any, peerRegistryPath = 'peers') {
this.gun = gun;
this.peerRegistry = gun.get(peerRegistryPath);
this.peerId = this.getPersistentPeerId();
}

// Get persistent peer ID or create a new one if it doesn't exist
private getPersistentPeerId(): string {
// Create or retrieve a tab-specific identifier
const tabIdKey = 'gun-tab-id';
let tabId = sessionStorage.getItem(tabIdKey);

if (!tabId) {
// Create a new tab identifier that will be unique to this browser tab
tabId = `tab-${Date.now()}-${Math.random().toString(36).substring(2, 10)}`;
sessionStorage.setItem(tabIdKey, tabId);
}

// Use the tab ID as part of the storage key to have different IDs per tab
const storageKey = `gun-peer-id-${tabId}`;
let storedId = localStorage.getItem(storageKey);

if (!storedId) {
storedId = `peer-${Math.random().toString(36).substring(2, 10)}`;
localStorage.setItem(storageKey, storedId);
}

return storedId;
}

// Get the peer ID
public getPeerId(): string {
return this.peerId;
}

// Register this peer in the registry
public registerPeer(): void {
this.peerRegistry.get(this.peerId).put({
id: this.peerId,
active: true,
firstSeen: Date.now(),
lastSeen: Date.now()
});

// Initial announcement
this.announcePeer();
}

// Announce this peer's presence to the network
public announcePeer(): void {
// Update last seen timestamp
this.peerRegistry.get(this.peerId).put({
active: true,
lastSeen: Date.now()
});
}

// Start periodic announcements
public async startPeriodicAnnouncements(intervalMs = 15000, callback?: () => void): Promise<void> {
this.stopPeriodicAnnouncements();

this.registerPeer();
if (callback) {
await callback();
}

this.announcementInterval = window.setInterval(async () => {
this.announcePeer();

// Execute the callback after each announcement if provided
if (callback) {
await callback();
}
}, intervalMs);
}

// Stop periodic announcements
public stopPeriodicAnnouncements(): void {
if (this.announcementInterval) {
clearInterval(this.announcementInterval);
this.announcementInterval = undefined;
}
}

// Check for active peers in the registry
public getActivePeers(): Promise<string[]> {
return new Promise((resolve) => {
const activePeers: string[] = [];
const cutoffTime = Date.now() - 20000; // 20 seconds ago

this.peerRegistry.map().once((data: any, id: string) => {
if (data && id !== this.peerId) {
const isStale = data.lastSeen < cutoffTime;

if (data.active && !isStale) {
activePeers.push(`${id} (registry)`);
}
}
});

// Give Gun a moment to gather data before resolving
setTimeout(() => resolve(activePeers), 100);
});
}

// Function to get the list of connected peers
public getConnectedPeers(): string[] {
const connectedPeers: string[] = [];

// Check direct peer connections via Gun
const peersList = this.gun.back('opt').peers || {};

// Check each peer connection
for (const id in peersList) {
if (Object.prototype.hasOwnProperty.call(peersList, id)) {
const peer = peersList[id];

// Only include peers that are actually connected
if (peer && peer.wire && peer.wire.constructor) {
const readyState = peer.wire.readyState;
const status = readyState === 1 ? 'connected' : 'disconnected';
connectedPeers.push(`${id} (${status})`);
} else {
connectedPeers.push(`${id} (unknown)`);
}
}
}

return connectedPeers;
}

// Function to generate a data overview
public async generateDataOverview(key: string): Promise<string> {
try {
const overview: Record<string, any> = {};

// Get node data
await new Promise((resolve) => {
this.gun.get(key).once((data: any) => {
overview[key] = { _: 'node', ...data };
resolve(data);
});
});

const fetchReferences = async (node: any, result: Record<string, any>) => {
for (const key in node) {
if (key !== '_' && typeof node[key] === 'object' && node[key] !== null) {
const ref = node[key]['#'];
if (ref) {
await new Promise((resolve) => {
this.gun.get(ref).once((refData: any) => {
result[ref] = { _: 'reference', ...refData };
fetchReferences(refData, result).then(resolve);
});
});
}
}
}
};

await fetchReferences(overview[key], overview);

return JSON.stringify(overview, null, 2);
} catch (error: any) {
return `Error generating overview: ${error.message}`;
}
}

// Clean up resources when no longer needed
public destroy(): void {
this.stopPeriodicAnnouncements();
}
}