This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
GCopy is a clipboard synchronization service that allows sharing clipboard data (text, screenshots, files) between different operating systems via a web browser. It uses in-memory storage with 24-hour expiration and email-based authentication.
- Entry point:
cmd/gcopy.go - Server:
internal/server/server.go- Gin HTTP server with routes for clipboard sync and user auth - Clipboard storage:
internal/server/wall.go- In-memory storage usingsync.Map, with automatic expiration after 24 hours - Authentication:
internal/server/user.go- Email-based auth with 6-digit verification codes (5-minute expiration), sessions stored via cookies - Configuration:
internal/config/- Command-line flag parsing for SMTP, TLS, and app settings - Clipboard types:
internal/gcopy/constants.godefines three types:text,screenshot,file
- Entry:
frontend/app/[locale]/page.tsx- Main page with sync functionality - Key libraries:
frontend/lib/clipboard.ts- Clipboard API interactions (read/write blobs)frontend/lib/auth.ts- SWR-based authentication state management
- API proxy:
frontend/next.config.jsrewrites/api/v1/*to backendSERVER_URL - i18n: Uses
next-intlfor internationalization
- User logs in via email verification code
- Session stored in cookie (managed by
gorilla/sessions) - Clipboard data is stored per-email in the Wall (in-memory map)
- Each clipboard update increments an index for conflict detection
- Manual sync: User clicks sync button (or uses keyboard shortcut) to pull/push clipboard data
- Frontend tracks state via URL query params:
ci(clipboard index) andcbi(clipboard blob ID hash)
# Run directly (development)
go run cmd/gcopy.go \
-app-key=<app-key> \
-smtp-host=<smtp-host> \
-smtp-port=<smtp-port> \
-smtp-username=<smtp-username> \
-smtp-password=<smtp-password> \
-smtp-ssl \
-debug
# Build binary
make ./bin/gcopy
# Run tests
make test
# Format and vet
make vet fmtcd frontend
# Install dependencies
npm ci
# Development (with HTTPS on port 3375)
npm run dev
# Build for production
npm run build
cp -r .next/static .next/standalone/.next/
NODE_ENV=production PORT=3375 node .next/standalone/server.js
# Lint
npm run lint
# Format
npm run prettier# Run all Go tests
make test
# Run specific package tests
go test -v ./pkg/utils/...
# Run single test
go test -v -run TestFunctionName ./path/to/packageWhen updating the project version:
- Modify version.txt file: Update the version number directly (e.g.,
v1.5.0) - Run sync command: Execute
make versionto sync the version to frontend package.json and format code
Example:
# 1. Edit version.txt file to change version to v1.5.0
# 2. Run sync command
make versionNote: Do NOT manually edit frontend/package.json version number. Always use make version to sync automatically.
- Uses IndexedDB via Dexie (
frontend/models/db.ts) - Stores up to 20 non-pinned items (auto-deletes older items)
- Stores
dataArrayBufferseparately to fix Safari WebKitBlobResource error - Each item has:
createdAt,pin(true/false),index,blobId,data,type,fileName
-app-key: Encryption key for sessions (required, min 8 chars)-auth-mode: Authentication mode:emailortoken(defaultemail)-smtp-*: Email service configuration (host, port, username, password, ssl, sender) - required for email mode only-listen: Server address (default:3376)-max-content-length: Max clipboard size in MiB (default 10)-debug: Enable debug logging
Note: TLS is handled by reverse proxy (e.g., Nginx), not by GCopy backend. Do not use -tls, -tls-cert-file, or -tls-key-file flags.
GCopy uses a single Docker image that contains both frontend and backend:
- Built from
build/Dockerfileusing multi-stage build - Frontend built with Next.js standalone mode
- Backend compiled as Go binary
- Uses supervisord for process management
- Only exposes port 3375 (frontend port)
- Frontend proxies API requests to backend internally on port 3376
Environment Variables (use GCOPY_ prefix to avoid conflicts):
GCOPY_APP_KEY: Encryption key (required)GCOPY_AUTH_MODE: Authentication modeGCOPY_SMTP_*: SMTP configurationGCOPY_LISTEN: Backend listen addressGCOPY_MAX_CONTENT_LENGTH: Max clipboard sizeGCOPY_DEBUG: Enable debug mode
Log Differentiation:
- Frontend logs prefixed with
[frontend] - Backend logs prefixed with
[backend]
SERVER_URL: Backend API URL (default in.env.sample:http://gcopy:3376).env.localfor development,.env.productionfor production- Development server uses self-signed certificates for HTTPS (required for Clipboard API)
Manual Sync Mechanism (NOT automatic polling):
- User clicks "Sync" button or uses keyboard shortcut (Enter, Cmd+1 on Mac, Ctrl+1 on other OS)
- Frontend calls
syncFunc()which:- First attempts to pull from server (
pullClipboard()) - If server has no new data (same index), automatically pushes local clipboard (
pushClipboard()) - Safari exception: Requires two button presses due to clipboard API restrictions
- First attempts to pull from server (
Pull Flow (pullClipboard()):
- GET
/api/v1/clipboardwithX-Indexheader from URL paramci - If server index matches local index: returns 200 with same index (no new data)
- If new data available:
- Downloads blob and writes to clipboard
- Updates URL params:
ci(new index),cbi(blob hash) - Adds to IndexedDB history
- For Safari: sets
status="interrupted-w", requires second click to write clipboard
Push Flow (pushClipboard()):
- Reads from quick-input textarea OR clipboard (textarea takes priority if not empty)
- Determines type by MIME:
text/plain,text/html,text/uri-list→text(converts to text/plain)image/png→screenshot- Other types → error (unsupported format)
- POST
/api/v1/clipboardwithX-Type,X-FileNameheaders and blob body - Updates URL params with new index and blob hash
- Adds to IndexedDB history
- Clears quick-input textarea after successful upload
State Tracking via URL:
?ci=<index>&cbi=<blob-hash>: Track current clipboard state- Prevents re-downloading same content
- Enables conflict detection
Safari Special Handling:
- Requires user gesture for each Clipboard API call
- Two-step process: first click fetches data, second click writes to clipboard
- Status states:
interrupted-r(need to push),interrupted-w(need to write)
Android Chrome/Edge Bug:
- Cannot read clipboard immediately after writing
- Waits 1 second before reading back to verify
- In-memory
sync.Mapkeyed by user email - Each clipboard:
Index(incremental),Type,Data([]byte),FileName,MIMEType,ClientName,CreatedAt - Housekeeping goroutine runs every minute, deletes clipboards older than 24 hours
- No persistence - data lost on server restart
- POST
/api/v1/user/email-code- Send 6-digit code to email- Generates random 6-digit code
- Stores in session:
email,code,loggedIn=false,validateAt - Sends email via SMTP with localized message (zh-CN or English)
- POST
/api/v1/user/login- Verify code- Checks: email matches, code matches, within 5 minutes of
validateAt - Sets
loggedIn=truein session
- Checks: email matches, code matches, within 5 minutes of
- GET
/api/v1/user- Check session status- Returns
emailandloggedInstatus
- Returns
- GET
/api/v1/user/logout- Clear session- Deletes all session values
Session Management:
- Uses
gorilla/sessionswith cookie store - Cookie name:
user_session - Encrypted with
-app-keyflag value - Sessions validated on each clipboard API request via
verifyAuthMiddleware
Upload:
- Drag & drop or file picker triggers
uploadFileHandler() - POST
/api/v1/clipboardwithX-Type: fileandX-FileName(URI encoded) - File sent as request body
- Updates URL param
cionly (no clipboard write) - Displays download link on success
Download:
- Pull flow detects
X-Type: fileheader - Decodes
X-FileNamefrom header - Creates
Fileobject and blob URL - Displays download link (does NOT write to clipboard due to browser limitations)
Browser Detection:
- Uses
react-device-detectfor OS/browser-specific logic - Safari: Requires two-step clipboard operations
- Android Chrome/Edge: Delayed clipboard read after write
Quick Input:
- Textarea for manual text input
- If not empty, uses textarea value instead of reading clipboard
- Cleared after successful push
History Management:
- IndexedDB via Dexie ORM
- Stores up to 20 recent items (pinned items kept separately)
- Auto-converts blob to ArrayBuffer for Safari compatibility
- Displays pinned items first, then recent items
Keyboard Shortcuts:
- Desktop only: Enter, Cmd+1 (Mac), Ctrl+1 (other OS)
- Triggers sync button click
Depends on Clipboard API (requires HTTPS). Tested on Chrome, Edge, Opera, Safari (macOS/iOS), and mobile browsers. See README for specific versions.