Renamed Razor component files in ReceiverUI to follow a consistent "Page" naming convention. Updated routes to reference the renamed files. Introduced the root route (`/`) with `Index.razor` as the application entry point. Updated documentation to reflect the file renames, route changes, and multi-envelope support. Clarified Redis/SQL caching details and deprecated the "Web" frontend in favor of `ReceiverUI`. These changes improve maintainability, consistency, and developer experience.
425 lines
14 KiB
Markdown
425 lines
14 KiB
Markdown
# EnvelopeGenerator — AI Context Reference
|
||
|
||
## Purpose
|
||
Digital document signing system with **unified Blazor WASM frontend** for both Senders and Receivers. Senders create envelopes and place signature fields. Receivers view PDFs, sign documents, export stamped PDFs.
|
||
|
||
**Primary Libraries:** DevExpress + PDF.js (PSPDFKit removed)
|
||
|
||
---
|
||
|
||
## Deployment Architecture
|
||
|
||
**Two Presentation Projects (Both Required):**
|
||
|
||
1. **EnvelopeGenerator.API** (ASP.NET Core Web API)
|
||
- Runs independently (development & production)
|
||
- **YARP Reverse Proxy** configured via `yarp.json`
|
||
- Proxies requests to:
|
||
- `EnvelopeGenerator.ReceiverUI` (Blazor WASM)
|
||
- External Auth.API service
|
||
- Serves as single entry point for all requests
|
||
|
||
2. **EnvelopeGenerator.ReceiverUI** (Blazor WebAssembly)
|
||
- Runs on separate host/port
|
||
- Accessed **only through API proxy** (not directly)
|
||
- Serves static files (HTML, JS, CSS, WASM)
|
||
|
||
**Request Flow:**
|
||
```
|
||
Client ? API:8088 (YARP Proxy) ? ReceiverUI:52936 (Blazor WASM)
|
||
? Auth.API:9090 (External Auth Service)
|
||
```
|
||
|
||
**Configuration:** `EnvelopeGenerator.API/yarp.json`
|
||
|
||
---
|
||
|
||
## ReceiverUI Route Structure
|
||
|
||
### Root Route
|
||
| Route | File | Purpose |
|
||
|---|---|---|
|
||
| `/` | `Index.razor` | Application entry point (landing page). |
|
||
|
||
### Sender Routes
|
||
| Route | File | Purpose |
|
||
|---|---|---|
|
||
| `/sender/login` | `LoginSenderPage.razor` | Username/password authentication |
|
||
| `/sender` | `EnvelopeSenderPage.razor` | Sender dashboard (envelope list) |
|
||
|
||
### Receiver Routes
|
||
| Route | File | Purpose |
|
||
|---|---|---|
|
||
| `/envelope/login/{EnvelopeKey}` | `LoginReceiverPage.razor` | Access code authentication for specific envelope |
|
||
| `/envelope/{EnvelopeKey}` | `EnvelopeReceiverPage.razor` | View & sign envelope (PDF.js viewer) |
|
||
|
||
**Multi-Envelope Support:** Receivers can login to multiple envelopes simultaneously (per-envelope cookie authentication).
|
||
|
||
---
|
||
|
||
## Architecture Evolution
|
||
|
||
### Old Architecture (Deprecated)
|
||
- **Sender UI:** `EnvelopeGenerator.Web` (Razor Pages + PSPDFKit)
|
||
- **Receiver UI:** `EnvelopeGenerator.ReceiverUI` (Blazor WASM + PDF.js)
|
||
- **Backend:** `EnvelopeGenerator.API`
|
||
|
||
### Current Architecture
|
||
- **Unified Frontend:** `EnvelopeGenerator.ReceiverUI` (Blazor WASM) — **Both Senders & Receivers**
|
||
- **Backend:** `EnvelopeGenerator.API` — **Both Senders & Receivers**
|
||
- **Libraries:** DevExpress + PDF.js
|
||
- **PSPDFKit:** **REMOVED**
|
||
|
||
---
|
||
|
||
## Solution Structure
|
||
|
||
| Project | Target | Purpose |
|
||
|---|---|---|
|
||
| `EnvelopeGenerator.API` | net8.0 | ASP.NET Core Web API. Backend for **both Senders & Receivers**. Auth, PDF serving, signature endpoints. |
|
||
| `EnvelopeGenerator.ReceiverUI` | net8.0 WASM | **Unified Blazor WebAssembly Frontend**. UI for **both Senders & Receivers**. YARP proxy to API. |
|
||
| `EnvelopeGenerator.Web` | net7/8/9 | **DEPRECATED.** Legacy Razor Pages (Sender UI). No longer used. |
|
||
| `EnvelopeGenerator.Application` | multi | MediatR CQRS handlers. Business logic. |
|
||
| `EnvelopeGenerator.Domain` | multi | Domain models, constants, interfaces. |
|
||
| `EnvelopeGenerator.Infrastructure` | multi | EF Core repos, DB context. |
|
||
| `EnvelopeGenerator.PdfEditor` | multi | iText7 utilities (NOT used in ReceiverUI). |
|
||
| `EnvelopeGenerator.DependencyInjection` | multi | DI registration helpers. |
|
||
| **VB.NET projects** (Service/Form/BBTests) | net462 | **Legacy. Do NOT touch.** |
|
||
|
||
---
|
||
|
||
## Key Files & Routes
|
||
|
||
| File | Route/Purpose |
|
||
|---|---|
|
||
| `ReceiverUI/Pages/Index.razor` | `/` — Application entry point (landing page). |
|
||
| `ReceiverUI/Pages/EnvelopeSenderPage.razor` | `/sender` — Sender dashboard (envelope list). |
|
||
| `ReceiverUI/Pages/EnvelopeReceiverPage.razor` | `/envelope/{key}` — Receiver PDF viewer & signing. |
|
||
| `ReceiverUI/Pages/LoginSenderPage.razor` | `/sender/login` — Sender username/password auth. |
|
||
| `ReceiverUI/Pages/LoginReceiverPage.razor` | `/envelope/login/{EnvelopeKey}` — Receiver access code auth. |
|
||
| `ReceiverUI/wwwroot/js/pdf-viewer.js` | PDF.js wrapper (zoom, pagination, thumbnails). |
|
||
| `ReceiverUI/wwwroot/js/receiver-signature.js` | Signature pad (draw/type/image). |
|
||
| `ReceiverUI/wwwroot/css/envelope-viewer.css` | EnvelopeViewer styles. |
|
||
| `ReceiverUI/Services/AuthService.cs` | Receiver + Sender authentication. |
|
||
| `ReceiverUI/Services/SignatureCacheService.cs` | Signature caching (Redis/SQL). |
|
||
| `API/Controllers/CacheController.cs` | Signature cache endpoints. |
|
||
|
||
---
|
||
|
||
## Coordinate System — CRITICAL
|
||
|
||
**Database Format:** INCHES (GdPicture14 native)
|
||
**Origin:** Top-left corner
|
||
**Axes:** X right, Y down
|
||
|
||
### Conversion Formulas
|
||
|
||
| From INCHES to | Formula | Example |
|
||
|---|---|---|
|
||
| **DevExpress DX** | `x_DX = x_inches * 100` | 1.5" ? 150 DX |
|
||
| **PDF Points** | `x_pt = x_inches * 72` | 1.5" ? 108 pt |
|
||
| **PDF.js Pixels** | Normalize ? scale | `(x_inches / pageWidth) * canvasWidth * scale` |
|
||
|
||
**A4 Dimensions:**
|
||
- Width: 8.27" = 595pt = 827 DX
|
||
- Height: 11.69" = 842pt = 1169 DX
|
||
|
||
### Unit Systems
|
||
|
||
| System | Unit | Origin | Y-Axis |
|
||
|---|---|---|---|
|
||
| **Database (GdPicture14)** | Inches | Top-left | Down |
|
||
| PDF.js | Pixels | Top-left | Down |
|
||
| iText7 PDF | Points (1/72") | **Bottom-left** | **Up** (flip required) |
|
||
| ~~PSPDFKit~~ | ~~Points~~ | ~~Top-left~~ | **REMOVED** |
|
||
|
||
---
|
||
|
||
## EnvelopeReceiver — PDF.js Viewer & Signing
|
||
|
||
**Route:** `/envelope/{EnvelopeKey}`
|
||
**Tech:** PDF.js 3.11.174 + Blazor WASM + configurable quality
|
||
**File:** `ReceiverUI/Pages/EnvelopeReceiverPage.razor`
|
||
|
||
### Key Features
|
||
1. HiDPI/Retina support (4x quality)
|
||
2. Configurable quality (`appsettings.json`)
|
||
3. Unlimited zoom (50%-300%)
|
||
4. Ctrl+Wheel global zoom
|
||
5. Resizable thumbnail sidebar (150-400px, localStorage)
|
||
6. Responsive (desktop/mobile)
|
||
|
||
### Configuration
|
||
**File:** `ReceiverUI/wwwroot/appsettings.json`
|
||
|
||
```json
|
||
{
|
||
"PdfViewer": {
|
||
"ThumbnailBaseScale": 0.75,
|
||
"ThumbnailEnableHiDPI": true,
|
||
"MainCanvasEnableHiDPI": true,
|
||
"ZoomStepPercentage": 5
|
||
}
|
||
}
|
||
```
|
||
|
||
### JavaScript API
|
||
**File:** `ReceiverUI/wwwroot/js/pdf-viewer.js`
|
||
|
||
```javascript
|
||
window.pdfViewer = {
|
||
initialize(canvasId, pdfDataUrl, dotNetRef),
|
||
renderPage(num),
|
||
renderSignatureButtons(signatures, pageNum, dotNetRef),
|
||
applySignature(signatureId, dataUrl, fullName, position, place),
|
||
zoomIn(), zoomOut(), dispose()
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Signature Workflow — EnvelopeReceiver
|
||
|
||
**IMPORTANT:** iText7 NOT used (GPL license issue). Client-side overlay system only.
|
||
|
||
### Workflow Steps
|
||
|
||
1. **Page Load:**
|
||
- Check `SignatureCacheService` for cached signature
|
||
- If cached ? skip popup, load signature
|
||
- If not ? show automatic popup (mandatory)
|
||
|
||
2. **Signature Popup (DxPopup):**
|
||
- **Cannot close** (no X, no ESC, no outside-click)
|
||
- **3 Tabs:** Draw (canvas) / Text (font select) / Image (upload)
|
||
- **Required:** Full name, Place
|
||
- **Optional:** Position
|
||
- **Save ?** Store in `_capturedSignature`, cache via API
|
||
|
||
3. **Signature Buttons:**
|
||
- Render purple "Unterschreiben" buttons at signature field positions
|
||
- Coordinates: INCHES ? POINTS ? Pixels (scaled)
|
||
- File: `pdf-viewer.js` ? `renderSignatureButtons()`
|
||
|
||
4. **Apply Signature (Click "Unterschreiben"):**
|
||
- JS: Remove button, create HTML overlay
|
||
- Format: Image + separator + text (Name, Position, Place, Date)
|
||
- **NOT stamped on PDF bytes** (visual overlay only)
|
||
|
||
5. **Re-rendering:**
|
||
- Zoom/Page change ? recalculate button positions
|
||
- Session state: `_capturedSignature` (lost on refresh)
|
||
|
||
### Data Model
|
||
**File:** `ReceiverUI/Models/SignatureCaptureDto.cs`
|
||
|
||
```csharp
|
||
public sealed record SignatureCaptureDto {
|
||
public required string DataUrl { get; init; } // base64 PNG
|
||
public required string FullName { get; init; }
|
||
public string Position { get; init; } = ""; // Optional
|
||
public required string Place { get; init; }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Signature Caching
|
||
|
||
**Purpose:** Persist signature across page refreshes (distributed cache: Redis/SQL)
|
||
|
||
### API Endpoints
|
||
**Controller:** `API/Controllers/CacheController.cs`
|
||
|
||
- `POST /api/Cache/SignatureCapture/{envelopeKey}` — Save
|
||
- `GET /api/Cache/SignatureCapture/{envelopeKey}` — Load
|
||
- `DELETE /api/Cache/SignatureCapture/{envelopeKey}` — Delete
|
||
|
||
**Cache Key Format:**
|
||
```
|
||
signature:91751687-8ae6-4777-bf5f-b8846085e62e:{envelopeKey}
|
||
```
|
||
|
||
**Configuration:** `appsettings.json`
|
||
```json
|
||
{
|
||
"Cache": {
|
||
"SignatureCacheExpiration": null // or "02:00:00" for 2h
|
||
}
|
||
}
|
||
```
|
||
|
||
### Service
|
||
**File:** `ReceiverUI/Services/SignatureCacheService.cs`
|
||
|
||
```csharp
|
||
public class SignatureCacheService {
|
||
Task SaveSignatureAsync(string envelopeKey, SignatureCaptureDto signature);
|
||
Task<SignatureCaptureDto?> GetSignatureAsync(string envelopeKey);
|
||
Task DeleteSignatureAsync(string envelopeKey);
|
||
}
|
||
```
|
||
|
||
**Error Handling:** Fire-and-forget saves, graceful degradation on load failure.
|
||
|
||
---
|
||
|
||
## Sender Login
|
||
|
||
**Route:** `/sender/login`
|
||
**File:** `ReceiverUI/Pages/LoginSenderPage.razor`
|
||
**Tech:** Bootstrap 5 + DevExpress Blazing Berry theme
|
||
|
||
### AuthService Extension
|
||
**File:** `ReceiverUI/Services/AuthService.cs`
|
||
|
||
```csharp
|
||
public enum SenderLoginResult { Success, InvalidCredentials, Error }
|
||
|
||
public async Task<SenderLoginResult> LoginSenderAsync(string username, string password) {
|
||
var response = await http.PostAsJsonAsync(
|
||
$"{_api.BaseUrl}/api/auth?cookie=true",
|
||
new { username, password });
|
||
|
||
return response.StatusCode switch {
|
||
HttpStatusCode.OK => SenderLoginResult.Success,
|
||
HttpStatusCode.Unauthorized => SenderLoginResult.InvalidCredentials,
|
||
_ => SenderLoginResult.Error
|
||
};
|
||
}
|
||
```
|
||
|
||
### API Integration
|
||
**Endpoint:** `POST /api/auth?cookie=true`
|
||
|
||
**Request:**
|
||
```json
|
||
{ "username": "TekH", "password": "***" }
|
||
```
|
||
|
||
**Response:**
|
||
- `200 OK` ? Cookie set, redirect to `/sender`
|
||
- `401 Unauthorized` ? Show error: "Ungültige Anmeldedaten"
|
||
- Other ? Show error: "Serverfehler"
|
||
|
||
**Cookie:** HTTP-only, Secure (HTTPS), SameSite=Strict
|
||
|
||
### UI Flow
|
||
1. User enters username + password
|
||
2. Click "Anmelden" or press Enter
|
||
3. Call `AuthService.LoginSenderAsync()`
|
||
4. Success ? `Navigation.NavigateTo("/sender", forceLoad: true)`
|
||
5. Error ? Display alert
|
||
|
||
---
|
||
|
||
## Receiver Login
|
||
|
||
**Route:** `/envelope/login/{EnvelopeKey}`
|
||
**File:** `ReceiverUI/Pages/LoginReceiverPage.razor`
|
||
|
||
**Multi-Envelope Support:** Cookies are stored per-envelope (e.g., `AuthTokenSignFLOWReceiver.{envelopeKey}`), allowing simultaneous authentication for multiple envelopes in the same browser session.
|
||
|
||
### AuthService Method
|
||
```csharp
|
||
public enum EnvelopeLoginResult { Success, InvalidCode, NotFound, Error }
|
||
|
||
public async Task<EnvelopeLoginResult> LoginEnvelopeReceiverAsync(string key, string accessCode) {
|
||
var form = new MultipartFormDataContent();
|
||
form.Add(new StringContent(accessCode), "AccessCode");
|
||
|
||
var response = await http.PostAsync(
|
||
$"{_api.BaseUrl}/api/Auth/envelope-receiver/{Uri.EscapeDataString(key)}", form);
|
||
|
||
return response.StatusCode switch {
|
||
HttpStatusCode.OK => EnvelopeLoginResult.Success,
|
||
HttpStatusCode.Unauthorized => EnvelopeLoginResult.InvalidCode,
|
||
HttpStatusCode.NotFound => EnvelopeLoginResult.NotFound,
|
||
_ => EnvelopeLoginResult.Error
|
||
};
|
||
}
|
||
```
|
||
|
||
**Success:** Redirect to `/envelope/{key}`
|
||
|
||
---
|
||
|
||
## NuGet Packages (ReceiverUI)
|
||
|
||
| Package | Version | Purpose |
|
||
|---|---|---|
|
||
| `DevExpress.Blazor.*` | 25.2.3 | UI components (grids, popups, etc.) |
|
||
| `SkiaSharp.*` | 3.119.1 | WASM rendering |
|
||
| ~~`itext`~~ | ~~8.0.5~~ | **NOT USED** (GPL license) |
|
||
|
||
**External CDN:**
|
||
- PDF.js 3.11.174: `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js`
|
||
|
||
---
|
||
|
||
## Mistakes History — Do NOT Repeat
|
||
|
||
| Mistake | Why Wrong |
|
||
|---|---|
|
||
| Using iText7 in EnvelopeReceiver | GPL license issue. Use overlay system instead. |
|
||
| Using PSPDFKit | Removed from architecture. Use PDF.js + DevExpress. |
|
||
| Hardcoded quality values in PDF.js | Use `appsettings.json` for configurability. |
|
||
| Complex toolbar layouts | User wants simplicity. Keep horizontal layout. |
|
||
| Over-designed UI (gradients/badges) | User prefers simple text labels. |
|
||
| Ignoring "revert" instructions | Revert HTML structure, not just CSS. |
|
||
| `BottomMarginBand` for signatures | Repeats on every page. Use DetailBand. |
|
||
| `imageY = (page-1) * 1169 + ann.Y` | Inflates DetailBand. Calculate per-page. |
|
||
|
||
---
|
||
|
||
## Development Notes
|
||
|
||
### Deprecated Projects
|
||
**DO NOT USE:**
|
||
- `EnvelopeGenerator.Web` (Razor Pages) — Replaced by unified ReceiverUI
|
||
- PSPDFKit — Removed, use PDF.js + DevExpress instead
|
||
|
||
### Legacy Projects (VB.NET)
|
||
**DO NOT TOUCH:** `EnvelopeGenerator.Service`, `EnvelopeGenerator.Form`, `EnvelopeGenerator.BBTests`
|
||
|
||
### Signature Coordinate Evidence
|
||
**File:** `EnvelopeGenerator.Form/frmFieldEditor.vb` (VB.NET)
|
||
|
||
```vb
|
||
Private Const SIGNATURE_WIDTH As Single = 1.77 ' inches
|
||
Private Const SIGNATURE_HEIGHT As Single = 1.96 ' inches
|
||
|
||
Sub LoadAnnotation(pElement As Signature, ...)
|
||
oAnnotation.Left = CSng(pElement.X) ' Direct INCHES assignment
|
||
oAnnotation.Top = CSng(pElement.Y)
|
||
End Sub
|
||
```
|
||
|
||
Proves database uses INCHES natively.
|
||
|
||
---
|
||
|
||
## Quick Reference
|
||
|
||
### When working with coordinates:
|
||
1. **Database ? UI:** INCHES × 72 = PDF Points
|
||
2. **UI ? Display:** Points × scale = Pixels
|
||
3. **iText7 stamping:** Flip Y-axis (top-down ? bottom-up)
|
||
|
||
### When adding features:
|
||
1. Check `Mistakes History` first
|
||
2. Prefer simplicity over complexity
|
||
3. Use `appsettings.json` for configuration
|
||
4. Keep consistent with existing design (Bootstrap 5 + Blazing Berry)
|
||
5. **Unified frontend:** ReceiverUI serves both Senders and Receivers
|
||
|
||
### When debugging:
|
||
1. **Coordinates:** Always check unit system (inches/points/pixels)
|
||
2. **Authentication:** Check cookie name/domain/SameSite
|
||
3. **Cache:** Check Redis/SQL connection + key format
|
||
4. **Frontend confusion:** Only use ReceiverUI (Web is deprecated)
|
||
|
||
---
|
||
|
||
**Last Updated:** Session 19 (Razor file naming convention + Index route proxy)
|