- JavaScript 50.7%
- LSL 48.4%
- Dockerfile 0.9%
| docker-compose.yml | ||
| Dockerfile | ||
| Item Delevery Server.lsl | ||
| LICENSE | ||
| package.json | ||
| README.md | ||
| server.js | ||
Item-Delevery-Server
Second Life Item Delivery System
A Docker-based Node.js backend service that handles item delivery requests for Second Life objects, featuring harvest management, idempotency controls, and secure URL registration. Overview
This system bridges Second Life (SL) objects with an external HTTP backend to handle item deliveries, crop harvesting, and inventory management. The backend maintains persistent state across region restarts and prevents duplicate deliveries through idempotency tracking. Architecture
Backend Service (Node.js/Express)
Registers and stores HTTP-in URLs from SL objects
Manages crop reward tables (server-authoritative)
Enforces harvest idempotency with configurable TTL
Forwards delivery requests back to SL ItemServer objects
SL ItemServer Script (LSL)
Requests HTTP-in URL and registers with backend
Handles region restarts gracefully
Delivers inventory items to avatars
Supports multiple delivery formats (single item, multi-item, legacy crop types)
Features
Harvest Idempotency: Prevents duplicate item deliveries using unique harvest IDs with automatic pruning
Server-Authoritative Rewards: Modify crop rewards without updating in-world scripts
URL Registration: Automatic HTTP-in URL lifecycle management with retry logic
Security: Optional shared secret authentication between services
Object Allowlist: Restrict which SL objects can register URLs
Persistent Storage: JSON file-backed data persistence across container restarts
Multi-Item Delivery: Support for delivering multiple items in a single transaction
Quick Start Prerequisites
Docker and Docker Compose
Nginx Proxy Manager (or modify networking configuration)
Second Life object with appropriate permissions
Deployment
Clone the repository and navigate to the project directory
Configure environment variables in docker-compose.yml:
text environment:
-
SHARED_SECRET=YourSecretHere # Match this in LSL script
-
ALLOWLIST= # Optional: comma-separated object UUIDs
-
TZ=Etc/GMT+5 # Your timezone
Build and start the container:
bash docker-compose up -d
The service will be available on port 3000 and connected to the nginx network at 172.29.0.35
Configure the LSL script:
Update API_URL and SERVER_URL to your domain
Set SHARED_SECRET to match your backend configuration
Add the script to your SL object's inventory
API Endpoints GET /
Health check endpoint returning service status and statistics.
Response:
json { "ok": true, "service": "sl-item-backend", "time": "2026-01-20T19:00:00.000Z", "knownObjects": 1, "harvestIdsCached": 42 }
POST /update-sl-url
Register an SL object's HTTP-in URL.
Request:
json { "sl_url": "https://sim1234.agni.lindenlab.com:12345/...", "object_uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" }
Response:
json { "ok": true, "key": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "sl_url": "https://..." }
POST /deliver
Legacy delivery endpoint supporting single or multiple items.
Single Item Request:
json { "object_uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "user_id": "ffffffff-eeee-dddd-cccc-bbbbbbbbbbbb", "item": "Corn", "qty": 2, "harvestId": "optional-unique-id" }
Multi-Item Request:
json { "object_uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "user_id": "ffffffff-eeee-dddd-cccc-bbbbbbbbbbbb", "items": [ {"name": "Corn", "qty": 2}, {"name": "Hay Bundle", "qty": 1} ] }
POST /harvest
Server-authoritative harvest endpoint with idempotency.
Request:
json { "object_uuid": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "user_id": "ffffffff-eeee-dddd-cccc-bbbbbbbbbbbb", "cropId": "HAY", "harvestId": "unique-harvest-identifier" }
Response (first harvest):
json { "ok": true, "duplicate": false, "cropId": "HAY", "delivered": { "items": [{"name": "Hay Bundle", "qty": 1}] }, "deliveredText": "1x Hay Bundle", "harvestId": "unique-harvest-identifier" }
Response (duplicate harvest):
json { "ok": true, "duplicate": true, "cropId": "HAY", "delivered": { "items": [{"name": "Hay Bundle", "qty": 1}] }, "deliveredText": "1x Hay Bundle" }
Configuration Environment Variables Variable Default Description PORT 3000 HTTP server port SHARED_SECRET "" Optional authentication secret ALLOWLIST "" Comma-separated object UUIDs DATA_DIR /data Persistent storage directory HARVEST_TTL_MS 604800000 Harvest ID cache duration (7 days) HARVEST_MAX_RECORDS 5000 Maximum cached harvest records TZ Etc/GMT+5 Container timezone Crop Rewards
Edit the CROPS object in server.js to modify rewards:
javascript const CROPS = { HAY: { items: [{ name: "Hay Bundle", qty: 1 }], }, CORN: { items: [{ name: "Corn", qty: 1 }], }, WHEAT: { items: [ { name: "Wheat Bundle", qty: 2 }, { name: "Wheat Seed", qty: 1 } ] }, };
Changes take effect immediately without restarting SL objects. LSL Script Configuration
Update these variables in Item-Delevery-Server.lsl:
text string API_URL = "http://items.hijinkshaven.com/update-sl-url"; string SERVER_URL = "http://items.hijinkshaven.com/"; string SHARED_SECRET = "Hijinx"; // Must match backend float URL_RETRY_SECONDS = 30.0;
Data Persistence
The system persists data in two JSON files under /data (mapped to ./data on host):
urls.json: Registered SL object HTTP-in URLs
harvests.json: Harvest idempotency cache with timestamps
Data survives container restarts and is automatically pruned based on TTL and max record limits. LSL Script Features
The ItemServer script supports three delivery formats:
Single Item:
json { "secret": "Hijinx", "user_id": "<avatar_uuid>", "item": "Corn", "qty": 2, "harvestId": "optional" }
Multiple Items:
json { "secret": "Hijinx", "user_id": "<avatar_uuid>", "items": [ {"name": "Corn", "qty": 2}, {"name": "Hay Bundle", "qty": 1} ], "harvestId": "optional" }
Legacy Crop Type:
json { "secret": "Hijinx", "user_id": "<avatar_uuid>", "crop_type": "Corn" }
Region Restart Handling
The script automatically detects region restarts via CHANGED_REGION_START and requests a new HTTP-in URL, then re-registers with the backend. Networking
The default configuration uses an external Docker network named nginx with a static IP assignment. Modify the docker-compose.yml network settings if using a different proxy setup:
text networks: nginx: external: true
Dependencies
Node.js Runtime:
express ^4.19.2 - HTTP server framework
axios ^1.7.9 - HTTP client for SL requests
Docker Base Image:
node:20-alpine - Lightweight Node.js 20 environment
Security Considerations
Set a strong SHARED_SECRET for production deployments
Use the ALLOWLIST to restrict which objects can register URLs
Consider using llRequestSecureURL() in LSL for HTTPS endpoints
The backend validates all incoming requests and returns appropriate error codes
15-second timeout on SL HTTP requests prevents hanging connections
Troubleshooting
Container won't start:
Verify the nginx network exists: docker network ls
Check if port 3000 is available
Review logs: docker logs sl-item-server
SL object not registering URL:
Confirm API_URL in LSL matches your backend
Check SHARED_SECRET matches between LSL and backend
Verify object UUID is in ALLOWLIST if configured
Look for error messages in SL object's owner chat
Duplicate deliveries:
Ensure unique harvestId values are generated per harvest
Check harvest cache isn't being cleared prematurely
Verify HARVEST_TTL_MS is appropriate for your use case
Items not delivered:
Confirm item names in CROPS match exact inventory names
Check SL object has items in inventory
Review backend logs for forwarding errors
License
This project is provided as-is for use with Second Life item delivery systems.