# Backup Answer-Sending Systems in `contestInterface/common.js`

This document describes all mechanisms used to ensure contestant answers are transmitted to the server, even when the primary system fails.

---

## Overview

| # | System | Function(s) | Endpoint / Target | Trigger Condition | Data Sent | User Visibility |
|---|--------|-------------|-------------------|-------------------|-----------|-----------------|
| 1 | Primary POST | `submitAnswer()` (~L1399) → `sendAnswers()` (~L1375) | `answer.php` (same origin) | Called on every answer submission via `submitAnswer()`. Retried periodically from `failedSendingAnswers()` (~L1383) with 60s delay (`delaySendingAttempts`). | `{ SID, answers, teamID, teamPassword, sendLastActivity, browserID }`, plus `finalAnswersSent` when `closingContest` is true. | Hidden (AJAX). No visible UI to the student. |
| 2 | Alternate server POST | `sendAnswers()` (URL swap, ~L1420) | `https://concours4.castor-informatique.fr/answer.php` | `sendAnswersTryAlternate` toggles `true`/`false` on each `failedSendingAnswers()` call (~L1389). When `true`, `sendAnswers()` uses this URL instead of local `answer.php`. Creates an **alternating retry** between servers. | Same payload as primary. | Hidden (AJAX). No user indication that a different server is used. |
| 3 | Image beacon | `backupSendAnswers()` (~L1457) + `getEncodedAnswers()` (~L1469) | `https://backup.castor-informatique.fr/?q=<base64-encoded data>` | Called from `failedSendingAnswers()` (~L1387, every send failure) **and** from `displayClosedInfo()` (~L1501, contest close with pending answers). | `getEncodedAnswers()` → base64 of `{ "pwd": teamPassword, "ans": [[questionID, answer], ...] }`. Injected as `src` of a 1×1 hidden `<img>` (`class='hidden'`). | Hidden (DOM `<img>` with `class='hidden'`). No UI shown to student. |
| 4 | Encoded text + download | `displayClosedInfo()` (~L1483) + `getEncodedAnswers()` (~L1469) | N/A — manual student transmission by email or similar | Contest closes (`finalCloseContest()` → `displayClosedInfo()`) **and** `getEncodedAnswers()` returns non-null (i.e., `answersToSend` is non-empty). | `#encodedAnswers` span filled with base64 string. `#divClosedEncodedDownload` `<a>` link: Blob of `$('#divClosedConnectionError').text()` + `"\r\n\r\n"` + base64 string, saved as `<hostname>_<teamPassword>.txt`. | **Fully visible**: base64 text on screen, `.txt` download button, instructions from `#divClosedConnectionError` HTML template. |
| 5 | QR code | `makeFinalQRCode()` (~L1437) + `getEncodedScores()` (~L1491) | `https://backup.castor-informatique.fr/?s=<encoded data>` | Called from `displayClosedInfo()` (~L1509). Only renders if `config.finalQRCodeMode` is set: **`backup` mode** requires `getEncodedScores()` to be non-null (unsent scores exist); **`always` mode** renders unconditionally. | **`backup`**: base64 of `[teamPassword, hostname, ISO_timestamp, questionID_1, score_1, ...]`. **`always`**: composite string with `teamPassword + ";" + obfuscated_scores + ";;" + ...`; scores are base64-encoded then further scrambled via `answerKey` (rotating index-mod-64 character substitution). | **Visible** when conditions met: QR code rendered via `QRCode` library into `#divClosedQRCode`, container shown with localized label `t('closed_qrcode_' + config.finalQRCodeMode)`. |

---

## 1. Primary POST (`answer.php`)

### Functions

| Function | Role |
|----------|------|
| `submitAnswer()` (~L1399) | Entry point. Stores the answer in `answersToSend`, calls `sendAnswers()`. |
| `sendAnswers()` (~L1375) | Iterates `answersToSend`, sets `sending = true` on each, POSTs to `answer.php`. On success, deletes sent items. On failure, calls `failedSendingAnswers()`. |

### Trigger Condition

- Called on every answer submission via `submitAnswer()`.
- Retried periodically: `failedSendingAnswers()` schedules a retry via `setTimeout(sendAnswers, delay)` with a 60-second delay (configurable via `delaySendingAttempts`).

### Data Sent

```
{ SID, answers, teamID, teamPassword, sendLastActivity, browserID }
```

When `closingContest` is true, an additional `finalAnswersSent: true` flag is included.

### Failure Handling

`failedSendingAnswers()` (~L1383):
1. Sets `Tracker.disabled = true`.
2. Resets all `sending` flags to `false` in `answersToSend`.
3. Calls `backupSendAnswers()` (system 3).
4. Toggles `sendAnswersTryAlternate` for next retry.
5. Schedules retry in 1 second (if currently on alternate) or 60 seconds (if currently on primary).

---

## 2. Alternate Server POST (`concours4`)

### Functions

| Function | Role |
|----------|------|
| `sendAnswers()` (~L1420) | Checks `sendAnswersTryAlternate`; if true, swaps the endpoint URL. |

### Trigger Condition

- `sendAnswersTryAlternate` is a boolean toggled on each call to `failedSendingAnswers()`.
- On first failure, it becomes `true` → next `sendAnswers()` call targets `concours4`.
- On second failure, it becomes `false` → next call targets local `answer.php` again.
- This creates an **alternating retry** pattern between local and alternate server.

### Endpoint

```
https://concours4.castor-informatique.fr/answer.php
```

### Data Sent

Same as primary system.

### User Visibility

None. The user has no indication that a different server is being used.

---

## 3. Image Beacon (`backupSendAnswers`)

### Functions

