Skip to content

Commit addaa0d

Browse files
authored
Merge pull request #166 from Resgrid/develop
CU-868fdadum Expo update for And16k, bug fixes and tweaks.
2 parents e310d87 + 83c0704 commit addaa0d

38 files changed

+4996
-634
lines changed

.DS_Store

0 Bytes
Binary file not shown.

.github/dependabot.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ version: 2
77
updates:
88
- package-ecosystem: "npm" # See documentation for possible values
99
directory: "/" # Location of package manifests
10+
open-pull-requests-limit: 0
1011
schedule:
1112
interval: "weekly"

.github/workflows/react-native-cicd.yml

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -276,30 +276,32 @@ jobs:
276276
run: |
277277
firebase appdistribution:distribute ./ResgridUnit-ios-adhoc.ipa --app ${{ secrets.FIREBASE_IOS_APP_ID }} --groups "testers"
278278
279-
- name: 📋 Extract Release Notes from PR Body
279+
- name: 📋 Prepare Release Notes file
280280
if: ${{ matrix.platform == 'android' }}
281281
env:
282+
RELEASE_NOTES_INPUT: ${{ github.event.inputs.release_notes }}
282283
PR_BODY: ${{ github.event.pull_request.body }}
283284
run: |
284285
set -eo pipefail
285-
# Grab lines after "## Release Notes" until the next header
286-
RELEASE_NOTES="$(printf '%s\n' "$PR_BODY" \
287-
| awk 'f && /^## /{f=0} /^## Release Notes/{f=1; next} f')"
288-
# Use a unique delimiter to write multiline into GITHUB_ENV
289-
delimiter="EOF_$(date +%s)_$RANDOM"
290-
{
291-
echo "RELEASE_NOTES<<$delimiter"
292-
printf '%s\n' "${RELEASE_NOTES:-No release notes provided.}"
293-
echo "$delimiter"
294-
} >> "$GITHUB_ENV"
295-
296-
- name: 📋 Prepare Release Notes file
297-
if: ${{ matrix.platform == 'android' }}
298-
run: |
286+
# Determine source of release notes: workflow input, PR body, or recent commits
287+
if [ -n "$RELEASE_NOTES_INPUT" ]; then
288+
NOTES="$RELEASE_NOTES_INPUT"
289+
elif [ -n "$PR_BODY" ]; then
290+
NOTES="$(printf '%s\n' "$PR_BODY" \
291+
| awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')"
292+
else
293+
NOTES="$(git log -n 5 --pretty=format:'- %s')"
294+
fi
295+
# Fail if no notes extracted
296+
if [ -z "$NOTES" ]; then
297+
echo "Error: No release notes extracted" >&2
298+
exit 1
299+
fi
300+
# Write header and notes to file
299301
{
300-
echo "## Version 7.${{ github.run_number }} - $(date +%Y-%m-%d)"
302+
echo "## Version 10.${{ github.run_number }} - $(date +%Y-%m-%d)"
301303
echo
302-
printf '%s\n' "${RELEASE_NOTES:-No release notes provided.}"
304+
printf '%s\n' "$NOTES"
303305
} > RELEASE_NOTES.md
304306
305307
- name: 📦 Create Release

