Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c4d73026b5 | |||
| 516c4f2f80 | |||
|
|
6ff47cec92 | ||
|
|
5b1fa185f7 | ||
|
|
d941638779 | ||
|
|
e6bef22494 | ||
|
|
3edc477069 | ||
|
|
5b618c08ed |
@@ -418,7 +418,7 @@ class EventProcessor {
|
|||||||
if (!routing.audit || typeof routing.audit !== 'string') return false;
|
if (!routing.audit || typeof routing.audit !== 'string') return false;
|
||||||
|
|
||||||
// Check maximum delay limit
|
// Check maximum delay limit
|
||||||
const maxDelay = this.config.get('thrower.maxDelay') || 3600;
|
const maxDelay = this.config.get('thrower.maxDelay') || 86460;
|
||||||
if (routing.delay > maxDelay) {
|
if (routing.delay > maxDelay) {
|
||||||
this.logger.error(`Routing delay ${routing.delay}s exceeds maximum allowed delay of ${maxDelay}s`);
|
this.logger.error(`Routing delay ${routing.delay}s exceeds maximum allowed delay of ${maxDelay}s`);
|
||||||
return false;
|
return false;
|
||||||
@@ -648,7 +648,7 @@ class ThrowerInfoManager {
|
|||||||
if (throwerInfo.privacyPolicy) tags.push(['privacy_policy', throwerInfo.privacyPolicy]);
|
if (throwerInfo.privacyPolicy) tags.push(['privacy_policy', throwerInfo.privacyPolicy]);
|
||||||
if (throwerInfo.termsOfService) tags.push(['terms_of_service', throwerInfo.termsOfService]);
|
if (throwerInfo.termsOfService) tags.push(['terms_of_service', throwerInfo.termsOfService]);
|
||||||
tags.push(['refresh_rate', throwerInfo.refreshRate.toString()]);
|
tags.push(['refresh_rate', throwerInfo.refreshRate.toString()]);
|
||||||
tags.push(['max_delay', (throwerInfo.maxDelay || 3600).toString()]);
|
tags.push(['max_delay', (throwerInfo.maxDelay || 86460).toString()]);
|
||||||
|
|
||||||
const eventTemplate = {
|
const eventTemplate = {
|
||||||
kind: 12222,
|
kind: 12222,
|
||||||
@@ -933,7 +933,7 @@ class WebSocketManager {
|
|||||||
this.logger.info('Stopping WebSocket monitoring...');
|
this.logger.info('Stopping WebSocket monitoring...');
|
||||||
|
|
||||||
this.connections.forEach(({ url, ws }) => {
|
this.connections.forEach(({ url, ws }) => {
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
if (ws.readyState === ws.OPEN) {
|
||||||
// Send CLOSE message for subscription
|
// Send CLOSE message for subscription
|
||||||
if (this.subscriptionId) {
|
if (this.subscriptionId) {
|
||||||
const closeMsg = JSON.stringify(['CLOSE', this.subscriptionId]);
|
const closeMsg = JSON.stringify(['CLOSE', this.subscriptionId]);
|
||||||
|
|||||||
@@ -5,126 +5,261 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "=== Superball Thrower Daemon Installation ==="
|
echo "🏀 Superball Thrower Daemon Installation"
|
||||||
echo
|
echo "========================================"
|
||||||
|
|
||||||
# Check if running as root
|
# Check if running as root
|
||||||
if [[ $EUID -eq 0 ]]; then
|
if [[ $EUID -eq 0 ]]; then
|
||||||
echo "This script should not be run as root for security reasons."
|
echo "❌ This script should not be run as root for security reasons"
|
||||||
echo "Please run as a regular user with sudo privileges."
|
echo " Please run as a regular user with sudo privileges"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if Node.js is installed
|
# Function to check if command exists
|
||||||
if ! command -v node &> /dev/null; then
|
command_exists() {
|
||||||
echo "Error: Node.js is not installed."
|
command -v "$1" >/dev/null 2>&1
|
||||||
echo "Please install Node.js 16 or later from https://nodejs.org/"
|
}
|
||||||
|
|
||||||
|
# Check for required system dependencies
|
||||||
|
echo "📋 Checking system dependencies..."
|
||||||
|
|
||||||
|
# Check for curl
|
||||||
|
if ! command_exists curl; then
|
||||||
|
echo "❌ curl is required but not installed"
|
||||||
|
echo " Please install curl: sudo apt update && sudo apt install curl"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check Node.js version
|
# Check for systemctl (systemd)
|
||||||
NODE_VERSION=$(node --version | cut -d'v' -f2 | cut -d'.' -f1)
|
if ! command_exists systemctl; then
|
||||||
if [ "$NODE_VERSION" -lt 16 ]; then
|
echo "❌ systemd is required but not found"
|
||||||
echo "Error: Node.js version 16 or later is required."
|
echo " This script requires a systemd-based system"
|
||||||
echo "Current version: $(node --version)"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ Node.js $(node --version) detected"
|
# Install latest Node.js using nvm
|
||||||
|
echo "🔧 Installing latest Node.js using nvm..."
|
||||||
|
|
||||||
# Check if npm is installed
|
# Download and install nvm
|
||||||
if ! command -v npm &> /dev/null; then
|
if [ ! -d "$HOME/.nvm" ]; then
|
||||||
echo "Error: npm is not installed."
|
echo " Downloading and installing nvm..."
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Load nvm
|
||||||
|
export NVM_DIR="$HOME/.nvm"
|
||||||
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||||
|
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||||
|
|
||||||
|
# Install Node.js 22 (latest LTS)
|
||||||
|
echo " Installing Node.js 22..."
|
||||||
|
nvm install 22
|
||||||
|
nvm use 22
|
||||||
|
|
||||||
|
# Verify installation
|
||||||
|
NODE_VERSION=$(node --version)
|
||||||
|
NPM_VERSION=$(npm --version)
|
||||||
|
echo "✅ Node.js $NODE_VERSION installed"
|
||||||
|
echo "✅ npm $NPM_VERSION installed"
|
||||||
|
|
||||||
|
# Copy Node.js binaries to system-wide location
|
||||||
|
echo "📋 Installing Node.js binaries system-wide..."
|
||||||
|
NODE_PATH=$(which node)
|
||||||
|
NPM_PATH=$(which npm)
|
||||||
|
|
||||||
|
if [ -n "$NODE_PATH" ] && [ -x "$NODE_PATH" ]; then
|
||||||
|
# Remove any existing symlinks first
|
||||||
|
sudo rm -f /usr/local/bin/node
|
||||||
|
sudo cp "$NODE_PATH" /usr/local/bin/node
|
||||||
|
sudo chmod +x /usr/local/bin/node
|
||||||
|
echo "✅ Node.js binary copied to /usr/local/bin/node"
|
||||||
|
else
|
||||||
|
echo "❌ Failed to find Node.js executable"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ npm $(npm --version) detected"
|
if [ -n "$NPM_PATH" ] && [ -x "$NPM_PATH" ]; then
|
||||||
|
# Remove any existing symlinks first
|
||||||
# Get the directory where this script is located
|
sudo rm -f /usr/local/bin/npm
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
sudo cp "$NPM_PATH" /usr/local/bin/npm
|
||||||
DAEMON_DIR="$SCRIPT_DIR"
|
sudo chmod +x /usr/local/bin/npm
|
||||||
|
echo "✅ npm binary copied to /usr/local/bin/npm"
|
||||||
echo "Installing to: $DAEMON_DIR"
|
else
|
||||||
echo
|
echo "❌ Failed to find npm executable"
|
||||||
|
|
||||||
# Install Node.js dependencies
|
|
||||||
echo "Installing Node.js dependencies..."
|
|
||||||
cd "$DAEMON_DIR"
|
|
||||||
npm install
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo "Error: Failed to install Node.js dependencies"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "✓ Dependencies installed successfully"
|
# Install npm dependencies
|
||||||
echo
|
echo "📦 Installing npm dependencies..."
|
||||||
|
if ! npm install; then
|
||||||
|
echo "❌ Failed to install npm dependencies"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Create user for daemon (if it doesn't exist)
|
echo "✅ Dependencies installed successfully"
|
||||||
DAEMON_USER="superball"
|
|
||||||
if ! id "$DAEMON_USER" &>/dev/null; then
|
# Interactive configuration
|
||||||
echo "Creating system user: $DAEMON_USER"
|
echo ""
|
||||||
|
echo "⚙️ Thrower Configuration"
|
||||||
|
echo "========================"
|
||||||
|
echo "Please provide the following information for your Superball Thrower:"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Prompt for configuration values
|
||||||
|
read -p "Enter your private key (hex format, leave empty to generate new): " PRIVATE_KEY
|
||||||
|
read -p "Enter your public key (hex format, leave empty if generating new): " PUBLIC_KEY
|
||||||
|
read -p "Enter thrower name (e.g., 'My Superball Thrower'): " THROWER_NAME
|
||||||
|
read -p "Enter thrower description: " THROWER_DESCRIPTION
|
||||||
|
read -p "Enter banner URL (optional, press enter to skip): " BANNER_URL
|
||||||
|
read -p "Enter icon URL (optional, press enter to skip): " ICON_URL
|
||||||
|
|
||||||
|
# Generate keypair if not provided
|
||||||
|
if [ -z "$PRIVATE_KEY" ] || [ -z "$PUBLIC_KEY" ]; then
|
||||||
|
echo "🔑 Generating new keypair..."
|
||||||
|
# We'll let the daemon generate the keypair on first run
|
||||||
|
PRIVATE_KEY=""
|
||||||
|
PUBLIC_KEY=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update config.json with user input
|
||||||
|
echo "📝 Updating configuration file..."
|
||||||
|
|
||||||
|
# Create a temporary config file with user values
|
||||||
|
cat > config.json << EOF
|
||||||
|
{
|
||||||
|
"thrower": {
|
||||||
|
"privateKey": "$PRIVATE_KEY",
|
||||||
|
"publicKey": "$PUBLIC_KEY",
|
||||||
|
"name": "$THROWER_NAME",
|
||||||
|
"description": "$THROWER_DESCRIPTION",
|
||||||
|
"banner": "$BANNER_URL",
|
||||||
|
"icon": "$ICON_URL",
|
||||||
|
"adminPubkey": "",
|
||||||
|
"contact": "",
|
||||||
|
"supportedSups": "1,2,3,4,5,6",
|
||||||
|
"software": "https://git.laantungir.net/laantungir/super_ball.git",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"privacyPolicy": "",
|
||||||
|
"termsOfService": "",
|
||||||
|
"refreshRate": 300,
|
||||||
|
"maxDelay": 86460
|
||||||
|
},
|
||||||
|
"relays": [
|
||||||
|
{ "url": "wss://relay.laantungir.net", "read": true, "write": true, "authStatus": "unknown" },
|
||||||
|
{ "url": "wss://relay.damus.io", "read": true, "write": true, "authStatus": "unknown" },
|
||||||
|
{ "url": "wss://nos.lol", "read": true, "write": true, "authStatus": "unknown" },
|
||||||
|
{ "url": "wss://relay.snort.social", "read": true, "write": true, "authStatus": "unknown" },
|
||||||
|
{ "url": "wss://relay.primal.net", "read": true, "write": true, "authStatus": "unknown" },
|
||||||
|
{ "url": "wss://relay.nostr.band", "read": true, "write": true, "authStatus": "unknown" },
|
||||||
|
{ "url": "wss://nostr.oxtr.dev", "read": true, "write": true, "authStatus": "unknown" },
|
||||||
|
{ "url": "wss://offchain.pub", "read": true, "write": true, "authStatus": "unknown" },
|
||||||
|
{ "url": "wss://nostr-pub.wellorder.net", "read": true, "write": true, "authStatus": "unknown" }
|
||||||
|
],
|
||||||
|
"daemon": {
|
||||||
|
"logLevel": "info",
|
||||||
|
"maxQueueSize": 1000,
|
||||||
|
"maxLogEntries": 1000,
|
||||||
|
"autoStart": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Configuration updated"
|
||||||
|
|
||||||
|
# Create system user for the daemon
|
||||||
|
echo "👤 Creating system user..."
|
||||||
|
DAEMON_USER="superball-thrower"
|
||||||
|
|
||||||
|
if id "$DAEMON_USER" &>/dev/null; then
|
||||||
|
echo "✅ User $DAEMON_USER already exists"
|
||||||
|
else
|
||||||
|
echo " Creating user: $DAEMON_USER"
|
||||||
sudo useradd --system --no-create-home --shell /bin/false "$DAEMON_USER"
|
sudo useradd --system --no-create-home --shell /bin/false "$DAEMON_USER"
|
||||||
echo "✓ User $DAEMON_USER created"
|
echo "✅ User $DAEMON_USER created"
|
||||||
else
|
|
||||||
echo "✓ User $DAEMON_USER already exists"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create directories
|
# Create installation directory
|
||||||
echo "Creating directories..."
|
INSTALL_DIR="/opt/superball-thrower"
|
||||||
sudo mkdir -p /var/log/superball
|
echo "📁 Setting up installation directory: $INSTALL_DIR"
|
||||||
sudo mkdir -p /etc/superball
|
|
||||||
sudo chown "$DAEMON_USER:$DAEMON_USER" /var/log/superball
|
|
||||||
echo "✓ Directories created"
|
|
||||||
|
|
||||||
# Copy configuration file
|
sudo mkdir -p "$INSTALL_DIR"
|
||||||
if [ ! -f "/etc/superball/config.json" ]; then
|
sudo cp daemon.js "$INSTALL_DIR/"
|
||||||
echo "Installing default configuration..."
|
sudo cp package.json "$INSTALL_DIR/"
|
||||||
sudo cp "$DAEMON_DIR/config.json" /etc/superball/config.json
|
sudo cp config.json "$INSTALL_DIR/"
|
||||||
sudo chown "$DAEMON_USER:$DAEMON_USER" /etc/superball/config.json
|
|
||||||
sudo chmod 600 /etc/superball/config.json
|
# Copy node_modules if it exists
|
||||||
echo "✓ Configuration installed to /etc/superball/config.json"
|
if [ -d "node_modules" ]; then
|
||||||
echo "⚠️ IMPORTANT: Edit /etc/superball/config.json with your private key and settings"
|
sudo cp -r node_modules "$INSTALL_DIR/"
|
||||||
else
|
|
||||||
echo "✓ Configuration already exists at /etc/superball/config.json"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Set ownership and permissions
|
||||||
|
sudo chown -R "$DAEMON_USER:$DAEMON_USER" "$INSTALL_DIR"
|
||||||
|
sudo chmod 755 "$INSTALL_DIR"
|
||||||
|
sudo chmod 644 "$INSTALL_DIR"/*.js "$INSTALL_DIR"/*.json
|
||||||
|
sudo chmod -R 755 "$INSTALL_DIR/node_modules" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "✅ Files installed to $INSTALL_DIR"
|
||||||
|
|
||||||
|
# Create log directory
|
||||||
|
LOG_DIR="/var/log/superball-thrower"
|
||||||
|
echo "📝 Creating log directory: $LOG_DIR"
|
||||||
|
|
||||||
|
sudo mkdir -p "$LOG_DIR"
|
||||||
|
sudo chown "$DAEMON_USER:$DAEMON_USER" "$LOG_DIR"
|
||||||
|
sudo chmod 755 "$LOG_DIR"
|
||||||
|
|
||||||
|
echo "✅ Log directory created"
|
||||||
|
|
||||||
# Install systemd service
|
# Install systemd service
|
||||||
echo "Installing systemd service..."
|
echo "⚙️ Installing systemd service..."
|
||||||
sudo cp "$DAEMON_DIR/superball-thrower.service" /etc/systemd/system/
|
|
||||||
sudo sed -i "s|/path/to/thrower_daemon|$DAEMON_DIR|g" /etc/systemd/system/superball-thrower.service
|
sudo cp superball-thrower.service /etc/systemd/system/
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
echo "✓ Systemd service installed"
|
sudo systemctl enable superball-thrower.service
|
||||||
|
|
||||||
# Set permissions
|
echo "✅ Systemd service installed and enabled"
|
||||||
echo "Setting permissions..."
|
|
||||||
sudo chown -R "$DAEMON_USER:$DAEMON_USER" "$DAEMON_DIR"
|
|
||||||
sudo chmod +x "$DAEMON_DIR/daemon.js"
|
|
||||||
echo "✓ Permissions set"
|
|
||||||
|
|
||||||
echo
|
# Initialize daemon if no keypair was provided
|
||||||
echo "=== Installation Complete ==="
|
if [ -z "$PRIVATE_KEY" ]; then
|
||||||
echo
|
echo "🔑 Initializing daemon to generate keypair..."
|
||||||
echo "Next steps:"
|
# Create a simple init script that generates keypair
|
||||||
echo "1. Edit the configuration file:"
|
sudo -u "$DAEMON_USER" bash -c "
|
||||||
echo " sudo nano /etc/superball/config.json"
|
cd \"$INSTALL_DIR\"
|
||||||
echo
|
/usr/local/bin/node -e \"
|
||||||
echo "2. Add your private key and configure relays"
|
const { generateSecretKey, getPublicKey } = require('nostr-tools/pure');
|
||||||
echo
|
const fs = require('fs');
|
||||||
echo "3. Enable and start the service:"
|
const config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
|
||||||
echo " sudo systemctl enable superball-thrower"
|
const sk = generateSecretKey();
|
||||||
echo " sudo systemctl start superball-thrower"
|
const pk = getPublicKey(sk);
|
||||||
echo
|
config.thrower.privateKey = Buffer.from(sk).toString('hex');
|
||||||
echo "4. Check service status:"
|
config.thrower.publicKey = pk;
|
||||||
echo " sudo systemctl status superball-thrower"
|
fs.writeFileSync('config.json', JSON.stringify(config, null, 2));
|
||||||
echo
|
console.log('Generated keypair:');
|
||||||
echo "5. View logs:"
|
console.log('Private key:', Buffer.from(sk).toString('hex'));
|
||||||
echo " sudo journalctl -u superball-thrower -f"
|
console.log('Public key:', pk);
|
||||||
echo
|
\"
|
||||||
echo "Configuration file location: /etc/superball/config.json"
|
"
|
||||||
echo "Log file location: /var/log/superball/daemon.log"
|
echo "✅ Keypair generated"
|
||||||
echo "Service name: superball-thrower"
|
fi
|
||||||
echo
|
|
||||||
echo "⚠️ SECURITY WARNING:"
|
# Installation complete
|
||||||
echo " Make sure to secure your private key in the configuration file!"
|
echo ""
|
||||||
echo " The config file should only be readable by the superball user."
|
echo "🎉 Installation completed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "📋 Next steps:"
|
||||||
|
echo " 1. Start the service: sudo systemctl start superball-thrower"
|
||||||
|
echo " 2. Check status: sudo systemctl status superball-thrower"
|
||||||
|
echo " 3. View logs: sudo journalctl -u superball-thrower -f"
|
||||||
|
echo ""
|
||||||
|
echo "📖 For more information, see the README.md file"
|
||||||
|
echo ""
|
||||||
|
echo "🔐 Security notes:"
|
||||||
|
echo " - The daemon runs as user '$DAEMON_USER' with limited privileges"
|
||||||
|
echo " - Configuration files are owned by the daemon user"
|
||||||
|
echo " - Logs are written to $LOG_DIR"
|
||||||
|
echo ""
|
||||||
|
echo "⚙️ Configuration summary:"
|
||||||
|
echo " - Thrower name: $THROWER_NAME"
|
||||||
|
echo " - Description: $THROWER_DESCRIPTION"
|
||||||
|
echo " - Config file: $INSTALL_DIR/config.json"
|
||||||
|
echo ""
|
||||||
@@ -3,9 +3,19 @@
|
|||||||
# Superball Thrower Daemon Installation Script
|
# Superball Thrower Daemon Installation Script
|
||||||
# This script installs and configures the Superball Thrower daemon
|
# This script installs and configures the Superball Thrower daemon
|
||||||
|
|
||||||
sudo apt install nodejs npm
|
# Download and install nvm:
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
|
||||||
|
|
||||||
|
# in lieu of restarting the shell
|
||||||
|
\. "$HOME/.nvm/nvm.sh"
|
||||||
|
|
||||||
|
# Download and install Node.js:
|
||||||
|
nvm install 22
|
||||||
|
|
||||||
|
# Verify the Node.js version:
|
||||||
|
node -v # Should print "v22.20.0".
|
||||||
|
|
||||||
|
# Verify npm version:
|
||||||
|
npm -v # Should print "10.9.3".
|
||||||
|
|
||||||
# Verify installation
|
|
||||||
node --version
|
|
||||||
npm --version
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=superball
|
User=superball-thrower
|
||||||
Group=superball
|
Group=superball-thrower
|
||||||
WorkingDirectory=/path/to/thrower_daemon
|
WorkingDirectory=/opt/superball-thrower
|
||||||
ExecStart=/usr/bin/node daemon.js /etc/superball/config.json
|
ExecStart=/usr/local/bin/node daemon.js start
|
||||||
|
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
@@ -21,14 +22,14 @@ NoNewPrivileges=true
|
|||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
ReadWritePaths=/var/log/superball
|
ReadWritePaths=/var/log/superball-thrower /opt/superball-thrower
|
||||||
ProtectKernelTunables=true
|
ProtectKernelTunables=true
|
||||||
ProtectKernelModules=true
|
ProtectKernelModules=true
|
||||||
ProtectControlGroups=true
|
ProtectControlGroups=true
|
||||||
RestrictRealtime=true
|
RestrictRealtime=true
|
||||||
RestrictSUIDSGID=true
|
RestrictSUIDSGID=true
|
||||||
LockPersonality=true
|
LockPersonality=true
|
||||||
MemoryDenyWriteExecute=true
|
# MemoryDenyWriteExecute=true # Disabled - conflicts with Node.js JIT compilation
|
||||||
RestrictNamespaces=true
|
RestrictNamespaces=true
|
||||||
SystemCallFilter=@system-service
|
SystemCallFilter=@system-service
|
||||||
SystemCallErrorNumber=EPERM
|
SystemCallErrorNumber=EPERM
|
||||||
|
|||||||
@@ -216,11 +216,11 @@ label {
|
|||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,4 +737,55 @@ small {
|
|||||||
|
|
||||||
.floating-tab:hover {
|
.floating-tab:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* .input-group {
|
||||||
|
margin: 20px;
|
||||||
|
} */
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
margin: 20px 20px 0;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 10px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-bottom: none;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content.active {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -29,8 +29,31 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>Final Event (What gets posted at the end)</h2>
|
<h2>Final Event (What gets posted at the end)</h2>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label for="final-content">Message Content:</label>
|
<div class="tabs">
|
||||||
<textarea id="final-content" rows="3" placeholder="Enter your message content..."></textarea>
|
<div class="tab active" data-tab="tab1">Post</div>
|
||||||
|
<div class="tab" data-tab="tab2">Reply</div>
|
||||||
|
<div class="tab" data-tab="tab3">Create Profile</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content active" id="tab1">
|
||||||
|
<h3>Post</h3>
|
||||||
|
<label for="final-content">Message Content:</label>
|
||||||
|
<textarea id="final-content" rows="3" placeholder="Enter your message content..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content" id="tab2">
|
||||||
|
<h3>Reply</h3>
|
||||||
|
<label for="reply-id">Replying To:</label>
|
||||||
|
<textarea id="reply-id" placeholder="Enter the eventId..."></textarea>
|
||||||
|
|
||||||
|
<label for="reply-content">Message Content:</label>
|
||||||
|
<textarea id="reply-content" rows="3" placeholder="Enter your message content..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content" id="tab3">
|
||||||
|
<h3>Create Profile</h3>
|
||||||
|
<label for="name">Name:</label>
|
||||||
|
<textarea id="name" placeholder="Enter your name..."></textarea>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="createFinalEvent()">Create Event That Will Be Published Publicly</button>
|
<button onclick="createFinalEvent()">Create Event That Will Be Published Publicly</button>
|
||||||
|
|
||||||
@@ -79,6 +102,26 @@
|
|||||||
<!-- <script src="./nostr-lite.js"></script> -->
|
<!-- <script src="./nostr-lite.js"></script> -->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const tabs = document.querySelectorAll('.tab');
|
||||||
|
const tabContents = document.querySelectorAll('.tab-content');
|
||||||
|
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
tab.addEventListener('click', () => {
|
||||||
|
// Remove active class from all tabs and contents
|
||||||
|
tabs.forEach(t => t.classList.remove('active'));
|
||||||
|
tabContents.forEach(c => c.classList.remove('active'));
|
||||||
|
|
||||||
|
// Add active class to clicked tab
|
||||||
|
tab.classList.add('active');
|
||||||
|
|
||||||
|
// Show corresponding content
|
||||||
|
const tabId = tab.getAttribute('data-tab');
|
||||||
|
document.getElementById(tabId).classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
// Global variables
|
// Global variables
|
||||||
let nlLite = null;
|
let nlLite = null;
|
||||||
let userPubkey = null;
|
let userPubkey = null;
|
||||||
@@ -232,7 +275,11 @@
|
|||||||
} else {
|
} else {
|
||||||
console.log('INFO', 'No relay list found, using defaults');
|
console.log('INFO', 'No relay list found, using defaults');
|
||||||
userRelays = [
|
userRelays = [
|
||||||
{ url: 'wss://relay.laantungir.net', type: 'both' }
|
{ url: 'wss://relay.laantungir.net', type: 'both' },
|
||||||
|
{ url: 'wss://relay.primal.net', type: 'both' },
|
||||||
|
{ url: 'wss://nos.lol', type: 'both' },
|
||||||
|
{ url: 'wss://relay.damus.io', type: 'both' },
|
||||||
|
{ url: 'wss://offchain.pub', type: 'both' }
|
||||||
];
|
];
|
||||||
return userRelays;
|
return userRelays;
|
||||||
}
|
}
|
||||||
@@ -712,22 +759,77 @@
|
|||||||
|
|
||||||
// Create final event (kind 1)
|
// Create final event (kind 1)
|
||||||
async function createFinalEvent() {
|
async function createFinalEvent() {
|
||||||
const content = document.getElementById('final-content').value.trim();
|
// Get the active tab
|
||||||
|
const activeTab = document.querySelector('.tab.active').getAttribute('data-tab');
|
||||||
|
|
||||||
|
// Get content based on active tab
|
||||||
|
let content = '';
|
||||||
|
let eventId = '';
|
||||||
|
let name = '';
|
||||||
|
|
||||||
|
switch(activeTab) {
|
||||||
|
case 'tab1': // Post
|
||||||
|
content = document.getElementById('final-content').value.trim();
|
||||||
|
break;
|
||||||
|
case 'tab2': // Reply
|
||||||
|
content = document.getElementById('reply-content').value.trim();
|
||||||
|
eventId = document.getElementById('reply-id').value.trim();
|
||||||
|
break;
|
||||||
|
case 'tab3': // Create Profile
|
||||||
|
name = document.getElementById('name').value.trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!content) {
|
// Validate content based on tab
|
||||||
alert('Please enter message content');
|
if (activeTab === 'tab1' || activeTab === 'tab2') {
|
||||||
return;
|
if (!content) {
|
||||||
|
alert('Please enter message content');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (activeTab === 'tab3') {
|
||||||
|
if (!name) {
|
||||||
|
alert('Please enter your name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create the final event (kind 1) - pure message, no relay info
|
let eventTemplate = {};
|
||||||
const eventTemplate = {
|
|
||||||
kind: 1,
|
switch(activeTab) {
|
||||||
content: content,
|
case 'tab1': // Post
|
||||||
tags: [],
|
eventTemplate = {
|
||||||
created_at: Math.floor(Date.now() / 1000)
|
kind: 1,
|
||||||
};
|
content: content,
|
||||||
|
tags: [],
|
||||||
|
created_at: Math.floor(Date.now() / 1000)
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tab2': // Reply
|
||||||
|
eventTemplate = {
|
||||||
|
kind: 1,
|
||||||
|
content: content,
|
||||||
|
tags: [['e', eventId, 'root']],
|
||||||
|
created_at: Math.floor(Date.now() / 1000)
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tab3': // Create Profile
|
||||||
|
eventTemplate = {
|
||||||
|
kind: 0,
|
||||||
|
content: JSON.stringify({
|
||||||
|
name: name
|
||||||
|
}),
|
||||||
|
tags: [],
|
||||||
|
created_at: Math.floor(Date.now() / 1000)
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Your existing event publishing logic here
|
||||||
|
console.log('Event to publish:', eventTemplate);
|
||||||
|
// ... rest of your publishing code
|
||||||
// Sign the event using window.nostr (NIP-07)
|
// Sign the event using window.nostr (NIP-07)
|
||||||
finalEvent = await window.nostr.signEvent(eventTemplate);
|
finalEvent = await window.nostr.signEvent(eventTemplate);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user