chore: railgun repro
This commit is contained in:
parent
e7c7ba5ce1
commit
98fe216280
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"railgun"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Railgun Messages</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 20px auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
#status {
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.message {
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
}
|
||||
#messageContainer {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
margin: 20px 0;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.input-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
#messageInput {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
#lastMessageTime {
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #e8f5e9;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="status">Status: Initializing...</div>
|
||||
<div id="lastMessageTime">No messages received yet</div>
|
||||
<div id="messageContainer"></div>
|
||||
<div class="input-container">
|
||||
<input type="text" id="messageInput" placeholder="Enter message">
|
||||
<button onclick="sendMessage()">Send Message</button>
|
||||
</div>
|
||||
<script type="module" src="index.ts"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,129 @@
|
|||
import { createDecoder, createEncoder, createLightNode, Protocols } from "@waku/sdk";
|
||||
import { determinePubsubTopic } from "@waku/utils";
|
||||
|
||||
|
||||
const CONTENT_TOPICS = [
|
||||
"/railgun/v2/0-1-fees/json",
|
||||
"/railgun/v2/0-56-fees/json",
|
||||
"/railgun/v2/0-137-fees/json",
|
||||
"/railgun/v2/0-42161-fees/json",
|
||||
"/railgun/v2/0-421$1-transact-response/json",
|
||||
"/railgun/v2/encrypted-metrics-pong/json"
|
||||
];
|
||||
const PUBSUB_TOPIC = "/waku/2/rs/0/1"
|
||||
|
||||
const railgunMa = "/dns4/railgun.ivansete.xyz/tcp/8000/wss/p2p/16Uiu2HAmExcXDvdCr2XxfCeQY1jhCoJ1HodgKauRatCngQ9Q1X61"
|
||||
|
||||
const clusterId = 0;
|
||||
const shard = 1;
|
||||
|
||||
console.log({
|
||||
pubsub: determinePubsubTopic(CONTENT_TOPICS[0], PUBSUB_TOPIC),
|
||||
})
|
||||
|
||||
// Add DOM helper functions
|
||||
function updateStatus(status) {
|
||||
document.getElementById('status').textContent = `Status: ${status}`;
|
||||
}
|
||||
|
||||
function addMessageToUI(message) {
|
||||
const messageContainer = document.getElementById('messageContainer');
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.className = 'message';
|
||||
|
||||
let content = message;
|
||||
if (message.payload) {
|
||||
content = new TextDecoder().decode(message.payload);
|
||||
}
|
||||
|
||||
messageElement.textContent = content;
|
||||
messageContainer.insertBefore(messageElement, messageContainer.firstChild);
|
||||
}
|
||||
|
||||
class Railgun {
|
||||
constructor() {
|
||||
updateStatus('Initializing Railgun...');
|
||||
}
|
||||
|
||||
get decoders() {
|
||||
console.log('Creating decoders for content topics:', CONTENT_TOPICS);
|
||||
return CONTENT_TOPICS.map(topic => createDecoder(topic, PUBSUB_TOPIC));
|
||||
}
|
||||
|
||||
async start() {
|
||||
updateStatus('Starting Waku light node...');
|
||||
this.waku = await createLightNode({
|
||||
networkConfig: {
|
||||
shards: [shard],
|
||||
clusterId: clusterId,
|
||||
}
|
||||
});
|
||||
|
||||
updateStatus('Connecting to peer...');
|
||||
await this.waku.dial(railgunMa);
|
||||
await this.waku.waitForPeers([Protocols.Filter]);
|
||||
updateStatus('Connected successfully');
|
||||
|
||||
this.waku.libp2p.addEventListener("peer:identify", async(peer) => {
|
||||
updateStatus('Peer connected');
|
||||
addMessageToUI(`Peer connected: ${peer.detail.id}`);
|
||||
});
|
||||
|
||||
this.waku.libp2p.addEventListener("peer:disconnect", (peer) => {
|
||||
updateStatus('Peer disconnected');
|
||||
addMessageToUI(`Peer disconnected: ${peer.detail.id}`);
|
||||
});
|
||||
}
|
||||
|
||||
async subscribe() {
|
||||
updateStatus('Subscribing to Waku filters...');
|
||||
|
||||
const {error} = await this.waku.filter.subscribe(this.decoders, (message) => {
|
||||
if (message.payload) {
|
||||
addMessageToUI(message);
|
||||
}
|
||||
}, {forceUseAllPeers: false});
|
||||
|
||||
if (error) {
|
||||
updateStatus(`Subscription error: ${error.message}`);
|
||||
} else {
|
||||
updateStatus('Successfully subscribed to Waku filters');
|
||||
}
|
||||
}
|
||||
|
||||
async push(message) {
|
||||
updateStatus('Pushing message...');
|
||||
const encoder = createEncoder({
|
||||
contentTopic: CONTENT_TOPICS[0],
|
||||
pubsubTopic: PUBSUB_TOPIC
|
||||
});
|
||||
|
||||
const result = await this.waku.lightPush.send(encoder, {
|
||||
payload: new TextEncoder().encode(message)
|
||||
});
|
||||
|
||||
if (result.error) {
|
||||
updateStatus(`Error sending message: ${result.error.message}`);
|
||||
} else {
|
||||
updateStatus('Message sent successfully');
|
||||
addMessageToUI(`Sent: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const railgun = new Railgun();
|
||||
export default railgun;
|
||||
|
||||
// Initialize the application
|
||||
await railgun.start();
|
||||
await railgun.subscribe();
|
||||
|
||||
// Add global function for sending messages
|
||||
window.sendMessage = async () => {
|
||||
const input = document.getElementById('messageInput');
|
||||
const message = input.value.trim();
|
||||
if (message) {
|
||||
await railgun.push(message);
|
||||
input.value = '';
|
||||
}
|
||||
};
|
|
@ -0,0 +1,230 @@
|
|||
import { createDecoder, createEncoder, createLightNode, Protocols, type LightNode, type Decoder, type DecodedMessage } from "@waku/sdk";
|
||||
import { determinePubsubTopic } from "@waku/utils";
|
||||
|
||||
const CONTENT_TOPICS = [
|
||||
"/railgun/v2/0-1-fees/json",
|
||||
"/railgun/v2/0-56-fees/json",
|
||||
"/railgun/v2/0-137-fees/json",
|
||||
"/railgun/v2/0-42161-fees/json",
|
||||
"/railgun/v2/0-421$1-transact-response/json",
|
||||
"/railgun/v2/encrypted-metrics-pong/json"
|
||||
] as const;
|
||||
|
||||
const PUBSUB_TOPIC = "/waku/2/rs/0/1";
|
||||
const railgunMa = "/dns4/railgun.ivansete.xyz/tcp/8000/wss/p2p/16Uiu2HAmExcXDvdCr2XxfCeQY1jhCoJ1HodgKauRatCngQ9Q1X61";
|
||||
const clusterId = 0;
|
||||
const shard = 1;
|
||||
|
||||
console.log({
|
||||
pubsub: determinePubsubTopic(CONTENT_TOPICS[0], PUBSUB_TOPIC),
|
||||
});
|
||||
|
||||
// Add timestamp tracking
|
||||
let lastMessageTimestamp: number | null = null;
|
||||
|
||||
function updateLastMessageTime(): void {
|
||||
const timestampElement = document.getElementById('lastMessageTime');
|
||||
if (!timestampElement) return;
|
||||
|
||||
if (!lastMessageTimestamp) {
|
||||
timestampElement.textContent = 'No messages received yet';
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const timeDiff = now - lastMessageTimestamp;
|
||||
const seconds = Math.floor(timeDiff / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
|
||||
if (hours > 0) {
|
||||
timestampElement.textContent = `Last message: ${hours}h ${minutes % 60}m ago`;
|
||||
} else if (minutes > 0) {
|
||||
timestampElement.textContent = `Last message: ${minutes}m ${seconds % 60}s ago`;
|
||||
} else {
|
||||
timestampElement.textContent = `Last message: ${seconds}s ago`;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the timestamp display every second
|
||||
setInterval(updateLastMessageTime, 1000);
|
||||
|
||||
function updateStatus(message: string): void {
|
||||
const statusElement = document.getElementById('status');
|
||||
if (!statusElement) return;
|
||||
statusElement.textContent = message;
|
||||
}
|
||||
|
||||
function addMessageToUI(message: DecodedMessage | string): void {
|
||||
const messageContainer = document.getElementById('messageContainer');
|
||||
if (!messageContainer) return;
|
||||
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.className = 'message';
|
||||
|
||||
// Update last message timestamp
|
||||
lastMessageTimestamp = Date.now();
|
||||
|
||||
let content: string;
|
||||
if (typeof message === 'string') {
|
||||
content = message;
|
||||
} else if (message.payload) {
|
||||
content = new TextDecoder().decode(message.payload);
|
||||
} else {
|
||||
content = 'Empty message';
|
||||
}
|
||||
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
messageElement.textContent = `[${timestamp}] ${content}`;
|
||||
messageContainer.insertBefore(messageElement, messageContainer.firstChild);
|
||||
}
|
||||
|
||||
class Railgun {
|
||||
private waku: LightNode | null = null;
|
||||
private subscriptionStartTime: number | null = null;
|
||||
private messageCount = 0;
|
||||
|
||||
constructor() {
|
||||
updateStatus('Initializing Railgun...');
|
||||
}
|
||||
|
||||
get decoders(): Decoder[] {
|
||||
console.log('Creating decoders for content topics:', CONTENT_TOPICS);
|
||||
return CONTENT_TOPICS.map(topic => createDecoder(topic, PUBSUB_TOPIC));
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
updateStatus('Starting Waku light node...');
|
||||
this.waku = await createLightNode({
|
||||
networkConfig: {
|
||||
shards: [shard],
|
||||
clusterId: clusterId,
|
||||
}
|
||||
});
|
||||
|
||||
updateStatus('Connecting to peer...');
|
||||
await this.waku.dial(railgunMa);
|
||||
await this.waku.waitForPeers([Protocols.Filter]);
|
||||
updateStatus('Connected successfully');
|
||||
|
||||
this.waku.libp2p.addEventListener("peer:identify", async(peer) => {
|
||||
updateStatus('Peer connected');
|
||||
addMessageToUI(`Peer connected: ${peer.detail.peerId}`);
|
||||
});
|
||||
|
||||
this.waku.libp2p.addEventListener("peer:disconnect", (peer) => {
|
||||
updateStatus('Peer disconnected');
|
||||
addMessageToUI(`Peer disconnected: ${peer.detail}`);
|
||||
});
|
||||
}
|
||||
|
||||
async subscribe(): Promise<void> {
|
||||
if (!this.waku) throw new Error('Waku not initialized');
|
||||
|
||||
this.subscriptionStartTime = Date.now();
|
||||
this.messageCount = 0;
|
||||
updateStatus('Subscribing to Waku filters...');
|
||||
|
||||
const {error} = await this.waku.filter.subscribe(this.decoders, (message) => {
|
||||
this.messageCount++;
|
||||
if (message.payload) {
|
||||
const decodedMessage = new TextDecoder().decode(message.payload);
|
||||
lastMessageTimestamp = Date.now();
|
||||
addMessageToUI(decodedMessage);
|
||||
|
||||
// Log debug information
|
||||
console.log('Filter message received:', {
|
||||
messageCount: this.messageCount,
|
||||
timeSinceSubscription: `${(Date.now() - (this.subscriptionStartTime || 0)) / 1000}s`,
|
||||
contentTopic: message.contentTopic,
|
||||
payload: decodedMessage,
|
||||
timestamp: new Date().toISOString(),
|
||||
lastMessageTimestamp
|
||||
});
|
||||
}
|
||||
}, {forceUseAllPeers: false});
|
||||
|
||||
if (error) {
|
||||
updateStatus(`Subscription error: ${error}`);
|
||||
} else {
|
||||
updateStatus('Successfully subscribed to Waku filters');
|
||||
}
|
||||
|
||||
// Add periodic connection check
|
||||
setInterval(() => this.checkConnection(), 30000); // Check every 30 seconds
|
||||
}
|
||||
|
||||
private async checkConnection(): Promise<void> {
|
||||
if (!this.waku) return;
|
||||
|
||||
const connections = this.waku.libp2p.getConnections();
|
||||
const filterPeers = this.waku.filter.connectedPeers;
|
||||
|
||||
console.log('Connection status:', {
|
||||
totalConnections: connections.length,
|
||||
filterPeers: filterPeers.length,
|
||||
messageCount: this.messageCount,
|
||||
timeSinceLastMessage: lastMessageTimestamp ?
|
||||
`${(Date.now() - lastMessageTimestamp) / 1000}s ago` :
|
||||
'No messages received',
|
||||
uptime: this.subscriptionStartTime ?
|
||||
`${(Date.now() - this.subscriptionStartTime) / 1000}s` :
|
||||
'Not subscribed'
|
||||
});
|
||||
|
||||
if (connections.length === 0 || filterPeers.length === 0) {
|
||||
updateStatus('Connection lost - attempting to reconnect...');
|
||||
try {
|
||||
await this.waku.dial(railgunMa);
|
||||
await this.waku.waitForPeers([Protocols.Filter]);
|
||||
updateStatus('Reconnected successfully');
|
||||
} catch (error) {
|
||||
updateStatus(`Reconnection failed: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async push(message: string): Promise<void> {
|
||||
if (!this.waku) throw new Error('Waku not initialized');
|
||||
|
||||
updateStatus('Pushing message...');
|
||||
const encoder = createEncoder({
|
||||
contentTopic: CONTENT_TOPICS[0],
|
||||
pubsubTopic: PUBSUB_TOPIC
|
||||
});
|
||||
|
||||
const {failures} = await this.waku.lightPush.send(encoder, {
|
||||
payload: new TextEncoder().encode(message)
|
||||
});
|
||||
|
||||
if (failures.length > 0) {
|
||||
updateStatus(`Error sending message: ${failures}`);
|
||||
} else {
|
||||
updateStatus('Message sent successfully');
|
||||
addMessageToUI(`Sent: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const railgun = new Railgun();
|
||||
export default railgun;
|
||||
|
||||
// Initialize the application
|
||||
await railgun.start();
|
||||
await railgun.subscribe();
|
||||
|
||||
// Add global function for sending messages
|
||||
declare global {
|
||||
interface Window {
|
||||
sendMessage: () => Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
window.sendMessage = async (): Promise<void> => {
|
||||
const input = document.getElementById('messageInput') as HTMLInputElement;
|
||||
const message = input.value.trim();
|
||||
if (message) {
|
||||
await railgun.push(message);
|
||||
input.value = '';
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "test",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@waku/sdk": "^0.0.30-5674b0e.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^4.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthrough": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
Loading…
Reference in New Issue