Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,14 @@ Perfect for:

---

## 🧠 Architecture

Explore the system design:

👉 docs/architecture/README.md

---

## 🛠️ Development

Chytanka requires a proxy server for handling some external sources.
Expand Down
96 changes: 96 additions & 0 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# 🧠 Chytanka Architecture

This section documents the architecture of Chytanka using a C4-style approach.

Chytanka is designed as a **multi-entry, embeddable reader platform** with hybrid data access.

---

## 🚀 Key Concepts

* 📖 **Embeddable Reader** — can run standalone or inside other websites (iframe)
* 🔌 **Multiple Entry Points**:

* Direct (PWA)
* Browser Extension
* Embedded (Host Website)
* 🌐 **Hybrid Data Access**:

* Direct requests to APIs
* Optional proxy for restricted sources
* Local file support (ZIP/CBZ/PDF)

---

## 📊 Diagrams

### 1. System Context

👉 [Open context diagram](./context.mmd)

Shows the ecosystem:

* User interactions
* External systems
* Entry points

---

### 2. Container Architecture

👉 [Open container diagram](./container.mmd)

Shows:

* Main runtime parts of the system
* Frontend + proxy separation

---

### 3. Data Flow

👉 [Open flow diagram](./flow.mmd)

Shows:

* How requests are resolved
* Direct vs proxy logic

---

### 4. Proxy Internals

👉 [Open proxy diagram](./proxy.mmd)

Shows:

* Request pipeline
* Validation and fetch logic

---

## 🧭 How to Read

Start with **Context**, then go deeper:

```text
Context → Container → Flow → Details
```

---

## 🧩 Architecture Style

Chytanka follows a pragmatic interpretation of the C4 model:

* Context diagrams describe the ecosystem
* Container diagrams describe runtime structure
* Flow diagrams explain behavior

---

## 📌 Notes

* Diagrams are written in **Mermaid**
* Stored alongside the code for versioning
* Designed to be readable without external tools
76 changes: 76 additions & 0 deletions docs/architecture/container.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
C4Container
title Chytanka - Container Architecture

UpdateLayoutConfig($c4ShapeInRow="4", $c4BoundaryInRow="2")

Person(user, "User")

System_Ext(host, "🧱 Host Website", "3rd-party site embedding Chytanka via iframe")

%% Enterprise_Boundary(b1, "Chytanka") {

Container_Boundary(pwa, "📖 Chytanka PWA") {

Container(app, "Viewer Application", "Angular", "UI, routing, rendering pages")
Container(embed, "Embed Bridge", "postMessage API", "Handles communication with host website")
Container(storage, "Local Storage", "IndexedDB / Cache API", "History, cache, metadata")
Container(facade, "Facade Layer", "Angular Services", "High-level API for viewer logic")
Container(worker, "File Worker", "Web Worker", "Reads ZIP/CBZ/PDF and extracts images")
Container(router, "Source Router", "TypeScript", "Decides data source (direct vs proxy)")
Container(api, "API Client", "HTTP", "Fetches remote resources")
}

Boundary(b2, "Browser Extension / Proxy") {
Container(extension, "🧩 Chytanka Helper", "Browser Extension", "Adds 'Read in Chytanka' and opens links")

Container(proxy, "🌐 Proxy Service", "Node.js (Express)", "Handles CORS, headers, rate limiting")
}
%% }

Boundary(b_sources, "External Data Sources") {
System_Ext(online, "🌐 Online APIs", "MangaDex, Imgur, Reddit etc.")
System_Ext(files, "🗂️ File System", "Local files (CBZ, PDF, etc.)")
}

Rel(user, app, "Uses UI")
Rel(user, extension, "Clicks extension UI")
Rel(user, host, "Visits website")

Rel(extension, app, "Opens reader with URL")

Rel(host, embed, "Sends/receives messages (postMessage)")

Rel(app, facade, "Uses")
Rel(facade, router, "Delegates data loading")

Rel(router, api, "Direct requests")
Rel(router, proxy, "Fallback / restricted requests")

Rel(api, online, "Fetches data")
Rel(proxy, online, "Fetches data")

Rel(app, worker, "Loads local files")
Rel(worker, files, "Reads files")

Rel(app, storage, "Reads/Writes")

UpdateElementStyle(app, $bgColor="#166496", $fontColor="#ffd60a", $borderColor="#166496")
UpdateElementStyle(facade, $bgColor="#2c7da0", $fontColor="#fff")
UpdateElementStyle(router, $bgColor="#2c7da0", $fontColor="#fff")
UpdateElementStyle(api, $bgColor="#2c7da0", $fontColor="#fff")
UpdateElementStyle(worker, $bgColor="#2c7da0", $fontColor="#fff")
UpdateElementStyle(storage, $bgColor="#2c7da0", $fontColor="#fff")
UpdateElementStyle(embed, $bgColor="#2c7da0", $fontColor="#fff")