app.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
113113
allowAlert: true,
114114
allowBadge: true,
115115
allowSound: true,
116+
allowCriticalAlerts: true,
116117
},
117118
},
118119
sounds: [
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# SignalR Service Reconnection Self-Blocking Fix
2+
3+
## Problem
4+
5+
The SignalR service had a self-blocking issue where reconnection attempts would prevent direct connection attempts. Specifically:
6+
7+
1. When a hub was in "reconnecting" state, direct connection attempts would be blocked
8+
2. This could lead to scenarios where:
9+
- A reconnection attempt was in progress
10+
- A user tried to manually connect
11+
- The manual connection would be rejected
12+
- If the reconnection failed, the hub would be stuck in reconnecting state
13+
- Future manual connection attempts would continue to be blocked
14+
15+
## Root Cause
16+
17+
The service used a single `reconnectingHubs` Set to track both:
18+
- Automatic reconnection attempts
19+
- Direct connection attempts
20+
21+
This caused the guard logic in `_connectToHubInternal` (lines 240-246) to block direct connections when hubs were in reconnecting state.
22+
23+
## Solution
24+
25+
Implemented a more granular state management system:
26+
27+
### 1. New State Enum
28+
29+
```typescript
30+
export enum HubConnectingState {
31+
IDLE = 'idle',
32+
RECONNECTING = 'reconnecting',
33+
DIRECT_CONNECTING = 'direct-connecting',
34+
}
35+
```
36+
37+
### 2. State Management
38+
39+
- Added `hubStates: Map<string, HubConnectingState>` to track individual hub states
40+
- Added `setHubState()` method to manage state transitions and maintain backward compatibility
41+
- Added helper methods: `isHubConnecting()`, `isHubReconnecting()`
42+
43+
### 3. Updated Connection Logic
44+
45+
**Direct Connections (`_connectToHubInternal` and `_connectToHubWithEventingUrlInternal`):**
46+
- Only block duplicate direct connections (same `DIRECT_CONNECTING` state)
47+
- Allow direct connections even when hub is in `RECONNECTING` state
48+
- Log reconnecting state but proceed with connection attempt
49+
- Set state to `DIRECT_CONNECTING` during connection attempt
50+
- Clean up state on both success and failure
51+
52+
**Automatic Reconnections:**
53+
- Set state to `RECONNECTING` during reconnection attempts
54+
- Clean up state on both success and failure
55+
- Maintain existing reconnection logic and limits
56+
57+
### 4. Backward Compatibility
58+
59+
- Maintained the `reconnectingHubs` Set for existing API compatibility
60+
- `setHubState()` automatically manages the legacy set alongside the new state map
61+
- All existing methods continue to work as expected
62+
63+
## Key Changes
64+
65+
1. **Lines 240-246**: Changed from blocking all connections during reconnect to only blocking duplicate direct connections
66+
2. **State Management**: Added proper state tracking with cleanup in success/failure paths
67+
3. **Connection Isolation**: Reconnection attempts and direct connections now operate independently
68+
4. **Cleanup**: Ensured state cleanup happens in all code paths to prevent stuck states
69+
70+
## Testing
71+
72+
- Updated existing tests to use new state management system
73+
- All existing tests continue to pass
74+
- Tests verify that direct connections are allowed during reconnection
75+
- Tests verify proper state cleanup in success and failure scenarios
76+
77+
## Benefits
78+
79+
- Eliminates self-blocking behavior during reconnections
80+
- Allows users to manually retry connections even during automatic reconnection
81+
- Prevents permanent stuck states
82+
- Maintains full backward compatibility
83+
- Provides better separation of concerns between automatic and manual connections
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# SignalR Service and Map Hook Refactoring
2+
3+
## Summary of Changes
4+
5+
This refactoring addresses multiple issues with the SignalR service and map updates to prevent concurrent API calls, improve performance, and ensure thread safety.
6+
7+
## Key Issues Addressed
8+
9+
1. **Multiple concurrent calls to GetMapDataAndMarkers**: SignalR events were triggering multiple simultaneous API calls
10+
2. **Lack of singleton enforcement**: SignalR service singleton pattern wasn't thread-safe
11+
3. **No request cancellation**: In-flight requests weren't being cancelled when new events came in
12+
4. **No debouncing**: Rapid consecutive SignalR events caused unnecessary API calls
13+
5. **No connection locking**: Multiple concurrent connection attempts to the same hub were possible
14+
15+
## Changes Made
16+
17+
### 1. Enhanced SignalR Service (`src/services/signalr.service.ts`)
18+
19+
#### Thread-Safe Singleton Pattern
20+
- Added proper singleton instance management with race condition protection
21+
- Added `resetInstance()` method for testing purposes
22+
- Improved singleton creation with polling mechanism to prevent multiple instances
23+
24+
#### Connection Locking
25+
- Added `connectionLocks` Map to prevent concurrent connections to the same hub
26+
- Added locking for `connectToHubWithEventingUrl()` and `connectToHub()` methods
27+
- Added waiting logic for `disconnectFromHub()` and `invoke()` methods to wait for ongoing connections
28+
29+
#### Improved Reconnection Logic
30+
- Enhanced `handleConnectionClose()` with better error handling and logging
31+
- Added proper cleanup on max reconnection attempts reached
32+
- Improved connection state management during reconnection attempts
33+
- Added check to prevent reconnection if connection was re-established during delay
34+
35+
#### Better Error Handling
36+
- Enhanced logging for all connection states
37+
- Improved error context in log messages
38+
- Added proper cleanup on connection failures
39+
40+
### 2. Refactored Map Hook (`src/hooks/use-map-signalr-updates.ts`)
41+
42+
#### Debouncing
43+
- Added 1-second debounce delay to prevent rapid consecutive API calls
44+
- Uses `setTimeout` to debounce SignalR update events
45+
46+
#### Concurrency Prevention
47+
- Added `isUpdating` ref to prevent multiple concurrent API calls
48+
- Only one `getMapDataAndMarkers` call can be active at a time
49+
50+
#### Request Cancellation
51+
- Added `AbortController` support to cancel in-flight requests
52+
- Previous requests are automatically cancelled when new updates come in
53+
- Proper cleanup of abort controllers
54+
55+
#### Enhanced Error Handling
56+
- Added special handling for `AbortError` (logged as debug, not error)
57+
- Improved error context in log messages
58+
- Better error recovery mechanisms
59+
60+
#### Proper Cleanup
61+
- Added cleanup for debounce timers on unmount
62+
- Added cleanup for abort controllers on unmount
63+
- Proper cleanup in useEffect dependency arrays
64+
65+
## Performance Improvements
66+
67+
1. **Reduced API Calls**: Debouncing prevents excessive API calls during rapid SignalR events
68+
2. **Request Cancellation**: Prevents unnecessary processing of outdated requests
69+
3. **Singleton Enforcement**: Ensures only one SignalR service instance exists
70+
4. **Connection Reuse**: Prevents duplicate connections to the same hub
71+
5. **Better Memory Management**: Proper cleanup prevents memory leaks
72+
73+
## Testing
74+
75+
### New Test Coverage
76+
- Comprehensive test suite for `useMapSignalRUpdates` hook (14 tests)
77+
- Tests for debouncing, concurrency prevention, error handling, and cleanup
78+
- Tests for AbortController integration
79+
- Tests for edge cases and error scenarios
80+
81+
### Enhanced SignalR Service Tests
82+
- Added tests for singleton behavior
83+
- Added tests for connection locking
84+
- Enhanced existing test coverage
85+
- Added tests for improved reconnection logic
86+
87+
## Configuration
88+
89+
### Debounce Timing
90+
- Default debounce delay: 1000ms (configurable via `DEBOUNCE_DELAY` constant)
91+
- Can be adjusted based on performance requirements
92+
93+
### Reconnection Settings
94+
- Max reconnection attempts: 5 (unchanged)
95+
- Reconnection interval: 5000ms (unchanged)
96+
- Enhanced with better cleanup and state management
97+
98+
## Backward Compatibility
99+
100+
All changes are backward compatible:
101+
- Public API of SignalR service remains unchanged
102+
- Map hook interface remains the same
103+
- Existing functionality is preserved with performance improvements
104+
105+
## Usage
106+
107+
The refactored components work transparently with existing code:
108+
109+
```typescript
110+
// SignalR service usage remains the same
111+
const signalRService = SignalRService.getInstance();
112+
await signalRService.connectToHubWithEventingUrl(config);
113+
114+
// Map hook usage remains the same
115+
useMapSignalRUpdates(onMarkersUpdate);
116+
```
117+
118+
## Benefits
119+
120+
1. **Improved Performance**: Fewer unnecessary API calls, better request management
121+
2. **Better User Experience**: Faster map updates, reduced server load
122+
3. **Enhanced Reliability**: Better error handling, improved connection management
123+
4. **Memory Efficiency**: Proper cleanup prevents memory leaks
124+
5. **Thread Safety**: Singleton pattern prevents race conditions
125+
6. **Testability**: Comprehensive test coverage ensures reliability
126+
127+
## Future Considerations
128+
129+
1. **Configurable Debounce**: Could make debounce delay configurable via environment variables
130+
2. **Request Priority**: Could implement priority system for different types of updates
131+
3. **Caching**: Could add intelligent caching for map data
132+
4. **Health Monitoring**: Could add connection health monitoring and reporting

package.json

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"build:production:android": "cross-env APP_ENV=production EXPO_NO_DOTENV=1 eas build --profile production --platform android ",
3434
"build:internal:ios": "cross-env APP_ENV=internal EXPO_NO_DOTENV=1 eas build --profile internal --platform ios",
3535
"build:internal:android": "cross-env APP_ENV=internal EXPO_NO_DOTENV=1 eas build --profile internal --platform android",
36+
"postinstall": "patch-package",
3637
"app-release": "cross-env SKIP_BRANCH_PROTECTION=true np --no-publish --no-cleanup --no-release-draft",
3738
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
3839
"type-check": "tsc --noemit",
@@ -91,7 +92,7 @@
9192
"@notifee/react-native": "^9.1.8",
9293
"@novu/react-native": "~2.6.6",
9394
"@react-native-community/netinfo": "^11.4.1",
94-
"@rnmapbox/maps": "10.1.38",
95+
"@rnmapbox/maps": "10.1.42-rc.0",
9596
"@semantic-release/git": "^10.0.1",
9697
"@sentry/react-native": "~6.10.0",
9798
"@shopify/flash-list": "1.7.3",
@@ -142,23 +143,23 @@
142143
"react-error-boundary": "~4.0.13",
143144
"react-hook-form": "~7.53.0",
144145
"react-i18next": "~15.0.1",
145-
"react-native": "0.76.9",
146+
"react-native": "0.77.3",
146147
"react-native-base64": "~0.2.1",
147148
"react-native-ble-manager": "^12.1.5",
148149
"react-native-callkeep": "github:Irfanwani/react-native-callkeep#957193d0716f1c2dfdc18e627cbff0f8a0800971",
149150
"react-native-edge-to-edge": "~1.1.2",
150151
"react-native-flash-message": "~0.4.2",
151-
"react-native-gesture-handler": "~2.20.2",
152+
"react-native-gesture-handler": "~2.22.0",
152153
"react-native-keyboard-controller": "~1.15.2",
153154
"react-native-logs": "~5.3.0",
154155
"react-native-mmkv": "~3.1.0",
155-
"react-native-reanimated": "~3.16.1",
156+
"react-native-reanimated": "~3.16.7",
156157
"react-native-restart": "0.0.27",
157-
"react-native-safe-area-context": "4.12.0",
158-
"react-native-screens": "~4.4.0",
158+
"react-native-safe-area-context": "~5.1.0",
159+
"react-native-screens": "~4.8.0",
159160
"react-native-svg": "~15.8.0",
160161
"react-native-web": "~0.19.13",
161-
"react-native-webview": "13.12.5",
162+
"react-native-webview": "~13.13.1",
162163
"react-query-kit": "~3.3.0",
163164
"tailwind-variants": "~0.2.1",
164165
"zod": "~3.23.8",
@@ -201,6 +202,8 @@
201202
"jest-junit": "~16.0.0",
202203
"lint-staged": "~15.2.9",
203204
"np": "~10.0.7",
205+
"patch-package": "^8.0.0",
206+
"postinstall-postinstall": "^2.1.0",
204207
"prettier": "~3.3.3",
205208
"react-native-svg-transformer": "~1.5.1",
206209
"tailwindcss": "3.4.4",
@@ -225,7 +228,13 @@
225228
},
226229
"install": {
227230
"exclude": [
228-
"eslint-config-expo"
231+
"eslint-config-expo",
232+
"react-native@~0.76.6",
233+
"react-native-reanimated@~3.16.1",
234+
"react-native-gesture-handler@~2.20.0",
235+
"react-native-screens@~4.4.0",
236+
"react-native-safe-area-context@~4.12.0",
237+
"react-native-webview@~13.12.5"
229238
]
230239
}
231240
},

