# 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 ```