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
42 changes: 23 additions & 19 deletions examples/javascript-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ You automatically become the **host** of the created group.
- Click "Dissolve Group" to exit and dissolve the group (host only)
- Only the group host can dissolve groups
- Dissolving removes all members and deletes the group
- **Group Dissolution Detection**: When a host dissolves a group, all member nodes automatically detect the dissolution via WebSocket subscription and are disconnected from the group

### 3. Sensor Data

Expand Down Expand Up @@ -178,10 +179,10 @@ await client.fireEventByNode(nodeId, groupId, domain, eventName, payload)
// Queries
await client.listGroupsByDomain(domain)

// Subscriptions (placeholder - polling for prototype)
// Subscriptions (WebSocket via AppSync)
client.subscribeToDataUpdates(groupId, domain, callback)
client.subscribeToEvents(groupId, domain, callback)
client.subscribeToGroupDissolve(groupId, domain, callback)
client.subscribeToGroupDissolve(groupId, domain, callback) // Real-time group dissolution detection
```

#### RateLimiter (mesh-client.js)
Expand Down Expand Up @@ -228,7 +229,8 @@ if (detector.hasChanged('temperature', 25)) {
- Connect with same API credentials
- Click "Refresh Group List"
- Join "Test Group"
- Observe events (when subscriptions implemented)
- Observe sensor data and events from Window 1 in real-time
- **Test Dissolution Detection**: When Window 1 dissolves the group, Window 2 should automatically detect and show error message

### Test Scenarios

Expand All @@ -245,6 +247,8 @@ if (detector.hasChanged('temperature', 25)) {
- [ ] Dissolve button only enabled when user is host
- [ ] Dissolve group removes group from list
- [ ] Host/Member role displays correctly
- [ ] Member nodes automatically detect and exit when host dissolves group
- [ ] Dissolution notification displays correct error message

#### Sensor Data
- [ ] Slider changes update display values
Expand All @@ -267,29 +271,29 @@ if (detector.hasChanged('temperature', 25)) {

## Known Limitations

### Prototype Phase 1
### Current Implementation Status

This is **Phase 1** of the prototype. The following features are placeholders:
The prototype has the following implementation status:

1. **WebSocket Subscriptions**
- Currently uses polling placeholders
- Will be implemented in Phase 2
- `subscribeToDataUpdates()` logs but doesn't actually subscribe
- `subscribeToEvents()` logs but doesn't actually subscribe
1. **Implemented WebSocket Subscriptions**
- ✅ `subscribeToGroupDissolve()` - Real-time group dissolution detection (Phase 2-4)
- ✅ `subscribeToDataUpdates()` - Real-time sensor data updates (Phase 2-2)
- ✅ `subscribeToEvents()` - Real-time event notifications (Phase 2-2)

2. **Backend API Gaps**
- `joinGroup` mutation not yet in backend (returns mock data)
- Backend Phase 2-4 will add join functionality
- `dissolveGroup` is implemented and working
2. **Backend API Status**
- ✅ `createGroup` - Fully implemented and working
- ✅ `joinGroup` - Fully implemented and working
- ✅ `dissolveGroup` - Fully implemented with automatic member notification
- ✅ `reportDataByNode` - Fully implemented with group existence validation
- ✅ `fireEventByNode` - Fully implemented with group existence validation

3. **Display of Other Nodes**
- "Other Nodes Data" panel is placeholder
- Will populate when subscriptions are working
3. **Display Features**
- ✅ "Other Nodes Data" panel displays real-time sensor data from group members
- ✅ Event history shows received events from other nodes
- ✅ Group dissolution automatically clears UI and shows notification

### Future Enhancements

- [ ] Implement real WebSocket subscriptions
- [ ] Display other nodes' sensor data in real-time
- [ ] Show group members list
- [ ] Add reconnection logic for network failures
- [ ] Persist group membership across page refresh
Expand Down
42 changes: 42 additions & 0 deletions examples/javascript-client/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const state = {
sessionTimerId: null,
dataSubscriptionId: null,
eventSubscriptionId: null,
dissolveSubscriptionId: null,
sensorData: {
temperature: 20,
brightness: 50,
Expand Down Expand Up @@ -379,6 +380,13 @@ async function handleJoinGroup() {
handleEventReceived
);

// Subscribe to group dissolution
state.dissolveSubscriptionId = state.client.subscribeToGroupDissolve(
state.currentGroup.id,
state.currentGroup.domain,
handleGroupDissolved
);

showSuccess('groupSuccess', `Joined group: ${state.selectedGroup.name}`);
updateCurrentGroupUI();
} catch (error) {
Expand Down Expand Up @@ -692,6 +700,40 @@ function handleEventReceived(event) {
showSuccess('eventSuccess', `Event received: ${event.name} from ${event.firedByNodeId}`);
}

/**
* Handle group dissolution notification
*/
function handleGroupDissolved(dissolveData) {
console.log('Group has been dissolved:', dissolveData);

// Unsubscribe from all active subscriptions
if (state.dataSubscriptionId) {
state.client.unsubscribe(state.dataSubscriptionId);
state.dataSubscriptionId = null;
}

if (state.eventSubscriptionId) {
state.client.unsubscribe(state.eventSubscriptionId);
state.eventSubscriptionId = null;
}

if (state.dissolveSubscriptionId) {
state.client.unsubscribe(state.dissolveSubscriptionId);
state.dissolveSubscriptionId = null;
}

// Clear group state
state.currentGroup = null;
state.selectedGroupId = null;

// Clear UI
displayOtherNodesData(null);
updateCurrentGroupUI();

// Show notification
showError('groupError', `Group has been dissolved: ${dissolveData.message}`);
}

/**
* Display other nodes' sensor data
*/
Expand Down
31 changes: 31 additions & 0 deletions js/functions/checkGroupExists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// checkGroupExists Pipeline Before Function
// グループの存在確認を行う共通Function
// reportDataByNode, fireEventByNode の前処理として使用

import { util } from '@aws-appsync/utils';

export function request(ctx) {
const { groupId, domain } = ctx.args;

return {
operation: 'GetItem',
key: util.dynamodb.toMapValues({
pk: `DOMAIN#${domain}`,
sk: `GROUP#${groupId}#METADATA`
})
};
}