src/api/common/client.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ export const api = axiosInstance;
120120
// Helper function to create API endpoints
121121
export const createApiEndpoint = (endpoint: string) => {
122122
return {
123-
get: <T,>(params?: Record<string, unknown>) => api.get<T>(endpoint, { params }),
124-
post: <T,>(data: Record<string, unknown>) => api.post<T>(endpoint, data),
125-
put: <T,>(data: Record<string, unknown>) => api.put<T>(endpoint, data),
126-
delete: <T,>(params?: Record<string, unknown>) => api.delete<T>(endpoint, { params }),
123+
get: <T,>(params?: Record<string, unknown>, signal?: AbortSignal) => api.get<T>(endpoint, { params, signal }),
124+
post: <T,>(data: Record<string, unknown>, signal?: AbortSignal) => api.post<T>(endpoint, data, { signal }),
125+
put: <T,>(data: Record<string, unknown>, signal?: AbortSignal) => api.put<T>(endpoint, data, { signal }),
126+
delete: <T,>(params?: Record<string, unknown>, signal?: AbortSignal) => api.delete<T>(endpoint, { params, signal }),
127127
};
128128
};

src/api/mapping/mapping.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ const getMayLayersApi = createApiEndpoint('/Mapping/GetMayLayers');
77

88
const getMapDataAndMarkersApi = createApiEndpoint('/Mapping/GetMapDataAndMarkers');
99

10-
export const getMapDataAndMarkers = async () => {
11-
const response = await getMapDataAndMarkersApi.get<GetMapDataAndMarkersResult>();
10+
export const getMapDataAndMarkers = async (signal?: AbortSignal) => {
11+
const response = await getMapDataAndMarkersApi.get<GetMapDataAndMarkersResult>(undefined, signal);
1212
return response.data;
1313
};
1414

15-
export const getMayLayers = async (type: number) => {
16-
const response = await getMayLayersApi.get<GetMapLayersResult>({
17-
type: encodeURIComponent(type),
18-
});
15+
export const getMayLayers = async (type: number, signal?: AbortSignal) => {
16+
const response = await getMayLayersApi.get<GetMapLayersResult>(
17+
{
18+
type: encodeURIComponent(type),
19+
},
20+
signal
21+
);
1922
return response.data;
2023
};

0 commit comments

Comments
 (0)