tangled
alpha
login
or
join now
byarielm.fyi
/
atlast
16
fork
atom
ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
16
fork
atom
overview
issues
1
pulls
pipelines
removal artifical delays, replace w exponential backoff
byarielm.fyi
4 months ago
195d3540
5388e077
+38
-17
4 changed files
expand all
collapse all
unified
split
netlify
functions
batch-follow-users.ts
src
constants
platforms.ts
hooks
useFollows.ts
useSearch.ts
+20
-4
netlify/functions/batch-follow-users.ts
···
98
98
// Create agent from OAuth session
99
99
const agent = new Agent(oauthSession);
100
100
101
101
-
// Follow all users - process with small delays to respect rate limits
101
101
+
// Follow all users
102
102
const results = [];
103
103
+
let consecutiveErrors = 0;
104
104
+
const MAX_CONSECUTIVE_ERRORS = 3;
105
105
+
103
106
for (const did of dids) {
104
107
try {
105
108
await agent.api.com.atproto.repo.createRecord({
···
117
120
success: true,
118
121
error: null
119
122
});
123
123
+
124
124
+
// Reset error counter on success
125
125
+
consecutiveErrors = 0;
120
126
} catch (error) {
127
127
+
consecutiveErrors++;
128
128
+
121
129
results.push({
122
130
did,
123
131
success: false,
124
132
error: error instanceof Error ? error.message : 'Follow failed'
125
133
});
134
134
+
135
135
+
// If we hit rate limits, implement exponential backoff
136
136
+
if (error instanceof Error &&
137
137
+
(error.message.includes('rate limit') || error.message.includes('429'))) {
138
138
+
const backoffDelay = Math.min(200 * Math.pow(2, consecutiveErrors), 2000);
139
139
+
console.log(`Rate limit hit. Backing off for ${backoffDelay}ms...`);
140
140
+
await new Promise(resolve => setTimeout(resolve, backoffDelay));
141
141
+
} else if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
142
142
+
// For other repeated errors, small backoff
143
143
+
await new Promise(resolve => setTimeout(resolve, 500));
144
144
+
}
126
145
}
127
127
-
128
128
-
// Small delay between follows to be respectful of rate limits
129
129
-
await new Promise(resolve => setTimeout(resolve, 100));
130
146
}
131
147
132
148
const successCount = results.filter(r => r.success).length;
-2
src/constants/platforms.ts
···
63
63
export const SEARCH_CONFIG = {
64
64
BATCH_SIZE: 25,
65
65
MAX_MATCHES: 1000,
66
66
-
BATCH_DELAY_MS: 500,
67
66
};
68
67
69
68
export const FOLLOW_CONFIG = {
70
69
BATCH_SIZE: 50,
71
71
-
BATCH_DELAY_MS: 1000,
72
70
};
+2
-5
src/hooks/useFollows.ts
···
34
34
let totalFailed = 0;
35
35
36
36
try {
37
37
-
const { BATCH_SIZE, BATCH_DELAY_MS } = FOLLOW_CONFIG;
37
37
+
const { BATCH_SIZE } = FOLLOW_CONFIG;
38
38
39
39
for (let i = 0; i < selectedUsers.length; i += BATCH_SIZE) {
40
40
const batch = selectedUsers.slice(i, i + BATCH_SIZE);
···
70
70
console.error('Batch follow error:', error);
71
71
}
72
72
73
73
-
// Small delay between batches
74
74
-
if (i + BATCH_SIZE < selectedUsers.length) {
75
75
-
await new Promise(resolve => setTimeout(resolve, BATCH_DELAY_MS));
76
76
-
}
73
73
+
// Rate limit handling is in the backend
77
74
}
78
75
79
76
const finalMsg = `Successfully followed ${totalFollowed} users${totalFailed > 0 ? `. ${totalFailed} failed.` : ''}`;
+16
-6
src/hooks/useSearch.ts
···
24
24
setSearchProgress({ searched: 0, found: 0, total: resultsToSearch.length });
25
25
onProgressUpdate(`Starting search for ${resultsToSearch.length} users...`);
26
26
27
27
-
const { BATCH_SIZE, MAX_MATCHES, BATCH_DELAY_MS } = SEARCH_CONFIG;
27
27
+
const { BATCH_SIZE, MAX_MATCHES } = SEARCH_CONFIG;
28
28
let totalSearched = 0;
29
29
let totalFound = 0;
30
30
+
let consecutiveErrors = 0;
31
31
+
const MAX_CONSECUTIVE_ERRORS = 3;
30
32
31
33
for (let i = 0; i < resultsToSearch.length; i += BATCH_SIZE) {
32
34
if (totalFound >= MAX_MATCHES) {
···
47
49
48
50
try {
49
51
const data = await apiClient.batchSearchActors(usernames);
52
52
+
53
53
+
// Reset error counter on success
54
54
+
consecutiveErrors = 0;
50
55
51
56
// Process batch results
52
57
data.results.forEach((result) => {
···
88
93
89
94
} catch (error) {
90
95
console.error('Batch search error:', error);
96
96
+
consecutiveErrors++;
97
97
+
91
98
// Mark batch as failed
92
99
setSearchResults(prev => prev.map((result, index) =>
93
100
i <= index && index < i + BATCH_SIZE
94
101
? { ...result, isSearching: false, error: 'Search failed' }
95
102
: result
96
103
));
97
97
-
}
98
98
-
99
99
-
// Small delay between batches
100
100
-
if (i + BATCH_SIZE < resultsToSearch.length && totalFound < MAX_MATCHES) {
101
101
-
await new Promise(resolve => setTimeout(resolve, BATCH_DELAY_MS));
104
104
+
105
105
+
// If we hit rate limits or repeated errors, add exponential backoff
106
106
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
107
107
+
const backoffDelay = Math.min(1000 * Math.pow(2, consecutiveErrors - MAX_CONSECUTIVE_ERRORS), 5000);
108
108
+
console.log(`Rate limit detected. Backing off for ${backoffDelay}ms...`);
109
109
+
onProgressUpdate(`Rate limit detected. Pausing briefly...`);
110
110
+
await new Promise(resolve => setTimeout(resolve, backoffDelay));
111
111
+
}
102
112
}
103
113
}
104
114