+1
-1
.gitignore
+1
-1
.gitignore
+61
internal/bluesky/client.go
+61
internal/bluesky/client.go
···
4
4
"context"
5
5
"fmt"
6
6
"strings"
7
+
"sync"
7
8
"time"
8
9
9
10
"github.com/bluesky-social/indigo/api/atproto"
···
17
18
xrpcClient *xrpc.Client
18
19
handle string
19
20
did string
21
+
config Config
22
+
mu sync.Mutex // protects token refresh
20
23
}
21
24
22
25
// Config holds configuration for Bluesky client
···
56
59
xrpcClient: xrpcClient,
57
60
handle: auth.Handle,
58
61
did: auth.Did,
62
+
config: cfg,
59
63
}, nil
60
64
}
61
65
···
85
89
86
90
_, err := atproto.RepoCreateRecord(ctx, c.xrpcClient, input)
87
91
if err != nil {
92
+
// Check if token expired and retry once after refresh
93
+
if c.isExpiredTokenError(err) {
94
+
if refreshErr := c.refreshSession(ctx); refreshErr != nil {
95
+
return fmt.Errorf("failed to create post: %w (refresh failed: %v)", err, refreshErr)
96
+
}
97
+
// Retry the post after refreshing
98
+
_, err = atproto.RepoCreateRecord(ctx, c.xrpcClient, input)
99
+
if err != nil {
100
+
return fmt.Errorf("failed to create post after refresh: %w", err)
101
+
}
102
+
return nil
103
+
}
88
104
return fmt.Errorf("failed to create post: %w", err)
105
+
}
106
+
107
+
return nil
108
+
}
109
+
110
+
// isExpiredTokenError checks if the error is due to an expired token
111
+
func (c *Client) isExpiredTokenError(err error) bool {
112
+
if err == nil {
113
+
return false
114
+
}
115
+
errStr := err.Error()
116
+
return strings.Contains(errStr, "ExpiredToken") || strings.Contains(errStr, "Token has expired")
117
+
}
118
+
119
+
// refreshSession refreshes the authentication session
120
+
func (c *Client) refreshSession(ctx context.Context) error {
121
+
c.mu.Lock()
122
+
defer c.mu.Unlock()
123
+
124
+
// Check if someone else already refreshed while we were waiting
125
+
if c.xrpcClient.Auth != nil && c.xrpcClient.Auth.RefreshJwt != "" {
126
+
// Try to use the refresh token
127
+
refresh, err := atproto.ServerRefreshSession(ctx, c.xrpcClient)
128
+
if err == nil {
129
+
c.xrpcClient.Auth.AccessJwt = refresh.AccessJwt
130
+
c.xrpcClient.Auth.RefreshJwt = refresh.RefreshJwt
131
+
return nil
132
+
}
133
+
// If refresh failed, fall through to re-authentication
134
+
}
135
+
136
+
// If refresh token doesn't work, re-authenticate with password
137
+
auth, err := atproto.ServerCreateSession(ctx, c.xrpcClient, &atproto.ServerCreateSession_Input{
138
+
Identifier: c.config.Handle,
139
+
Password: c.config.Password,
140
+
})
141
+
if err != nil {
142
+
return fmt.Errorf("failed to re-authenticate: %w", err)
143
+
}
144
+
145
+
c.xrpcClient.Auth = &xrpc.AuthInfo{
146
+
AccessJwt: auth.AccessJwt,
147
+
RefreshJwt: auth.RefreshJwt,
148
+
Handle: auth.Handle,
149
+
Did: auth.Did,
89
150
}
90
151
91
152
return nil