export function response(ctx) {
// グループが存在しない場合はエラー
if (!ctx.result) {
util.error(
`Group ${ctx.args.groupId}@${ctx.args.domain} does not exist or has been dissolved`,
'GroupNotFound'
);
}

// グループ情報をcontextに保存(後続のfunctionで使用可能)
ctx.stash.group = ctx.result;
return ctx.result;
}
12 changes: 12 additions & 0 deletions js/resolvers/Mutation.joinGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ export function request(ctx) {
return {
operation: 'TransactWriteItems',
transactItems: [
// 0. グループの存在確認 (ConditionCheck)
{
table: ctx.env.TABLE_NAME,
operation: 'ConditionCheck',
key: util.dynamodb.toMapValues({
pk: `DOMAIN#${domain}`,
sk: `GROUP#${groupId}#METADATA`
}),
condition: {
expression: 'attribute_exists(pk)'
}
},
// 1. グループ内のノード情報を追加
{
table: ctx.env.TABLE_NAME,
Expand Down
59 changes: 53 additions & 6 deletions lib/mesh-v2-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,23 +168,70 @@ export class MeshV2Stack extends cdk.Stack {

// Resolvers for Phase 2-2: High-Frequency Mutations

// Mutation: reportDataByNode (DynamoDB integration)
dynamoDbDataSource.createResolver('ReportDataByNodeResolver', {
// Function: checkGroupExists (共通のグループ存在確認)
const checkGroupExistsFunction = new appsync.AppsyncFunction(this, 'CheckGroupExistsFunction', {
name: 'checkGroupExists',
api: this.api,
dataSource: dynamoDbDataSource,
runtime: appsync.FunctionRuntime.JS_1_0_0,
code: appsync.Code.fromAsset(path.join(__dirname, '../js/functions/checkGroupExists.js'))
});

// Function: reportDataByNode (main logic)
const reportDataByNodeFunction = new appsync.AppsyncFunction(this, 'ReportDataByNodeFunction', {
name: 'reportDataByNode',
api: this.api,
dataSource: dynamoDbDataSource,
runtime: appsync.FunctionRuntime.JS_1_0_0,
code: appsync.Code.fromAsset(path.join(__dirname, '../js/resolvers/Mutation.reportDataByNode.js'))
});

// Pipeline Resolver: reportDataByNode (グループ存在確認 → データ報告)
new appsync.Resolver(this, 'ReportDataByNodePipelineResolver', {
api: this.api,
typeName: 'Mutation',
fieldName: 'reportDataByNode',
runtime: appsync.FunctionRuntime.JS_1_0_0,
code: appsync.Code.fromAsset(path.join(__dirname, '../js/resolvers/Mutation.reportDataByNode.js'))
pipelineConfig: [checkGroupExistsFunction, reportDataByNodeFunction],
code: appsync.Code.fromInline(`
// Pipeline resolver: pass through
export function request(ctx) {
return {};
}
export function response(ctx) {
return ctx.prev.result;
}
`)
});

// None Data Source for event pass-through
const noneDataSource = this.api.addNoneDataSource('NoneDataSource');

// Mutation: fireEventByNode (None DataSource for pass-through)
noneDataSource.createResolver('FireEventByNodeResolver', {
// Function: fireEventByNode (main logic)
const fireEventByNodeFunction = new appsync.AppsyncFunction(this, 'FireEventByNodeFunction', {
name: 'fireEventByNode',
api: this.api,
dataSource: noneDataSource,
runtime: appsync.FunctionRuntime.JS_1_0_0,
code: appsync.Code.fromAsset(path.join(__dirname, '../js/resolvers/Mutation.fireEventByNode.js'))
});

// Pipeline Resolver: fireEventByNode (グループ存在確認 → イベント発火)
new appsync.Resolver(this, 'FireEventByNodePipelineResolver', {
api: this.api,
typeName: 'Mutation',
fieldName: 'fireEventByNode',
runtime: appsync.FunctionRuntime.JS_1_0_0,
code: appsync.Code.fromAsset(path.join(__dirname, '../js/resolvers/Mutation.fireEventByNode.js'))
pipelineConfig: [checkGroupExistsFunction, fireEventByNodeFunction],
code: appsync.Code.fromInline(`
// Pipeline resolver: pass through
export function request(ctx) {
return {};
}
export function response(ctx) {
return ctx.prev.result;
}
`)
});

// Resolvers for Phase 2-4: dissolveGroup with Lambda
Expand Down
Loading
Loading