From a58a5b8917014995f2b98e48213974276387f63e Mon Sep 17 00:00:00 2001 From: Suzu008 Date: Sat, 30 May 2026 22:29:45 +0800 Subject: [PATCH] refactor(sw): bidirectional SW status sync with IndexedDB persistence Replace fire-and-forget key delivery with a sync-status protocol that verifies key consistency between main thread and Service Worker using SHA-256 fingerprints via MessageChannel request-response. - SW (ts/sw.ts): persist keys in IndexedDB, auto-restore on activate; respond to sync-status queries with loaded keys + fingerprints; support cryptio-clear-keys to wipe memory + DB. - New ts/utils/swSync.ts: single entry point for SW registration, key sync, and status verification. Controller change listener re-syncs on SW restart. Deduplicates concurrent sync calls. - Deleted ts/utils/swKeySender.ts (replaced by swSync.ts). - Remove SW registration (~55 lines) from cryptioSaveImage.ts; setup() now calls syncSWStatus(). - uploadUtils.ts: drop 5-retry preview loop (~6.4s worst case); single attempt with syncSWStatus fallback on failure. - cryptioSettings.ts: sync after key changes; clearSWKeys() on Clear All. - cryptioUploadImage/Video.ts: syncSWStatus in setup + preview fallback. - types.ts: add SWStatus interface. --- .gitignore | 2 +- AGENTS.md | 179 ++++++++++++++++ __init__.py | 14 +- js/cryptioSettings.js | 407 ------------------------------------ pyproject.toml | 5 +- skills-lock.json | 59 ++++++ src/cryptio/api.py | 20 ++ src/cryptio/nodes.py | 8 +- src/cryptio/save_image.py | 312 +++++++++++---------------- src/cryptio/save_video.py | 3 - src/cryptio/text_crypt.py | 102 ++++----- src/cryptio/upload_image.py | 184 ++++++++-------- ts/cryptioSaveImage.ts | 89 +++----- ts/cryptioSettings.ts | 5 + ts/cryptioUploadImage.ts | 8 +- ts/cryptioUploadVideo.ts | 20 +- ts/sw.ts | 323 ++++++++++++++++++++++++++++ ts/types.ts | 13 ++ ts/utils/imageLoader.ts | 10 +- ts/utils/swSync.ts | 199 ++++++++++++++++++ ts/utils/uploadUtils.ts | 44 ++-- ts/utils/videoLoader.ts | 10 +- 22 files changed, 1161 insertions(+), 855 deletions(-) create mode 100644 AGENTS.md delete mode 100644 js/cryptioSettings.js create mode 100644 skills-lock.json create mode 100644 ts/sw.ts create mode 100644 ts/utils/swSync.ts diff --git a/.gitignore b/.gitignore index 144982e..f9163e7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ __pycache__/ *.py[cod] *$py.class -/js # OSX useful to ignore *.DS_Store .AppleDouble @@ -102,6 +101,7 @@ cookiecutter-pypackage-env/ *.code-workspace # Frontend extension +js/ node_modules/ .env .env.local diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..681b009 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,179 @@ +# CryptIO Development Guide + +> **Keep this file in sync.** When architecture, commands, references, or conventions change, update this file so future sessions stay accurate. + +## Quick Commands + +```bash +# Python: dev install (live-reload on ComfyUI restart) +pip install -e .[dev] + +# TypeScript: compile to js/ +cd ts && pnpm build + +# TypeScript: type-check only (no emit) +cd ts && pnpm check + +# Lint (ruff check --fix) + format (ruff format) +pre-commit run --all-files + +# Lint only (CI does this) +ruff check . +``` + +**To test changes**: restart ComfyUI. Python changes are picked up via editable install. JS changes are served from `./js` (set by `WEB_DIRECTORY`). + +--- + +## Architecture + +### Encryption Flow + +``` +Upload: Browser --[server public key]--> AES-GCM encrypt --> Server decrypts with server private key +Output: Server --[client public key]--> AES-GCM encrypt --> Browser decrypts with client private key +KeyEx: Browser sends client pubkey --> Server sends encrypted server keypair (RSA+AES-CBC) +``` + +- **Server keypair** → `keys/keys.json` (auto-generated on first run, gitignored via `/keys`) +- **Key store at runtime** → ComfyUI's system user directory (`folder_paths.get_system_user_directory()/cryptio/keys/`), NOT the repo's `keys/` +- **Client keypair** → browser `localStorage` (`cryptio_client_keypair`, `cryptio_server_keys`) +- **Crypto primitives**: RSA-2048 for key wrapping, AES-GCM for data, AES-CBC for key exchange + +### Nodes (all V3 `io.ComfyNode`) + +All nodes use the V3 pattern: `define_schema()` (Schema + widget inputs), `execute()`, `fingerprint_inputs()`, `validate_inputs()`. + +| File | Nodes | Output Types | +|------|-------|-------------| +| `text_crypt.py` | TextEncrypt, TextDecrypt | String | +| `upload_image.py` | UploadImageCryptIO | Image + Mask | +| `save_image.py` | SaveImageCryptIO, PreviewImageCryptIO | UI (cryptio_images) | +| `upload_video.py` | UploadVideoCryptIO | Video | +| `save_video.py` | SaveVideoCryptIO, PreviewVideoCryptIO | UI (cryptio_images, animated) | + +### Extension Registration + +`__init__.py` uses `comfy_entrypoint()` returning a `ComfyExtension` subclass. Nodes are registered via `get_node_list()` returning `NODE_CLASS_MAPPINGS.values()`. The old `NODE_CLASS_MAPPINGS` + `NODE_DISPLAY_NAME_MAPPINGS` dicts are still defined in `nodes.py` for backwards compat. + +### Key Manager + +`KeyManager` is a singleton (`src/cryptio/utils/key_utils.py`). It caches keys in memory and checks file mtime to detect external changes. The global instance is `_key_manager`. + +### File Layout + +``` +__init__.py # WEB_DIRECTORY = "./js", comfy_entrypoint, ComfyExtension +src/cryptio/ + nodes.py # Node class imports + NODE_CLASS_MAPPINGS/NODE_DISPLAY_NAME_MAPPINGS + api.py # HTTP routes: /cryptio/exchange_keys, /upload_encrypted, /view_encrypted + text_crypt.py # TextEncrypt, TextDecrypt + upload_image.py # UploadImageCryptIO + save_image.py # SaveImageCryptIO, PreviewImageCryptIO + upload_video.py # UploadVideoCryptIO + save_video.py # SaveVideoCryptIO, PreviewVideoCryptIO + utils/ + crypto_utils.py # RSA+AES-GCM hybrid encrypt/decrypt, AES-CBC helpers + key_utils.py # KeyManager singleton + +ts/ # TypeScript source (compiled to js/) + cryptioSettings.ts # Settings panel UI + cryptioUploadImage.ts / cryptioUploadVideo.ts # Upload nodes + cryptioSaveImage.ts / cryptioSaveVideo.ts # Save/Preview nodes + textEncrypt.ts # Text encrypt node + utils/ # cryptoKeys, cryptoUtils, uploadUtils, fileUtils, loaders + +js/ # Compiled JS (served to browser, WEB_DIRECTORY); not in .gitignore +``` + +--- + +## References + +All references are **read-only** — do not modify files in these locations. + +- **ComfyUI node development skills** available in `.agents/skills/` (basics, inputs, outputs, datatypes, lifecycle, frontend, advanced, migration, packaging). Use the `skill` tool to load them when needed. +- **ComfyUI**: `D:\CODE\ComfyUI` — the ComfyUI installation directory. Reference for `folder_paths`, `comfy_api`, base types, and node infrastructure. +- **ComfyUI frontend source**: `D:\CODE\ComfyUI_frontend` — consult when debugging frontend integration (settings system, node UI, extension APIs). + +## CI + +- **build-pipeline.yml**: runs on PR to main/master — `ruff check .` then `pytest tests/` +- **validate.yml**: runs node-diff for backwards compatibility on PR +- **publish_node.yml**: publishes to Comfy registry + +Note: `tests/` directory exists but is currently empty. + +--- + +## Settings / Frontend Customization + +### SettingCustomRenderer API + +CryptIO uses custom DOM rendering for settings instead of built-in toggle/select types: + +```typescript +// type must be a function returning HTMLElement, NOT "boolean" or "combo" +{ + id: "cryptio.key_management", + type: renderCryptIOSettings, // (name, setter, value, attrs?) => HTMLElement + defaultValue: null, + category: ["CryptIO🔒", "Key Management"], +} +``` + +Called by `FormItem.vue` -> `CustomFormValue.vue` in ComfyUI frontend. + +### Category / Nav Structure + +- `category` **must have >= 2 levels** for a setting to appear as a nav tree node. Single-element `["CryptIO🔒"]` becomes a "floating" leaf under "Other". +- Dedicated nav group for CryptIO required modifying `useSettingUI.ts` in ComfyUI frontend — not part of this repo. + +### ComfyUI APIs (prefer these over raw DOM) + +```typescript +// Toasts +app.extensionManager.toast.add({ severity, summary, detail, life }) + +// Confirmation dialogs (instead of window.confirm) +app.extensionManager.dialog.confirm({ title, message }) + +// Setting access +app.extensionManager.setting.set("id", value) +``` + +### Styling + +Inject a `