-
Notifications
You must be signed in to change notification settings - Fork 0
prototype: implement JavaScript client for Mesh v2 (Phase 1) #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- Create vanilla JavaScript client prototype - Implement GraphQL client with fetch API - Add domain management (URL param, 256 char limit) - Implement group operations (create, list, join, dissolve) - Add sensor data UI with rate limiting (4/sec) - Add event system with rate limiting (2/sec) - Implement 90-minute session management - Create Express server for static files - Add comprehensive README documentation Features: - dissolveGroup (host only) - updated from leaveGroup - RateLimiter and ChangeDetector utilities - LocalStorage for API configuration - Host/Member role display - Change detection for sensor data - Event history with auto-scroll - Session timer with warnings Technical Stack: - Vanilla JavaScript (ES6+) - Native fetch API for GraphQL - WebSocket subscriptions (placeholder) - No build tools required - Express.js for static serving Files: - examples/javascript-client/index.html - UI layout - examples/javascript-client/mesh-client.js - GraphQL client - examples/javascript-client/app.js - Application logic - examples/javascript-client/server.js - Express server - examples/javascript-client/package.json - Dependencies - examples/javascript-client/README.md - Documentation Related: #6, smalruby/smalruby3-gui#453 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed three critical issues: 1. mesh-client.js:213 - Syntax error - Fixed typo: "subscribeTo DataUpdates" → "subscribeToDataUpdates" - Method name had invalid space character 2. app.js:23 - ReferenceError - Moved RateLimiter/ChangeDetector initialization to DOMContentLoaded - Classes are now initialized after mesh-client.js loads - Changed from const to let for deferred initialization 3. favicon.ico 404 error - Added favicon.png from mesh extension icon - Source: gui/smalruby3-gui/src/lib/libraries/extensions/mesh/mesh-small.png - Updated HTML to reference local PNG file All JavaScript files now pass syntax validation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The joinGroupBtn remained disabled even after selecting a group from the list because selectGroup() function didn't call updateUI() to refresh button states. Added updateUI() call at the end of selectGroup() to properly enable/disable the Join Selected Group button based on selection state. Fixes group selection functionality in Group Management panel. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed three GraphQL validation errors by aligning prototype with actual backend implementation: 1. dissolveGroup: Changed return field from `dissolvedAt` to `message` (GroupDissolvePayload type) 2. reportDataByNode: Changed input type from `KeyValuePairInput` to `SensorDataInput` 3. joinGroup: Removed `nodeName` parameter - backend auto-generates node names All changes verified against graphql/schema.graphql and resolver implementations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed GraphQL validation error by using correct Event type fields: - Changed `eventName` to `name` - Changed `firedAt` to `timestamp` - Added `domain` field to match Event type schema Also updated event history display to use correct field names and simplified event handling to use API response directly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented data subscription to display other nodes' sensor values: Changes to mesh-client.js: - Added listGroupStatuses() query method - Updated subscribeToDataUpdates() to poll using listGroupStatuses every 2s - Real WebSocket subscriptions should be implemented in production Changes to app.js: - Added dataSubscriptionId to state - Added displayOtherNodesData() function to render other nodes' sensor data - Subscribe to data updates when creating or joining a group - Unsubscribe when dissolving group - Filter out current node from display This enables "Other Nodes Data" panel to show real-time sensor values from other nodes in the same group via polling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaced polling-based subscriptions with real GraphQL WebSocket subscriptions: Changes to package.json: - Added aws-amplify@6.0.0 dependency Changes to mesh-client.js: - Added ES module imports for AWS Amplify from esm.sh CDN - Configure Amplify with AppSync endpoint and API key - Implemented subscribeToDataUpdates() with real WebSocket subscription - Implemented subscribeToEvents() with real WebSocket subscription - Implemented subscribeToGroupDissolve() with real WebSocket subscription - Updated unsubscribe() and disconnect() for Amplify subscription cleanup - Export classes for ES module usage Changes to app.js: - Import MeshClient, RateLimiter, ChangeDetector from mesh-client.js Changes to index.html: - Load scripts as ES modules (type="module") This enables true real-time updates via WebSocket for: - Sensor data updates from other nodes - Events fired by other nodes - Group dissolution notifications 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added three missing query methods to complete schema coverage: 1. getGroup(groupId, domain) - Get specific group details by ID and domain - Returns Group type with all fields 2. getNodeStatus(nodeId) - Get current status of a specific node - Returns NodeStatus with sensor data and timestamp 3. listNodesInGroup(groupId, domain) - List all nodes that have joined a group - Returns array of Node types All schema queries (5/5), mutations (5/5), and subscriptions (3/3) are now implemented in the prototype. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed esm.sh CDN module resolution error by adding build step: Changes: - Added esbuild dev dependency for bundling - Updated package.json with build script - Changed imports from esm.sh CDN to local node_modules - Bundle mesh-client.js with AWS Amplify into mesh-client.bundle.js - Updated HTML to load bundled version - Updated app.js to import from bundle - Added .gitignore for build artifacts - Updated README with build process documentation Build Process: 1. npm run build - Creates mesh-client.bundle.js (~461KB) 2. npm start - Automatically builds and starts server 3. Bundle includes all AWS Amplify dependencies for WebSocket subscriptions This resolves the "does not provide an export named 'getId'" error by properly bundling Amplify's internal dependencies. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed ES module scoping issue where selectGroup was not accessible from inline onclick handlers. Changes: - Replaced onclick attribute with data attributes (data-group-id, etc.) - Added programmatic event listeners in displayGroupList() - Updated selectGroup() to find selected item by data-group-id - Removed reference to event.target (not available in new approach) This resolves "selectGroup is not defined" error that occurs when functions are scoped to ES modules instead of global scope. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed initialization timing issue where setInterval was called at module load time before rate limiters were initialized. Problem: - setInterval(updateRateStatus, 1000) was at top level of module - Rate limiters (sensorRateLimiter, eventRateLimiter) are initialized in DOMContentLoaded handler - This caused updateRateStatus to fail on first call Solution: - Moved setInterval into DOMContentLoaded handler - Now runs after rate limiters are properly initialized - Ensures all dependencies are ready before starting interval This prevents "Cannot read property 'getCallCount' of undefined" errors on page load. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed "Cannot return null for non-nullable type: NodeStatus" error in WebSocket subscriptions by ensuring NodeStatus exists before subscribing. Problem: - onDataUpdateInGroup subscription requires non-nullable NodeStatus! - When joining a group, no NodeStatus exists yet (no sensor data sent) - AppSync subscription validation fails with null error Solution: 1. Send initial sensor data immediately after joining/creating group 2. This creates NodeStatus entry in DynamoDB 3. Subscription can then receive updates without null errors 4. Enhanced error logging to show GraphQL error details Changes: - handleJoinGroup: Send initial sensor data before subscribing - handleCreateGroup: Send initial sensor data before subscribing - Subscription error handlers: Log detailed GraphQL errors This ensures NodeStatus exists for all group members and prevents subscription initialization errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Changed onDataUpdateInGroup return type from NodeStatus! to NodeStatus - Changed onEventInGroup return type from Event! to Event - Changed onGroupDissolve return type from GroupDissolvePayload! to GroupDissolvePayload This prevents GraphQL validation errors when subscriptions are triggered before NodeStatus/Event/GroupDissolvePayload entities exist. Added test to verify subscription return types are nullable. Deployed to staging and verified all 23 integration tests pass. Fixes subscription null error in prototype: "Cannot return null for non-nullable type: 'NodeStatus'" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated comments to clarify that initial sensor data is sent for initialization (sharing current state with group members), not for preventing null errors. The schema change to nullable subscriptions has resolved the null error issue, but sending initial sensor data is still good UX. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit implements comprehensive subscription testing and fixes the listGroupStatuses query resolver that was causing null errors. ## Subscription Testing (Issue #6) ### E2E Subscription Tests - Created AppSyncSubscriptionHelper for WebSocket testing - Implements AppSync WebSocket protocol (connection_init, start, data) - Base64-encoded authentication headers - graphql-ws protocol support - Added subscription_e2e_spec.rb with 2 E2E tests: - reportDataByNode → onDataUpdateInGroup subscription - fireEventByNode → onEventInGroup subscription - Added subscription_trigger_spec.rb with 9 validation tests: - Mutation response field validation for subscription filtering - GraphQL schema @aws_subscribe directive validation - Mutation/Subscription return type matching ### Test Results - 11 subscription tests: all passing - Validates real-time WebSocket notifications work correctly ## listGroupStatuses Query Implementation ### Problem - listGroupStatuses resolver was not implemented - Caused "Cannot return null for non-nullable type" error - Frontend mesh-client.js failed when calling listGroupStatuses ### Solution - Implemented Query.listGroupStatuses.js resolver - Queries DynamoDB for all NodeStatus in a group - Returns empty array (not null) when no data exists - Properly filters STATUS items - Added CDK stack registration for the resolver - Created comprehensive integration tests (node_status_spec.rb) ### Test Coverage - 3 integration tests: all passing - Validates data storage and retrieval - Tests multi-node scenarios - Confirms empty array handling ## Other Changes ### Schema Updates - Made mutation return types nullable to match subscriptions - reportDataByNode: NodeStatus! → NodeStatus - fireEventByNode: Event! → Event - dissolveGroup: GroupDissolvePayload! → GroupDissolvePayload ### Frontend Fix - Added missing 'domain' field to reportDataByNode query - Required for AppSync subscription filtering ### Debug Tools - Created debug-subscription.html for browser testing - Added SUBSCRIPTION_DEBUG.md documentation Fixes #6 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
… entries This commit fixes two issues with event handling in the prototype client: ## Event Subscription Implementation ### Problem - Events were being sent successfully via fireEventByNode mutation - However, subscription notifications were not being received - Event history only showed self-sent events, not events from other nodes ### Root Cause - subscribeToEvents was never called when joining a group - Only subscribeToDataUpdates was being registered ### Solution 1. Added event subscription registration on group join - Calls subscribeToEvents with handleEventReceived callback - Subscribes to onEventInGroup subscription 2. Implemented handleEventReceived callback - Adds received events to history - Shows notification when events arrive 3. Added eventSubscriptionId to application state 4. Properly unsubscribe from events on group dissolve ## Duplicate Event History Fix ### Problem - When sending an event, it appeared twice in the event history - Once when sent (immediate) - Again when received via subscription ### Solution - Removed addEventToHistory call from handleSendEvent - Events are now only added to history via subscription - This ensures consistent behavior for both self-sent and received events - All group members see the same event history ## Changes ### app.js - Added eventSubscriptionId to state - Subscribe to events on group join (lines 373-377) - Unsubscribe from events on group dissolve (lines 423-427) - Implemented handleEventReceived callback (lines 605-615) - Removed duplicate history entry on send (line 545) ### Benefits - Events now properly propagate to all group members via subscription - Event history shows all events exactly once - Consistent event handling for all participants 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add disconnect functionality to properly clean up resources and reduce AWS costs: - Add Disconnect button that appears when connected - Dissolve group if user is host on disconnect - Unsubscribe from all subscriptions (data updates and events) - Clear all state on disconnect Fix session timer countdown issue: - Store interval ID in state to allow proper cleanup - Stop timer countdown on disconnect - Reset timer display to default (Session: --:--) when disconnected - Prevent multiple timers from running simultaneously Implementation details: - Added sessionTimerId to state object - Modified startSessionTimer() to store and clear interval ID - Updated handleDisconnect() to stop timer and reset display - Updated updateUI() to show/hide disconnect button based on connection state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix all StandardRB linting issues related to string literals: - Change single quotes to double quotes in require statements - Change single quotes to double quotes in string literals - Change single quotes to double quotes in string interpolations - Change single quotes to double quotes in symbols Add best practice guideline to CLAUDE.md: - New section "7. Ruby String Literals" - Explains rationale for using double quotes - Provides good/bad examples - Includes StandardRB commands for checking and fixing Files auto-fixed by `bundle exec standardrb --fix`: - spec/requests/node_status_spec.rb (2 fixes) - spec/requests/subscription_e2e_spec.rb (9 fixes) - spec/requests/subscription_trigger_spec.rb (3 fixes) - spec/support/appsync_subscription_helper.rb (23 fixes) Total: 37 style violations fixed 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Overview
Implements Phase 1 of the JavaScript client prototype for Mesh v2 integration (#6). This provides a reference implementation using vanilla JavaScript (no TypeScript, no build tools) to match Smalruby's technology stack.
Related Issues:
What's Implemented
Phase 1: Basic Setup ✅
1. Directory Structure
2. GraphQL Client (mesh-client.js)
Vanilla JavaScript client using native fetch API:
Implemented Mutations:
createGroup(name, hostId, domain)dissolveGroup(groupId, hostId, domain)- Host onlyreportDataByNode(nodeId, groupId, domain, data)fireEventByNode(nodeId, groupId, domain, eventName, payload)Implemented Queries:
listGroupsByDomain(domain)Mock Implementations:
joinGroup(groupId, nodeId, nodeName, domain)- Returns mock data until backend readySubscription Placeholders:
subscribeToDataUpdates()- Polling placeholder for Phase 2subscribeToEvents()- Polling placeholder for Phase 2subscribeToGroupDissolve()- Polling placeholder for Phase 23. UI Features
Configuration Panel:
?mesh=domain)Group Management:
Sensor Data:
Event System:
Session Management:
4. Helper Utilities
RateLimiter:
ChangeDetector:
Key Changes from Issue #6
dissolveGroup Implementation
Updated behavior based on backend design:
leaveGroupmutationUI Updates:
Testing
Local Testing
With Staging API
Get credentials:
Configure in UI:
https://...appsync-api....com/graphqlda2-...Test scenarios:
Lint & Build
Documentation
README.md includes:
Implementation Notes
Technology Stack
Code Quality
Future Work (Phase 2-5)
Phase 2: Group Management
joinGroupmutationPhase 3: Sensor Data
Phase 4: Events
Phase 5: Polish
Files Changed
Success Criteria
Screenshots
Server startup:
Next Steps
mainjoinGroupin backend🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com