UpdateElementStyle(proxy, $bgColor="#88a8c8", $fontColor="#fff")
UpdateElementStyle(extension, $bgColor="#88a8c8", $fontColor="#fff")

UpdateElementStyle(host, $bgColor="#444", $fontColor="#eee")
UpdateElementStyle(files, $bgColor="#444", $fontColor="#eee")
UpdateElementStyle(online, $bgColor="#444", $fontColor="#eee")

UpdateRelStyle(router, proxy, $textColor="blue", $lineColor="blue")
UpdateRelStyle(proxy, online, $textColor="blue", $lineColor="blue")

UpdateRelStyle(api, online, $textColor="green", $lineColor="green")
UpdateRelStyle(worker, files, $textColor="green", $lineColor="green")
58 changes: 58 additions & 0 deletions docs/architecture/context.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
C4Context
title Chytanka - System Context

Person(user, "User", "End user who reads manga and comics using Chytanka")

Enterprise_Boundary(b_chytanka, "Chytanka") {
System(extension, "🧩 Chytanka Helper", "Browser extension")
System(chytanka, "📖 Chytanka", "PWA for reading manga and comics")
System(proxy, "🌐 Proxy Service", "Optional proxy for restricted requests")
}

System_Ext(host, "🧱 Host Website", "3rd-party site embedding Chytanka and providing UI")

Boundary(b_sources, "External Data Sources") {
System_Ext(files, "🗂️ File System", "Local files (CBZ, PDF, etc.)")
System_Ext(online, "🌐 Online APIs", "MangaDex, Imgur, Reddit etc.")
}

Rel(user, chytanka, "Uses directly")
Rel(user, extension, "Clicks extension UI")
Rel(user, host, "Visits website")
Rel(extension, chytanka, "Opens links")

Rel(host, chytanka, "Embeds via iframe")
Rel(host, chytanka, "Controls via postMessage")

Rel(chytanka, online, "Fetches data (direct)")
Rel(chytanka, files, "Reads local files")
Rel(chytanka, proxy, "Optional requests")

Rel(proxy, online, "Fetches data")

%% Styles

UpdateLayoutConfig($c4ShapeInRow="2", $c4BoundaryInRow="2")

UpdateElementStyle(chytanka, $fontColor="#ffd60a", $bgColor="#166496", $borderColor="#166496")
UpdateElementStyle(extension, $fontColor="#fff", $bgColor="#88a8c8", $borderColor="#88a8c8")
UpdateElementStyle(proxy, $fontColor="#fff", $bgColor="#5f7f9f", $borderColor="#5f7f9f")

UpdateElementStyle(host, $fontColor="#eee", $bgColor="#444")
UpdateElementStyle(files, $fontColor="#eee", $bgColor="#444")
UpdateElementStyle(online, $fontColor="#eee", $bgColor="#444")

UpdateRelStyle(chytanka, proxy, $textColor="blue", $lineColor="blue", $offsetX="50", $offsetY="-10")
UpdateRelStyle(proxy, online, $textColor="blue", $lineColor="blue", $offsetX="0", $offsetY="10")

UpdateRelStyle(chytanka, online, $textColor="#166496", $lineColor="#166496", $offsetX="0", $offsetY="-10")
UpdateRelStyle(chytanka, files, $textColor="#166496", $lineColor="#166496", $offsetX="-30", $offsetY="10")

UpdateRelStyle(user, extension, $textColor="#88a8c8", $lineColor="#88a8c8", $offsetY="-20", $offsetX="15")
UpdateRelStyle(user, chytanka, $textColor="#166496", $lineColor="#166496", $offsetY="-20")
UpdateRelStyle(extension, chytanka, $textColor="#88a8c8", $lineColor="#88a8c8", $offsetX="-30", $offsetY="15")
UpdateRelStyle(user, host, $textColor="#88a8c8", $lineColor="#88a8c8", $offsetY="-20", $offsetX="-35")

UpdateRelStyle(host, chytanka, $textColor="#88a8c8", $lineColor="#88a8c8", $offsetY="-20", $offsetX="35")

UpdateElementStyle(b_sources, $fontColor="#ffeeee")
62 changes: 62 additions & 0 deletions docs/architecture/flow.mmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
graph TD
%% Entry points
Start([Open Manga])

User[User Action]
Extension[Extension]
Host[Host Website]

User --> Start
Extension --> Start
Host --> Start

%% Input normalization
Start --> Input[Resolve Input Source]

%% Source types
Input -->|URL| Remote[Remote Source]
Input -->|File| Local[Local File]

