Files
427e7578-d7bf-49c8-aee9-2dd…/README.md
2026-03-16 00:03:39 +01:00

12 KiB
Raw Permalink Blame History

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
  2. Architecture
  3. Features
  4. Setup & Installation
  5. How It Works
  6. Question Types
  7. Technology Stack
  8. Limitations
  9. Self-Hosting the PeerJS Signaling Server
  10. Development Commands
  11. 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 15 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

# 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

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 (15) 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

npm install -g peer
peerjs --port 9000 --key peerjs

Option 2: Docker

docker run -p 9000:9000 peerjs/peerjs-server

Option 3: Node.js

// 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:

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

# 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