Skip to content

Commit 1eb4414

Browse files
committed
feat(api-graphql): add WebSocket health monitoring with persistent storage
- Add getConnectionHealth() and getPersistentConnectionHealth() methods - Add isConnected(), reconnect(), and disconnect() controls - Implement cross-platform persistent storage (AsyncStorage/localStorage) - Track keep-alive messages with 65-second health threshold - Fixes #9749, #4459, #5403, #7057
1 parent dbcab40 commit 1eb4414

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed

packages/api-graphql/src/Providers/AWSWebSocketProvider/index.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,27 @@ import {
4949
} from './appsyncUrl';
5050
import { awsRealTimeHeaderBasedAuth } from './authHeaders';
5151

52+
// Platform-safe AsyncStorage import
53+
let AsyncStorage: any;
54+
try {
55+
// Try to import AsyncStorage for React Native (optional dependency)
56+
// eslint-disable-next-line import/no-extraneous-dependencies
57+
AsyncStorage = require('@react-native-async-storage/async-storage').default;
58+
} catch (e) {
59+
// Fallback for web/other platforms - use localStorage if available
60+
AsyncStorage =
61+
typeof localStorage !== 'undefined'
62+
? {
63+
setItem: (key: string, value: string) => {
64+
localStorage.setItem(key, value);
65+
66+
return Promise.resolve();
67+
},
68+
getItem: (key: string) => Promise.resolve(localStorage.getItem(key)),
69+
}
70+
: null;
71+
}
72+
5273
const dispatchApiEvent = (payload: HubPayload) => {
5374
Hub.dispatch('api', payload, 'PubSub', AMPLIFY_SYMBOL);
5475
};
@@ -682,6 +703,18 @@ export abstract class AWSWebSocketProvider {
682703
if (type === MESSAGE_TYPES.GQL_CONNECTION_KEEP_ALIVE) {
683704
this.maintainKeepAlive();
684705

706+
// Persist keep-alive timestamp for cross-session tracking
707+
if (AsyncStorage) {
708+
try {
709+
AsyncStorage.setItem(
710+
'AWS_AMPLIFY_LAST_KEEP_ALIVE',
711+
JSON.stringify(new Date()),
712+
);
713+
} catch (error) {
714+
this.logger.warn('Failed to persist keep-alive timestamp:', error);
715+
}
716+
}
717+
685718
return;
686719
}
687720

@@ -1051,6 +1084,59 @@ export abstract class AWSWebSocketProvider {
10511084
};
10521085
}
10531086

1087+
/**
1088+
* Get persistent WebSocket health state (survives app restarts)
1089+
*/
1090+
async getPersistentConnectionHealth(): Promise<
1091+
import('../../types').WebSocketHealthState
1092+
> {
1093+
let persistentKeepAliveTime: number | undefined;
1094+
let _timeSinceLastPersistentKeepAlive: number | undefined;
1095+
1096+
// Try to get persistent keep-alive timestamp
1097+
if (AsyncStorage) {
1098+
try {
1099+
const persistentKeepAlive = await AsyncStorage.getItem(
1100+
'AWS_AMPLIFY_LAST_KEEP_ALIVE',
1101+
);
1102+
if (persistentKeepAlive) {
1103+
const keepAliveDate = new Date(JSON.parse(persistentKeepAlive));
1104+
persistentKeepAliveTime = keepAliveDate.getTime();
1105+
_timeSinceLastPersistentKeepAlive =
1106+
Date.now() - persistentKeepAliveTime;
1107+
}
1108+
} catch (error) {
1109+
this.logger.warn(
1110+
'Failed to retrieve persistent keep-alive timestamp:',
1111+
error,
1112+
);
1113+
}
1114+
}
1115+
1116+
// Use the more recent timestamp (in-memory vs persistent)
1117+
const lastKeepAliveTime =
1118+
Math.max(this.keepAliveTimestamp || 0, persistentKeepAliveTime || 0) ||
1119+
undefined;
1120+
1121+
const timeSinceLastKeepAlive = lastKeepAliveTime
1122+
? Date.now() - lastKeepAliveTime
1123+
: undefined;
1124+
1125+
// Health check includes persistent data
1126+
const isHealthy =
1127+
this.connectionState === ConnectionState.Connected &&
1128+
lastKeepAliveTime &&
1129+
timeSinceLastKeepAlive !== undefined &&
1130+
timeSinceLastKeepAlive < 65000; // 65 second threshold
1131+
1132+
return {
1133+
isHealthy: Boolean(isHealthy),
1134+
connectionState: this.connectionState || ConnectionState.Disconnected,
1135+
lastKeepAliveTime,
1136+
timeSinceLastKeepAlive,
1137+
};
1138+
}
1139+
10541140
/**
10551141
* Check if WebSocket is currently connected
10561142
*/

packages/api-graphql/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ export interface WebSocketControl {
536536
disconnect(): void;
537537
isConnected(): boolean;
538538
getConnectionHealth(): WebSocketHealthState;
539+
getPersistentConnectionHealth(): Promise<WebSocketHealthState>;
539540
onConnectionStateChange(
540541
callback: (state: import('./PubSub').ConnectionState) => void,
541542
): () => void;

0 commit comments

Comments
 (0)