%% --- LOCAL FLOW ---
Local --> Worker[File Worker]
Worker --> Extract[Extract Pages]
Extract --> Render[Render Viewer]

%% --- REMOTE FLOW ---
Remote --> Router[Source Router]

%% Decision
Router -->|Direct Allowed| Direct[Direct Fetch]
Router -->|CORS / Restricted| Proxy[Proxy Fetch]

%% Direct path
Direct --> API[External API]

%% Proxy path
Proxy --> ProxyService[Proxy Service]
ProxyService --> API

%% Response
API --> Decode[Decode / Normalize]
Decode --> Render

%% --- EMBED CONTROL ---
Host -.->|postMessage| Control[Embed Control Layer]
Control -.-> Render

%% --- EXTENSION FLOW ---
Extension -->|Open URL| Start

%% --- FINAL ---
Render --> Viewer[Viewer UI]

%% Styling (optional readability)
classDef core fill:#166496,color:#ffd60a;
classDef logic fill:#2c7da0,color:#fff;
classDef external fill:#444,color:#eee;
classDef proxy fill:#88a8c8,color:#fff;

class Start,Viewer core;
class Router,Input,Decode logic;
class API external;
class ProxyService proxy;
Empty file added docs/architecture/proxy.mmd
Empty file.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chytanka",
"version": "0.13.58",
"version": "0.13.59",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down
4 changes: 2 additions & 2 deletions src/app/@site-modules/reddit/reddit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Observable, map } from 'rxjs';
import { environment } from '../../../environments/environment';
import { CompositionEpisode } from '../@common-read';
import { ProxyService } from '../../shared/data-access/proxy.service';
import { Base64 } from '../../shared/utils';

@Injectable({
providedIn: 'root'
Expand All @@ -15,7 +14,8 @@ export class RedditService {
constructor(private http: HttpClient) { }

getComposition(postId: string): Observable<CompositionEpisode> {
return this.http.get<any>(environment.redditHost + postId + '.json')
const url = environment.redditHost + postId + '.json';
return this.http.get<any>(this.proxy.proxyUrl(url))
.pipe(map((data: any) => { return this.map(data) }))
}

Expand Down
15 changes: 8 additions & 7 deletions src/app/@site-modules/zenko/zenko.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ export class ZenkoService {
proxy: ProxyService = inject(ProxyService)

getComposition(id: string): Observable<CompositionEpisode> {
return this.http.get<any>(this.proxy.proxyUrl((environment.zenkoHost + id))+'&ref=https://zenko.online')
return this.http.get<any>(this.proxy.proxyUrl((environment.zenkoChapters + id)) + `&ref=${environment.zenkoHost}`)
.pipe(map((data) => { return this.map(data) }))
}

map(data: any): CompositionEpisode {
const {vol, ch, name, output} = this.titleDecode(data.name);
const imgParams = `?optimizer=image&width=900&quality=90&height=auto`;
const { vol, ch, name, output } = this.titleDecode(data.name);
const x = parseFloat(ch);
const part = Math.round((x - Math.floor(x)) * 10)
const mappedResponse = {
Expand All @@ -29,9 +30,9 @@ export class ZenkoService {

publisher: {
id: data.publisher.id as string,
site: `https://zenko.online/teams/`+data.publisher.id as string,
site: environment.zenkoTeams + data.publisher.id as string,
name: data.publisher.name as string,
avatar: this.proxy.proxyUrl(`https://zenko.b-cdn.net/${data.publisher.avatar}?optimizer=image&width=900&quality=90&height=auto`) + '&ref=https://zenko.online' as string,
avatar: this.proxy.proxyUrl(environment.zenkoCdn + data.publisher.avatar + imgParams) + `&ref=${environment.zenkoHost}` as string,
description: data.publisher.description as string,
links: data.publisher.links?.map((l: any) => { return { link: l.link, title: l.title }; })
} as unknown as CompositionPublisher,
Expand All @@ -40,8 +41,8 @@ export class ZenkoService {
return {
src: item.imgUrl || item.content
};
})).filter((i: any) => i.src).map((img: any) => { return { src: this.proxy.proxyUrl(`https://zenko.b-cdn.net/${img.src}?optimizer=image&width=900&quality=90&height=auto`) + '&ref=https://zenko.online' } })
})).filter((i: any) => i.src).map((img: any) => { return { src: this.proxy.proxyUrl(environment.zenkoCdn + img.src + imgParams) + `&ref=${environment.zenkoHost}` } })

};

return mappedResponse;
Expand All @@ -55,7 +56,7 @@ export class ZenkoService {
const name = parts[2] ?? 'Без назви';

const output = `Том ${vol} Розділ ${ch}: ${name}`;
return {vol, ch, name, output}
return { vol, ch, name, output }

}
}
Loading
Loading