| Function | Role |
|----------|------|
| `backupSendAnswers()` (~L1457) | Creates a hidden 1×1 `<img>` element with `src` set to the backup URL. |
| `getEncodedAnswers()` (~L1469) | Generates the base64-encoded payload from `answersToSend`. |

### Trigger Condition

- **On send failure**: Called from `failedSendingAnswers()` (~L1387) every time the POST to `answer.php` (or `concours4`) fails.
- **On contest close**: Called from `displayClosedInfo()` (~L1501) when the contest ends and `getEncodedAnswers()` returns a non-null value (i.e., there are unsent answers).

### Data Sent

`getEncodedAnswers()` encodes:

```json
{
  "pwd": "<teamPassword>",
  "ans": [[<questionID>, "<answer>"], ...]
}
```

This is base64-encoded and appended as a query parameter:

```
https://backup.castor-informatique.fr/?q=<base64-encoded JSON>
```

### Implementation Details

- The `<img>` element has `id='backup-send-answers'` and CSS class `hidden`.
- It is created once and reused (the `src` attribute is updated on subsequent calls).
- This is a fire-and-forget HTTP GET — no response is expected or processed.

### User Visibility

Completely hidden. No UI shown to the student.

---

## 4. Encoded Text Display + Download

### Functions

| Function | Role |
|----------|------|
| `displayClosedInfo()` (~L1483) | Main function that renders the contest-closed screen. |
| `getEncodedAnswers()` (~L1469) | Same function as used by system 3. |

### Trigger Condition

- Called when the contest closes: `finalCloseContest()` → `displayClosedInfo()`.
- The encoded answers section is only shown if `getEncodedAnswers()` returns a non-null value, meaning `answersToSend` is non-empty (some answers were never successfully transmitted).

### What Is Displayed

1. **`#encodedAnswers` span**: Filled with the raw base64 string from `getEncodedAnswers()`.
2. **`#divClosedEncodedAnswers` div**: A container div made visible to hold the encoded text and download link.
3. **`#divClosedEncodedDownload` link**: An `<a>` element configured as a download link:
   - **Blob content**: `$('#divClosedConnectionError').text()` (a template string containing instructions for the student) + `"\r\n\r\n"` + the base64-encoded answers.
   - **Filename**: `<window.location.hostname>_<teamPassword>.txt`
   - The student can click this to download a `.txt` file containing the instructions and the encoded data.

### User Visibility

**Fully visible**. The student sees:
- The base64-encoded text on screen.
- A download button to save it as a text file.
- Instructions (from the `#divClosedConnectionError` HTML template) explaining how to transmit the data.

---

## 5. QR Code (`makeFinalQRCode`)

### Functions

| Function | Role |
|----------|------|
| `makeFinalQRCode()` (~L1437) | Generates and renders the QR code. |
| `getEncodedScores()` (~L1491) | Generates a base64-encoded score payload (used in `backup` mode). |
| `btoa()` + custom obfuscation | In `always` mode, applies a character substitution keyed by `answerKey`. |

### Trigger Condition

Called from `displayClosedInfo()` (~L1509). Only renders if:

- `config.finalQRCodeMode` is set, **AND**
  - **`backup` mode**: `getEncodedScores()` returns non-null (there are unsent answers/scores).
  - **`always` mode**: Always renders, regardless of pending answers.

If conditions are not met, `#divClosedQRCodeContainer` is hidden.

### Two Modes

#### `backup` mode

- **Data**: `getEncodedScores()` → base64 of:

  ```
  [teamPassword, hostname, ISO_timestamp, questionID_1, score_1, questionID_2, score_2, ...]
  ```

- **URL**: `https://backup.castor-informatique.fr/?s=<base64 data>`

- Simpler, only encodes score data for unsent answers.

#### `always` mode

- **Data**: A composite string built as:

  ```
  teamPassword + ";" + obfuscated_scores + ";;" + hasAnswersToSend + ";" + lastAnswersSentDate + ";;" + now + ";" + base64(obfuscated_scores) + ";;" + ffTeamScore + ";" + teamPassword
  ```

- **Obfuscation**: The base64-encoded scores are further scrambled using a custom substitution cipher:
  - Characters are mapped via `answerKey` (a rotating key): each character's base64 index is shifted by `answerKey[i % answerKey.length]` modulo 64.
  - Uses a custom base64 alphabet (`b64c`) with URL-safe characters (`-` and `_` instead of `+` and `/`).
  - After use, `answerKey` is set to `null` to prevent reuse.

- **URL**: `https://backup.castor-informatique.fr/?s=<encoded composite string>`

- More comprehensive: includes both pending answers indicator and score data.

### Display

- Rendered via the `QRCode` library into `#divClosedQRCode`.
- Container `#divClosedQRCodeContainer` shown with localized text from `t('closed_qrcode_' + config.finalQRCodeMode)`.

### User Visibility

**Visible** (QR code image displayed on screen) when conditions are met.

---

## Call Flow Summary

```
submitAnswer()
  └─ sendAnswers()  ─── POST to answer.php (or concours4 if alternating)
       ├─ Success → delete from answersToSend
       └─ Failure → failedSendingAnswers()
                      ├─ backupSendAnswers()          [system 3]
                      └─ setTimeout(sendAnswers, ...) [retry, alternating servers]

closeContest()
  └─ doCloseContest()
       ├─ If no pending answers → finalCloseContest() directly
       └─ If pending answers → show "please wait", sendAnswers(), then finalCloseContest()
            └─ finalCloseContest()
                 └─ If solutions shown → showScoresHat()
                 └─ Otherwise → displayClosedInfo()
                      ├─ backupSendAnswers()               [system 3, if pending answers]
                      ├─ Encoded text + download button     [system 4, if pending answers]
                      └─ makeFinalQRCode()                 [system 5, if configured]
```
