Skip to content

Repository files navigation

stadtmodell-interface-sose26

Interactive exhibition prototype — three synchronized browser windows share state via a central WebSocket server. The controller window on machine 2 drives exhibit switching; two display windows on machine 1 react in real time.

Architecture

┌────────────────────────────────────────────────────────────────┐
│                          Machine 1                             │
│                                                                │
│  ┌─────────────────┐     ┌─────────────────┐                  │
│  │  app-display-1  │     │  app-display-2  │                  │
│  │   :3002         │     │   :3003         │                  │
│  │  ┌───────────┐  │     │  ┌───────────┐  │                  │
│  │  │  iframe   │  │     │  │  iframe   │  │                  │
│  │  └───────────┘  │     │  └───────────┘  │                  │
│  └────────┬────────┘     └────────┬────────┘                  │
│           │ socket.io             │ socket.io                  │
│           └──────────┬────────────┘                            │
│                      │                                         │
│              ┌───────▼──────┐    ┌──────────────┐             │
│              │    server    │    │   sub-apps   │             │
│              │    :3000     │    │    :4000     │             │
│              └───────┬──────┘    └──────────────┘             │
└──────────────────────┼─────────────────────────────────────────┘
                       │ socket.io
┌──────────────────────┼─────────────────────────────────────────┐
│                      │       Machine 2                         │
│           ┌──────────▼──────────┐                              │
│           │    app-controller   │                              │
│           │        :3001        │                              │
│           │  ┌───────────────┐  │                              │
│           │  │ [Def][Liv][Spo][Cul] │  ← exhibit menu        │
│           │  ├───────────────┤  │                              │
│           │  │    iframe     │  │                              │
│           │  └───────────────┘  │                              │
│           └─────────────────────┘                              │
└────────────────────────────────────────────────────────────────┘

State flow

  1. User clicks a menu button in the controller (machine 2)
  2. Controller sends stateChange to the socket server (machine 1)
  3. Server broadcasts stateUpdate to all connected clients
  4. Each main app fades out the old iframe, fades in the new one
  5. The active iframe receives the full state via postMessage
  6. Sub-apps can also send state changes upward via window.parent.postMessage

Ports

Service Machine Port Description
server 1 3000 Socket.io state server
app-display-1 1 3002 Display app — first window
app-display-2 1 3003 Display app — second window
sub-apps 1 4000 Static server for all sub-app boilerplates
app-controller 2 3001 Controller app with exhibit menu

Project structure

├── config.json               # ← set serverIp here before running on two machines
├── server/                   # Socket.io state server
│   └── index.js
├── app-controller/           # Controller app (menu + iframe) — machine 2
│   ├── server.js
│   └── public/
│       ├── index.html
│       └── app.js
├── app-display-1/            # Display app — machine 1
│   ├── server.js
│   └── public/
│       ├── index.html
│       └── app.js
├── app-display-2/            # Display app — machine 1
│   ├── server.js
│   └── public/
│       ├── index.html
│       └── app.js
└── sub-apps/                 # 12 sub-app boilerplates (4 exhibits × 3 views)
    ├── server.js
    ├── default/            # Blue theme — pulsing rings
    │   ├── controller/index.html
    │   ├── display-1/index.html
    │   └── display-2/index.html
    ├── living/             # Green theme — animated bar chart
    │   ├── controller/index.html
    │   ├── display-1/index.html
    │   └── display-2/index.html
    ├── sports/             # Red theme — rotating dial
    │   ├── controller/index.html
    │   ├── display-1/index.html
    │   └── display-2/index.html
    └── culture/            # Purple theme — rotating crystal
        ├── controller/index.html
        ├── display-1/index.html
        └── display-2/index.html

Setup

1. Install dependencies

Run once on both machines after cloning the repo:

npm run install:all

2. Set the server IP

Edit config.json at the root of the project and set serverIp to machine 1's IP address on the local network:

{
  "serverIp": "192.168.1.100"
}

For single-machine testing leave it as "localhost".

All apps read this file — no environment variables needed.

Running

Single machine (local development / testing)

Starts all five servers in one terminal with colour-coded output:

npm start

Then open all three app windows:

npm run open:apps          # Chrome
npm run open:apps:safari   # Safari

Two machines (exhibition setup)

Machine 1 — after setting serverIp in config.json:

npm run start:machine-1
npm run open:apps:machine-1   # opens display-1 and display-2

Machine 2 — copy the repo (with the updated config.json) to machine 2, then:

npm run start:machine-2
npm run open:apps:machine-2   # opens the controller

Both machines must be on the same network.

Extending

Adding state

The global state object lives in server/index.js:

let state = {
  activeExhibit: 'default'   // add more fields here
};

Any connected client can patch it:

socket.emit('stateChange', { myNewField: 'value' });

All clients (and their active iframes via postMessage) receive the merged update immediately.

Adding an exhibit

  1. Create a new folder under sub-apps/ (e.g. nature/) with three sub-folders, each containing an index.html: controller/, display-1/, display-2/.
  2. Add <iframe id="iframe-nature"> to each main app's index.html.
  3. Add 'nature' to the EXHIBITS array in each main app's app.js.
  4. Add a menu button in app-controller/public/index.html with data-exhibit="nature".

Sub-app → parent communication

Sub-apps can trigger global state changes without a direct socket connection:

window.parent.postMessage({
  type: 'stateChange',
  payload: { activeExhibit: 'living' }
}, '*');

The parent main app relays this to the socket server, which broadcasts to all clients.

Receiving state in a sub-app

window.addEventListener('message', (event) => {
  if (event.data?.type === 'stateUpdate') {
    const state = event.data.payload;
    // update your UI
  }
});

Crash isolation

Each sub-app runs inside an <iframe>. JavaScript errors thrown inside an iframe do not propagate to the parent window, so a broken sub-app cannot crash the main display. All three iframes are pre-loaded at startup and toggled with CSS opacity — sub-app state is preserved across exhibit switches.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages