myscript/iinkts/client-system
Claude Code and agent skill by myscript/iinkts
- community
Checking that the SKILL.md file is still in the repository.
Understanding the client architecture (WebSocket vs HTTP, protocol flow, message handling). Use when working with backend communication, implementing new client features, or debugging recognition issues.
Install
npx vskill@latest install myscript/iinkts/client-systemSKILL.md
Client System Architecture
Overview
The client system handles communication with the MyScript Cloud backend for handwriting recognition. Lives in src/client/.
Canvas variant → client mapping:
| Client | Protocol | Used by | |
|---|---|---|---|
WebSocketClient | WebSocket | InteractiveInkCanvas | |
WebSocketSSRClient | WebSocket (separate class, own protocol additions e.g. svgPatch) | InteractiveInkSSRCanvas | |
HTTPClientV2 | HTTP/REST, stateless batch | InkCanvas | |
HTTPClientV1 | HTTP/REST, deprecated | InkCanvasDeprecated |
WebSocketSSRClient is not a subclass of WebSocketClient — it's an independent implementation with its own config type (WebSocketSSRClientConfiguration). This skill covers WebSocketClient and HTTPClientV2/V1 in detail; consult src/client/WebSocketSSRClient.ts directly for SSR-specific protocol details.
WebSocket Protocol Flow
Implementation: src/client/WebSocketClient.ts — a sequence diagram already lives in the doc comment at the top of the file (lines 36-55), consult it first.
1. Connection + handshake
Auth is a message-field challenge/response, not an HTTP header (that's HTTP-only, see below):
HMAC computation: src/utils/crypto.ts — computeHmac(message, applicationKey, hmacKey). Concatenates applicationKey + hmacKey as the HMAC-SHA-512 key (via WebCrypto crypto.subtle), signs message, hex-encodes.
2. Recognition flow
3. Interactive operations
There is no `convert` message on the WebSocket protocol — conversion is an HTTPClientV1-only concept (HTTPClientV1.convert()).
4. Session management
There is no `close`/`closed` JSON message. Closing the session is a native WebSocket.close(code, reason) call, handled via the native CloseEvent, not a protocol message.
Type definitions: src/client/WebSocketClientMessage.ts — full server→client enum TWebSocketClientMessageType: hmacChallenge, authenticated, sessionDescription, newPart, partChanged, contentChanged, idle, pong, exported, gestureDetected, contextlessGesture, mathSolverResult, error, ack.
Event emission
Events go through ClientEvent (src/client/ClientEvent.ts), enum ClientEventName:
START_INITIALIZATION/END_INITIALIZATIONCONTENT_CHANGED,IDLE,EXPORTED,ERRORCONNECTION_CLOSESVG_PATCH(WebSocket/SSR only)SESSION_OPENEDCONNECTION_STATUS_CHANGED—"connected" | "offline" | "error"GESTURE_DETECTED
There is no dedicated `CLEARED` event — clear() just triggers the normal contentChanged flow.
HTTP Protocol Flow (HTTPClientV2)
Implementation: src/client/HTTPClientV2.ts
Single entry point, no separate init/connect/export calls — HTTP is a stateless one-shot recognize:
HTTPClientV2 has no addStrokes, transform, erase, undo, redo, close, or destroy methods — those are WebSocket-only concepts.
HTTPClientV1 (deprecated)
src/client/HTTPClientV1.ts — @deprecated Use HTTPClientV2 instead. Endpoint /api/v4.0/iink/batch. Body uses legacy strokeGroups (grouped by pen style) plus xDPI/yDPI/height/width/optional conversionState — materially different shape from V2. Uniquely exposes convert(model, conversionState?, requestedMimeTypes?) and resize(model), neither of which exist on V2 or the WebSocket client.
Message Handling Pattern: DeferredPromise
src/utils/DeferredPromise.ts — .promise/.resolve/.reject/.isFullFilled/.isPending. Used throughout WebSocketClient to track pending round trips keyed by blockId/strokeId/mimeType, since the protocol has no universal correlation id:
Authentication summary
- WebSocket: message-field challenge/response (
hmacChallenge→ client replies{ type: 'hmac', hmac }). See flow above. - HTTP:
computeHmac(JSON.stringify(body), applicationKey, hmacKey)sent as a plainhmacrequest header alongsideapplicationKeyheader — no challenge round trip.
Reconnection Strategy (WebSocketClient only)
Two layers, both in WebSocketClient.ts:
- Offline queue + background reconnect loop — when
addStrokes()etc. is called while disconnected andserver.websocket.offlineQueueEnabledis true, messages queue (#enqueueOfflineMessage) and#startReconnectLoop()retriesinit()everyserver.websocket.reconnectDelayms (default 3000) up tomaxReconnectAttempts(default 10), then#giveUpReconnecting()rejects the queue and emitsCONNECTION_STATUS_CHANGED: "error". On reconnect,#drainOfflineQueue()replays queued messages in order. - Legacy synchronous auto-reconnect in `send()` — if the socket is closing/closed and
server.websocket.autoReconnectis true (default),send()itself callsinit()and retries, bounded byserver.websocket.maxRetryCount(default 2).
Guards #connectingPromise / #closingPromise prevent both paths from racing a deliberate close()/newSession() teardown.
Ping Worker (WebSocket Keep-Alive)
src/worker/ping.worker.ts — pure ticker, no server contact from the worker itself:
Wired in WebSocketClient.initPing(): on each worker tick, sends a real { type: 'ping' } WS message if pingCount < maxPingLostCount, else closes the socket with reason MAXIMUM_PING_REACHED. pingCount resets on any non-pong message or pong.
Config in ServerConfiguration.ts (DefaultServerWebsocketConfiguration): pingEnabled (default true), pingDelay (default 15000ms), maxPingLostCount (default 20).
Testing Clients
See test/unit/client/ for real specs: WebSocketClient.test.ts, WebSocketSSRClient.test.ts, HTTPClientV1.test.ts, HTTPClientV2.test.ts.
Best Practices
✅ Do use DeferredPromise for async operations ✅ Do validate stroke data before sending ✅ Do handle ERROR and CONNECTION_STATUS_CHANGED events ✅ Do rely on the built-in reconnection (don't hand-roll another layer on top) ✅ Do clean up on destroy (close(), worker termination)
❌ Don't invent message types not in TWebSocketClientMessageType / the client→server literals in WebSocketClient.ts ❌ Don't mix HTTP and WebSocket clients on the same canvas instance ❌ Don't assume immediate recognition — wait for contentChanged/idle, there's no per-op ack ❌ Don't call HTTPClientV2-only stateless patterns (single send()) on a WebSocket client or vice versa
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/client-system/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/myscript/iinkts/client-system)