Skip to content

Commit c8b25cc

Browse files
committed
intrument token refresh code and create sample debugging app
1 parent 6fe5f17 commit c8b25cc

File tree

141 files changed

+7061
-30
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

141 files changed

+7061
-30
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.build/
9+
.buildlog/
10+
.history
11+
.svn/
12+
.swiftpm/
13+
migrate_working_dir/
14+
15+
# IntelliJ related
16+
*.iml
17+
*.ipr
18+
*.iws
19+
.idea/
20+
21+
# The .vscode folder contains launch configuration and tasks you configure in
22+
# VS Code which you may wish to be included in version control, so this line
23+
# is commented out by default.
24+
#.vscode/
25+
26+
# Flutter/Dart/Pub related
27+
**/doc/api/
28+
**/ios/Flutter/.last_build_id
29+
.dart_tool/
30+
.flutter-plugins-dependencies
31+
.pub-cache/
32+
.pub/
33+
/build/
34+
/coverage/
35+
36+
# Symbolication related
37+
app.*.symbols
38+
39+
# Obfuscation related
40+
app.*.map.json
41+
42+
# Android Studio will place build artifacts here
43+
/android/app/debug
44+
/android/app/profile
45+
/android/app/release
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: "20f82749394e68bcfbbeee96bad384abaae09c13"
8+
channel: "stable"
9+
10+
project_type: app
11+
12+
# Tracks metadata for the flutter migrate command
13+
migration:
14+
platforms:
15+
- platform: root
16+
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
17+
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
18+
- platform: android
19+
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
20+
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
21+
- platform: ios
22+
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
23+
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
24+
- platform: linux
25+
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
26+
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
27+
- platform: macos
28+
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
29+
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
30+
- platform: web
31+
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
32+
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
33+
- platform: windows
34+
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
35+
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
36+
37+
# User provided section
38+
39+
# List of Local paths (relative to this file) that should be
40+
# ignored by the migrate tool.
41+
#
42+
# Files that are not part of the templates will be ignored by default.
43+
unmanaged_files:
44+
- 'lib/main.dart'
45+
- 'ios/Runner.xcodeproj/project.pbxproj'
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# Token Refresh Debug App
2+
3+
This is a diagnostic application designed to help reproduce and debug the intermittent token refresh issue reported in [Issue #1158](https://github.com/supabase/supabase-flutter/issues/1158).
4+
5+
## Problem Description
6+
7+
Users are experiencing inconsistent token refresh behavior where:
8+
- Sometimes auto-refresh works correctly
9+
- Sometimes the SDK emits a `signedOut` event instead of refreshing expired tokens
10+
- This results in 403 errors and unexpected user logouts
11+
- The issue is intermittent and difficult to reproduce
12+
13+
## Purpose
14+
15+
This app provides:
16+
1. **Real-time session monitoring** - View token expiry status, time remaining, and session details
17+
2. **Comprehensive logging** - All instrumentation logs from the SDK are displayed in the console
18+
3. **App lifecycle tracking** - Monitor app state changes (paused/resumed)
19+
4. **Manual testing tools** - Trigger API calls and manual token refreshes
20+
5. **Reproduction environment** - Controlled conditions to reproduce the issue
21+
22+
## Setup
23+
24+
### Prerequisites
25+
26+
1. A Supabase project with authentication enabled
27+
2. Configure your project with a short token expiry for easier testing:
28+
- Go to your Supabase Dashboard
29+
- Navigate to Authentication > Settings
30+
- Set "JWT expiry limit" to a short duration (e.g., 300 seconds = 5 minutes)
31+
- This allows you to reproduce the issue faster without waiting hours
32+
33+
3. Create a test user in your project
34+
4. Optional: Create a `test_table` for API testing (not required)
35+
36+
### Installation
37+
38+
```bash
39+
cd examples/token_refresh_debug_app
40+
flutter pub get
41+
flutter run
42+
```
43+
44+
## Usage
45+
46+
### Step 1: Configure Supabase
47+
48+
1. Launch the app
49+
2. Enter your Supabase URL (e.g., `https://your-project.supabase.co`)
50+
3. Enter your Supabase Anon Key
51+
4. Click "Initialize"
52+
53+
### Step 2: Sign In
54+
55+
1. Enter your test user email
56+
2. Enter password
57+
3. Click "Sign In"
58+
59+
### Step 3: Monitor Session
60+
61+
Once signed in, you'll see the Debug Dashboard with:
62+
63+
- **Session Status Card** (Green/Red)
64+
- Current session state (Active or EXPIRED)
65+
- User ID and email
66+
- Token expiry time
67+
- Time remaining until expiry
68+
- Access token preview
69+
- Refresh token availability
70+
71+
- **Controls**
72+
- Test API Call - Makes a query to test if token is valid
73+
- Manual Token Refresh - Manually triggers a refresh
74+
- Sign Out - Logs out the user
75+
- App State indicator
76+
77+
- **Event Log**
78+
- Shows auth state changes (signedIn, tokenRefreshed, signedOut)
79+
- Shows app lifecycle changes (resumed, paused, inactive)
80+
- Timestamped for correlation with console logs
81+
82+
### Step 4: Reproduce the Issue
83+
84+
#### Method 1: App Pause/Resume with Expired Token
85+
86+
1. **Note the expiry time** - Check "Time Until Expiry"
87+
2. **Minimize the app** - Use your device/simulator to background the app
88+
3. **Wait for token to expire** - Wait longer than the expiry time
89+
4. **Resume the app** - Bring the app back to foreground
90+
5. **Observe behavior**:
91+
-**Expected**: Session status stays green, "Time Until Expiry" resets (token was refreshed)
92+
-**Bug**: Session disappears or shows "No active session" (signedOut event was emitted)
93+
94+
#### Method 2: Network Interruption
95+
96+
1. **Enable airplane mode** while token is about to expire
97+
2. **Wait for auto-refresh to trigger**
98+
3. **Re-enable network**
99+
4. **Observe** if session is preserved or user is signed out
100+
101+
#### Method 3: Rapid Lifecycle Changes
102+
103+
1. **Rapidly pause and resume** the app multiple times
104+
2. **Check** if session remains stable
105+
3. **Look for** race conditions in the logs
106+
107+
### Step 5: Analyze Logs
108+
109+
The app outputs comprehensive logs to the console showing:
110+
111+
```
112+
INFO: 14:23:45.123: supabase.supabase_flutter: App lifecycle state changed to: resumed
113+
FINE: 14:23:45.124: supabase.auth: Starting auto refresh with session state: expiresAt=2024-01-20T14:28:45.000Z, isExpired=false, hasRefreshToken=true
114+
FINE: 14:23:45.125: supabase.auth: Auto-refresh tick: expires in 58 ticks (583s), threshold=3
115+
INFO: 14:23:45.126: supabase.supabase_flutter: Starting session recovery from local storage
116+
```
117+
118+
Key things to look for:
119+
- **Session recovery timing** - Does it complete before auto-refresh starts?
120+
- **Auto-refresh tick calculations** - Are expiry times calculated correctly?
121+
- **Error messages** - What type of errors occur during refresh?
122+
- **SignedOut events** - When do they occur and why?
123+
- **App lifecycle timing** - Do rapid state changes cause issues?
124+
125+
## Expected Log Flow (Successful Refresh)
126+
127+
```
128+
1. App resumed
129+
2. Auto-refresh timer starts
130+
3. Session recovery starts
131+
4. Session recovery completes
132+
5. Auto-refresh tick checks expiry
133+
6. Token refresh triggered (when threshold reached)
134+
7. Token refresh successful
135+
8. tokenRefreshed event emitted
136+
9. Session persisted to storage
137+
```
138+
139+
## Problematic Log Flow (Issue Reproduces)
140+
141+
```
142+
1. App resumed
143+
2. Auto-refresh timer starts
144+
3. Auto-refresh tick runs immediately
145+
4. Session recovery still in progress (race condition)
146+
5. Token refresh fails (various reasons)
147+
6. signedOut event emitted
148+
7. Session cleared
149+
8. User unexpectedly logged out
150+
```
151+
152+
## Key Scenarios to Test
153+
154+
### Scenario 1: Clean Resume After Expiry
155+
- Start: Valid session
156+
- Action: Pause app for >expiry duration
157+
- Resume: Should auto-refresh
158+
- Check: Session stays active
159+
160+
### Scenario 2: Network Error During Refresh
161+
- Start: Token about to expire
162+
- Action: Enable airplane mode
163+
- Wait: Trigger auto-refresh attempt
164+
- Resume: Re-enable network
165+
- Check: Session preserved, retries refresh
166+
167+
### Scenario 3: Concurrent Refresh Attempts
168+
- Start: Token about to expire
169+
- Action: Rapidly open/close app
170+
- Check: Only one refresh call made
171+
- Check: No race conditions
172+
173+
### Scenario 4: Custom Storage Implementation
174+
- Configure: Use FlutterSecureStorage instead of SharedPreferences
175+
- Run: All above scenarios
176+
- Check: Same behavior as default storage
177+
178+
## Troubleshooting
179+
180+
### Issue: "No active session" shows immediately after resume
181+
- This indicates the session was not properly restored from storage
182+
- Check logs for storage read errors
183+
- Verify permissions for SharedPreferences/FlutterSecureStorage
184+
185+
### Issue: Token refresh fails with 401
186+
- Check if refresh token is still valid
187+
- Verify Supabase project settings allow token refresh
188+
- Check if user was deleted/disabled
189+
190+
### Issue: Token refresh fails with network error
191+
- Verify internet connectivity
192+
- Check Supabase project status
193+
- Look for retryable vs non-retryable errors in logs
194+
195+
### Issue: App doesn't respond to lifecycle changes
196+
- Verify WidgetsBindingObserver is properly registered
197+
- Check if auto-refresh is enabled in configuration
198+
- Look for timer start/stop logs
199+
200+
## Configuration Options
201+
202+
You can modify the app to test different scenarios:
203+
204+
### Change Log Level
205+
In `main.dart`, adjust logging level:
206+
```dart
207+
Logger.root.level = Level.ALL; // Most verbose
208+
Logger.root.level = Level.FINE; // Debug info
209+
Logger.root.level = Level.INFO; // Important events only
210+
```
211+
212+
### Test with Different Storage
213+
Swap SharedPreferences for FlutterSecureStorage:
214+
```dart
215+
await Supabase.initialize(
216+
url: url,
217+
anonKey: anonKey,
218+
authOptions: FlutterAuthClientOptions(
219+
localStorage: MyCustomSecureStorage(), // Your implementation
220+
),
221+
);
222+
```
223+
224+
### Adjust Auto-Refresh Timing
225+
The timing constants are in `packages/gotrue/lib/src/constants.dart`:
226+
- `autoRefreshTickDuration` - How often to check (default: 10 seconds)
227+
- `autoRefreshTickThreshold` - When to refresh (default: 3 ticks before expiry)
228+
- `expiryMargin` - Safety buffer (default: 30 seconds)
229+
230+
## Contributing Findings
231+
232+
When reporting results:
233+
234+
1. **Include console logs** - Copy relevant log sections
235+
2. **Describe the scenario** - Which test case you ran
236+
3. **Note timing** - How long after pause did you resume?
237+
4. **Environment details** - iOS/Android version, Flutter version
238+
5. **Storage type** - Default or custom implementation
239+
6. **Consistency** - How often does it reproduce (1/10, 5/10, always)?
240+
241+
## Technical Details
242+
243+
### Instrumentation Added
244+
245+
This app uses the instrumentation added to the SDK:
246+
- **gotrue_client.dart** - Token refresh lifecycle
247+
- **supabase_auth.dart** - App lifecycle and recovery
248+
- **session.dart** - Expiry calculations
249+
- **local_storage.dart** - Storage operations
250+
- **supabase.dart** - Initialization flow
251+
252+
### Dependencies
253+
254+
- `supabase_flutter` - Local path to repository version with instrumentation
255+
- `logging` - For structured log output
256+
- `intl` - For timestamp formatting
257+
258+
## Next Steps
259+
260+
After reproducing the issue with this app:
261+
262+
1. Share logs with the Supabase team
263+
2. Help identify the specific timing conditions that trigger the bug
264+
3. Test proposed fixes
265+
4. Verify the fix resolves the issue in your production app
266+
267+
## Related Issues
268+
269+
- [Issue #1158](https://github.com/supabase/supabase-flutter/issues/1158) - Original bug report
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This file configures the analyzer, which statically analyzes Dart code to
2+
# check for errors, warnings, and lints.
3+
#
4+
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5+
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6+
# invoked from the command line by running `flutter analyze`.
7+
8+
# The following line activates a set of recommended lints for Flutter apps,
9+
# packages, and plugins designed to encourage good coding practices.
10+
include: package:flutter_lints/flutter.yaml
11+
12+
linter:
13+
# The lint rules applied to this project can be customized in the
14+
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
15+
# included above or to enable additional rules. A list of all available lints
16+
# and their documentation is published at https://dart.dev/lints.
17+
#
18+
# Instead of disabling a lint rule for the entire project in the
19+
# section below, it can also be suppressed for a single line of code
20+
# or a specific dart file by using the `// ignore: name_of_lint` and
21+
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
22+
# producing the lint.
23+
rules:
24+
# avoid_print: false # Uncomment to disable the `avoid_print` rule
25+
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
26+
27+
# Additional information about this file can be found at
28+
# https://dart.dev/guides/language/analysis-options

0 commit comments

Comments
 (0)