348 lines
12 KiB
Markdown
348 lines
12 KiB
Markdown
# P2P Survey App
|
||
|
||
A serverless peer-to-peer survey application built with Ionic and Angular.
|
||
Survey creators store all data locally in their browser and participants connect
|
||
directly via WebRTC — no backend server required.
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Overview](#overview)
|
||
2. [Architecture](#architecture)
|
||
3. [Features](#features)
|
||
4. [Setup & Installation](#setup--installation)
|
||
5. [How It Works](#how-it-works)
|
||
- [Creator Flow](#creator-flow)
|
||
- [Participant Flow](#participant-flow)
|
||
6. [Question Types](#question-types)
|
||
7. [Technology Stack](#technology-stack)
|
||
8. [Limitations](#limitations)
|
||
9. [Self-Hosting the PeerJS Signaling Server](#self-hosting-the-peerjs-signaling-server)
|
||
10. [Development Commands](#development-commands)
|
||
11. [Technology Choices](#technology-choices)
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
P2P Survey App lets you create and distribute surveys without any server-side
|
||
infrastructure. All survey data — questions, participant tokens, and responses —
|
||
lives exclusively in the survey creator's browser (IndexedDB). Participants
|
||
connect directly to the creator's browser using WebRTC peer-to-peer data
|
||
channels, facilitated by the PeerJS library.
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
```
|
||
Creator Browser (Host)
|
||
┌─────────────────────────────────────────────┐
|
||
│ Ionic/Angular App │
|
||
│ ┌─────────────────┐ ┌──────────────────┐ │
|
||
│ │ PeerService │ │ Dexie (IndexedDB)│ │
|
||
│ │ (PeerJS/WebRTC)│<─>│ surveys │ │
|
||
│ └────────┬────────┘ │ participants │ │
|
||
│ │ │ responses │ │
|
||
│ Peer ID: survey-{id}└──────────────────┘ │
|
||
└───────────┼─────────────────────────────────┘
|
||
│
|
||
[PeerJS Cloud – signaling only, not data relay]
|
||
│
|
||
Participant Browser
|
||
┌─────────────────────────────────────────────┐
|
||
│ Opens: /participate?host=survey-{id} │
|
||
│ &token={uuid} │
|
||
│ │
|
||
│ 1. Connect to host peer via WebRTC │
|
||
│ 2. Send { type: 'join', token } │
|
||
│ 3. Receive survey questions │
|
||
│ 4. Fill in answers │
|
||
│ 5. Send { type: 'submit', answers } │
|
||
└─────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Star topology
|
||
|
||
The survey creator acts as the central hub. Each participant opens a direct
|
||
WebRTC data channel to the creator's browser. The creator's browser validates
|
||
tokens, stores responses, and optionally pushes aggregated results back.
|
||
|
||
The PeerJS signaling server is only used for the initial WebRTC handshake
|
||
(exchanging ICE candidates). Once connected, all data flows directly between
|
||
the two browsers — the signaling server never sees response data.
|
||
|
||
---
|
||
|
||
## Features
|
||
|
||
- **Create surveys** with four question types: free text, multiple choice,
|
||
yes/no, and 1–5 rating
|
||
- **Generate unique participant links** — each link contains a UUID token that
|
||
identifies one participant
|
||
- **Token-based access control** — each token can only be submitted once;
|
||
reuse is rejected by the host
|
||
- **Draft saving** — participants can save answers before final submission
|
||
without locking their token
|
||
- **Live results** — the creator sees responses update in real time using
|
||
Dexie's `liveQuery`
|
||
- **Optional results sharing** — the creator can toggle whether participants
|
||
see aggregated results after submitting
|
||
- **CSV export** — download all responses as a comma-separated file
|
||
- **Local-only storage** — all data lives in the creator's IndexedDB; delete
|
||
the survey and the data is gone
|
||
|
||
---
|
||
|
||
## Setup & Installation
|
||
|
||
### Prerequisites
|
||
|
||
- Node.js 18+ and npm 9+
|
||
- A modern browser (Chrome, Firefox, Edge, Safari)
|
||
|
||
### Install
|
||
|
||
```bash
|
||
# 1. Install Ionic CLI globally (skip if already installed)
|
||
npm install -g @ionic/cli
|
||
|
||
# 2. Install project dependencies
|
||
npm install
|
||
|
||
# 3. Start the development server
|
||
ionic serve
|
||
```
|
||
|
||
The app opens at `http://localhost:8100`.
|
||
|
||
### Build for production
|
||
|
||
```bash
|
||
ionic build --prod
|
||
```
|
||
|
||
Output is written to `www/`. Deploy the contents of `www/` to any static
|
||
web host (GitHub Pages, Netlify, Vercel, Nginx, etc.).
|
||
|
||
---
|
||
|
||
## How It Works
|
||
|
||
### Creator Flow
|
||
|
||
1. **Open the app** and click **Create Survey**.
|
||
2. **Enter a title** (required) and optional description.
|
||
3. **Add questions** — choose a type for each and mark which are required.
|
||
4. Click **Create** to save the survey to IndexedDB and navigate to the
|
||
**Survey Detail** page.
|
||
5. On the detail page:
|
||
- Toggle **Show results to participants** if desired.
|
||
- Enter a number and click **Generate Links** to create unique participant
|
||
tokens. Copy links to share.
|
||
- Click **Start Hosting** to initialise the PeerJS peer. The app listens
|
||
for incoming connections using the peer ID `survey-{surveyId}`.
|
||
6. **Keep this page or the Results page open** while collecting responses.
|
||
If the creator navigates away, the peer is destroyed and participants
|
||
cannot connect.
|
||
7. Click **View Results** to see live aggregated and individual responses.
|
||
8. Click **Export CSV** to download all responses.
|
||
|
||
### Participant Flow
|
||
|
||
1. **Open the unique link** shared by the survey creator.
|
||
2. The app parses `host` and `token` from the URL query parameters.
|
||
3. A WebRTC connection is established to the host's peer ID.
|
||
4. The participant sends `{ type: 'join', token }`.
|
||
5. The host validates the token:
|
||
- **Invalid token** → error card is shown.
|
||
- **Already submitted** → error card is shown (token is locked).
|
||
- **Valid** → survey questions are sent back.
|
||
6. Participant fills in answers. Answers are auto-saved as drafts after each
|
||
blur event (free text) or selection change (other types).
|
||
7. Click **Submit** to finalise. The token is locked on the host side.
|
||
8. If the creator enabled result sharing, aggregated results are shown.
|
||
|
||
---
|
||
|
||
## Question Types
|
||
|
||
| Type | Input widget | Aggregation |
|
||
|---|---|---|
|
||
| **Free Text** | Multi-line textarea | List of all answers |
|
||
| **Multiple Choice** | Radio buttons (creator-defined options) | Count per option + bar chart |
|
||
| **Yes / No** | Radio buttons (Yes / No) | Count per option + bar chart |
|
||
| **Rating** | 5 buttons (1–5) | Average score + distribution bar chart |
|
||
|
||
---
|
||
|
||
## Technology Stack
|
||
|
||
| Library | Version | Role |
|
||
|---|---|---|
|
||
| `@ionic/angular` | 8.x | Mobile-first UI components |
|
||
| `@angular/core` | 20.x | Application framework |
|
||
| `peerjs` | 1.5.x | WebRTC data channel wrapper |
|
||
| `dexie` | 4.x | IndexedDB wrapper with liveQuery |
|
||
| `qrcode` | 1.5.x | QR code generation (optional usage) |
|
||
|
||
---
|
||
|
||
## Limitations
|
||
|
||
### Host must be online
|
||
|
||
The survey creator's browser **must be open** and on the Survey Detail or
|
||
Results page for participants to submit responses. There is no server that
|
||
relays or buffers messages. If the creator goes offline, participants see a
|
||
"Host is Offline" card.
|
||
|
||
### Data loss
|
||
|
||
All data is stored in the creator's browser IndexedDB. Data is lost if:
|
||
- The creator explicitly deletes the survey
|
||
- The browser's storage is cleared (private/incognito mode, clearing site data)
|
||
- The browser's IndexedDB quota is exceeded
|
||
|
||
Export responses to CSV regularly to prevent data loss.
|
||
|
||
### PeerJS Cloud rate limits
|
||
|
||
The free PeerJS Cloud signaling server (`0.peerjs.com`) has rate limits and
|
||
may occasionally be unavailable. For production use, self-host the signaling
|
||
server (see below).
|
||
|
||
### NAT traversal
|
||
|
||
WebRTC uses STUN to traverse most NAT configurations. Strict corporate
|
||
firewalls or symmetric NAT may block direct connections. For full reliability
|
||
in such environments, add a TURN server to the PeerJS configuration in
|
||
`src/app/services/peer.service.ts`.
|
||
|
||
### Browser compatibility
|
||
|
||
Requires a browser that supports:
|
||
- WebRTC (DataChannels)
|
||
- IndexedDB
|
||
- `crypto.randomUUID()`
|
||
|
||
All modern browsers (Chrome 86+, Firefox 78+, Safari 15.4+, Edge 86+) satisfy
|
||
these requirements.
|
||
|
||
---
|
||
|
||
## Self-Hosting the PeerJS Signaling Server
|
||
|
||
For production deployments, run your own PeerJS server to avoid rate limits.
|
||
|
||
### Option 1: npm
|
||
|
||
```bash
|
||
npm install -g peer
|
||
peerjs --port 9000 --key peerjs
|
||
```
|
||
|
||
### Option 2: Docker
|
||
|
||
```bash
|
||
docker run -p 9000:9000 peerjs/peerjs-server
|
||
```
|
||
|
||
### Option 3: Node.js
|
||
|
||
```javascript
|
||
// server.js
|
||
const { PeerServer } = require('peer');
|
||
const server = PeerServer({ port: 9000, path: '/peerjs' });
|
||
```
|
||
|
||
### Configure the app to use your server
|
||
|
||
Edit `src/app/services/peer.service.ts` and update the `Peer` constructor:
|
||
|
||
```typescript
|
||
this.peer = new Peer(peerId, {
|
||
host: 'your-peer-server.example.com',
|
||
port: 9000,
|
||
path: '/peerjs',
|
||
secure: true, // use wss:// if your server has TLS
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Development Commands
|
||
|
||
```bash
|
||
# Start dev server with live reload
|
||
ionic serve
|
||
|
||
# Build for production
|
||
ionic build --prod
|
||
|
||
# Run TypeScript type check
|
||
npx tsc --noEmit
|
||
|
||
# Lint
|
||
npx ng lint
|
||
|
||
# Build Angular app only (no Ionic wrapper)
|
||
npx ng build
|
||
```
|
||
|
||
---
|
||
|
||
## Technology Choices
|
||
|
||
### PeerJS over Gun.js
|
||
|
||
Gun.js is a decentralized graph database that syncs between peers. However,
|
||
a survey has a **star topology** (one host, many participants) — not a mesh
|
||
where every peer is equal. PeerJS maps directly to this model: the creator
|
||
opens one peer ID, participants connect to it, and all data flows through
|
||
that central point. Token validation and response deduplication are trivial
|
||
to enforce at the host. Gun.js would require additional complexity to achieve
|
||
the same guarantees.
|
||
|
||
### Dexie.js over raw IndexedDB
|
||
|
||
Raw IndexedDB uses a callback-based API that is verbose and error-prone.
|
||
Dexie wraps it in clean Promises, adds a TypeScript-friendly schema
|
||
definition, and provides `liveQuery` — a reactive subscription mechanism
|
||
that automatically re-runs queries when the underlying data changes. This
|
||
powers the live results view without any manual polling or event wiring.
|
||
|
||
### Module-based Angular over Standalone Components
|
||
|
||
The Ionic CLI scaffolds a module-based project by default with Angular 20.
|
||
Module-based components provide a clear separation of concern via `NgModule`
|
||
declarations and are well-supported by the Ionic ecosystem.
|
||
|
||
---
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
src/
|
||
└── app/
|
||
├── shared/
|
||
│ └── models/
|
||
│ └── survey.models.ts # All TypeScript interfaces + P2PMessage type
|
||
├── database/
|
||
│ └── database.ts # Dexie singleton (AppDatabase)
|
||
├── services/
|
||
│ ├── peer.service.ts # PeerJS wrapper (NgZone-aware)
|
||
│ ├── survey.service.ts # Survey/participant CRUD
|
||
│ └── response.service.ts # Response storage + aggregation
|
||
├── pages/
|
||
│ ├── home/ # Survey list
|
||
│ ├── create-survey/ # Survey creation/editing
|
||
│ ├── survey-detail/ # Settings, link generation, hosting
|
||
│ ├── survey-results/ # Live results view
|
||
│ └── participate/ # Participant survey form
|
||
├── app-routing.module.ts
|
||
├── app.component.ts
|
||
└── app.module.ts
|
||
```
|