EmergencyBox uses a simple client-server architecture optimized for embedded systems:
┌─────────────────────────────────────────┐
│ Client (Browser) │
│ ┌─────────────────────────────────┐ │
│ │ HTML/CSS (index.html) │ │
│ │ JavaScript (app.js) │ │
│ └─────────────────────────────────┘ │
└──────────────┬──────────────────────────┘
│ HTTP/AJAX
│
┌──────────────▼──────────────────────────┐
│ Web Server (lighttpd) │
│ ┌─────────────────────────────────┐ │
│ │ FastCGI → PHP │ │
│ │ Static files │ │
│ └─────────────────────────────────┘ │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ Backend (PHP APIs) │
│ ┌─────────────────────────────────┐ │
│ │ send_message.php │ │
│ │ get_messages.php │ │
│ │ upload.php │ │
│ │ get_files.php │ │
│ └─────────────────────────────────┘ │
└──────────────┬──────────────────────────┘
│
┌──────────────▼──────────────────────────┐
│ Data Layer (SQLite3) │
│ ┌─────────────────────────────────┐ │
│ │ messages table │ │
│ │ files table │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
- HTML5: Semantic markup
- CSS3: Responsive grid layout, flexbox
- Vanilla JavaScript: No dependencies for maximum offline reliability
- AJAX: XMLHttpRequest for file uploads, Fetch API for other requests
- PHP 7.4+: Server-side logic
- SQLite3: Embedded database
- lighttpd: Lightweight web server
- FastCGI: PHP execution
emergencybox/
├── www/ # Web root
│ ├── index.html # Main interface
│ ├── css/
│ │ └── style.css # All styles
│ ├── js/
│ │ └── app.js # Frontend application
│ ├── api/ # PHP backend
│ │ ├── config.php # Configuration & database init
│ │ ├── init_db.php # Database setup script
│ │ ├── send_message.php # POST message API
│ │ ├── get_messages.php # GET messages API
│ │ ├── clear_chat.php # DELETE messages API
│ │ ├── upload.php # POST file upload API
│ │ └── get_files.php # GET files list API
│ └── uploads/ # File storage
│ ├── emergency/
│ ├── media/
│ ├── documents/
│ └── general/
├── config/ # Server configuration
│ ├── php.ini # PHP settings
│ └── lighttpd.conf # Web server config
├── docs/ # Documentation
│ ├── INSTALLATION.md
│ ├── USAGE.md
│ └── DEVELOPMENT.md
└── README.md
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message TEXT NOT NULL,
priority INTEGER DEFAULT 0, -- 0 = normal, 1 = priority
file_id INTEGER DEFAULT NULL, -- FK to files.id
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);CREATE TABLE files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, -- Original filename
path TEXT NOT NULL, -- Relative path from web root
category TEXT NOT NULL, -- Folder/category name
size INTEGER NOT NULL, -- File size in bytes
uploaded DATETIME DEFAULT CURRENT_TIMESTAMP
);Send a new message to the chat.
Request:
{
"message": "Emergency shelter at coordinates...",
"priority": 1,
"file_id": 42
}Response:
{
"success": true,
"message_id": 123
}Retrieve all messages (latest 100).
Response:
{
"success": true,
"messages": [
{
"id": 1,
"message": "System online",
"priority": 0,
"timestamp": "2025-01-10 12:00:00",
"file_id": null,
"file_name": null,
"file_path": null,
"file_size": null
}
]
}Clear all chat messages.
Response:
{
"success": true
}Upload a file.
Request: multipart/form-data
file: File datacategory: Category name (emergency, media, documents, general, custom)custom_folder: Required if category is "custom"
Response:
{
"success": true,
"file_id": 42,
"file_name": "evacuation_map.pdf",
"file_path": "uploads/emergency/evacuation_map.pdf",
"file_size": 2048576
}Get list of all files.
Response:
{
"success": true,
"files": [
{
"id": 1,
"name": "evacuation_map.pdf",
"path": "uploads/emergency/evacuation_map.pdf",
"category": "emergency",
"size": 2048576,
"uploaded": "2025-01-10 12:00:00"
}
]
}Located in www/js/app.js, this class manages all frontend functionality.
Key Methods:
init(): Initialize application and event listenerssendMessage(): Send chat message via APIloadMessages(): Fetch and render messagesuploadFile(): Handle file upload with progress trackingloadFiles(): Fetch and render file listopenFileLinkModal(): Show file selection modalstartPolling(): Auto-refresh messages and files every 2 seconds
The app uses simple polling instead of WebSockets for maximum compatibility:
startPolling() {
setInterval(() => {
this.loadMessages();
this.loadFiles();
}, 2000); // Every 2 seconds
}Why polling?
- Simpler implementation
- No WebSocket server needed
- Works on all browsers
- Minimal server load with SQLite
-
Create folder:
mkdir /opt/share/www/uploads/your-category chmod 777 /opt/share/www/uploads/your-category
-
Add to select dropdown in
www/index.html:<option value="your-category">Your Category</option>
Edit www/api/get_messages.php:
// Change LIMIT 100 to your desired number
$query = "SELECT ... LIMIT 200";Edit CSS variables in www/css/style.css:
:root {
--primary-color: #2563eb; /* Change to your color */
--danger-color: #dc2626;
--success-color: #16a34a;
/* ... */
}Edit www/api/upload.php:
// After line where $file is defined, add:
$allowed_types = ['image/jpeg', 'image/png', 'application/pdf'];
$file_type = mime_content_type($file['tmp_name']);
if (!in_array($file_type, $allowed_types)) {
handleError('File type not allowed');
}-
Update PHP config (
config/php.ini):upload_max_filesize = 10G post_max_size = 10G
-
Update lighttpd config (
config/lighttpd.conf):server.max-request-size = 10737418240 # 10GB in bytes -
Update constant in
www/api/config.php:define('MAX_FILE_SIZE', 10 * 1024 * 1024 * 1024); // 10GB
-
Restart services
You can test EmergencyBox on your development machine:
cd www
php -S localhost:8000Then:
- Initialize database:
php api/init_db.php - Open browser:
http://localhost:8000
Note: File uploads >2GB may not work with built-in server.
Create Dockerfile:
FROM php:7.4-apache
RUN apt-get update && apt-get install -y sqlite3 libsqlite3-dev
RUN docker-php-ext-install pdo pdo_sqlite
COPY www/ /var/www/html/
RUN chown -R www-data:www-data /var/www/html
EXPOSE 80Build and run:
docker build -t emergencybox .
docker run -p 8080:80 emergencyboxIf you have many concurrent users, increase the polling interval:
// In www/js/app.js
this.pollInterval = 5000; // 5 seconds instead of 2Add indexes for faster queries:
CREATE INDEX idx_messages_timestamp ON messages(timestamp);
CREATE INDEX idx_files_category ON files(category);
CREATE INDEX idx_files_uploaded ON files(uploaded);For very large files, implement chunked uploads to avoid timeouts:
- Split file into chunks client-side
- Upload chunks sequentially
- Reassemble server-side
While EmergencyBox is designed for offline use, you can add security:
Add to lighttpd.conf:
auth.backend = "plain"
auth.backend.plain.userfile = "/opt/etc/lighttpd.users"
auth.require = ( "/" => ("method" => "basic", "realm" => "EmergencyBox", "require" => "valid-user"))
Add rate limiting to PHP APIs:
// In config.php
function checkRateLimit($ip, $action, $limit = 10, $window = 60) {
// Implement rate limiting logic
}All APIs already sanitize input, but you can add stricter validation:
// Example: Restrict message content
if (preg_match('/[^a-zA-Z0-9\s\.,!?-]/', $message)) {
handleError('Invalid characters in message');
}-
Store nickname in localStorage:
const nickname = localStorage.getItem('nickname') || 'Anonymous';
-
Send with message:
const data = { message: message, nickname: nickname, // ... };
-
Update database schema:
ALTER TABLE messages ADD COLUMN nickname TEXT DEFAULT 'Anonymous';
- Create API endpoint
delete_file.php - Add delete button to file items
- Update database and filesystem
- Generate thumbnails on upload
- Store thumbnail path in database
- Display thumbnails in file browser
Edit config/php.ini:
display_errors = On
error_reporting = E_ALL# lighttpd errors
tail -f /opt/var/log/lighttpd/error.log
# PHP errors
tail -f /tmp/php_errors.logOpen browser developer tools (F12) and check:
- Console for JavaScript errors
- Network tab for API request/response
- Application tab for localStorage
To contribute to EmergencyBox:
- Fork the repository
- Create a feature branch
- Test thoroughly on actual router hardware
- Submit pull request with detailed description
EmergencyBox is open source and free for humanitarian use.