Skip to content

Commit 47ba3bc

Browse files
authored
fix(kick): check token before refreshing (#395)
Sometimes the refreshing fails, so do a sanity check if we can connect at all. It doesn't prevent all errors, but at least some.
1 parent d74cd2d commit 47ba3bc

File tree

3 files changed

+121
-43
lines changed

3 files changed

+121
-43
lines changed

CHANGELOG.c7.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Unversioned
44

5-
- Major: Added experimental Kick support (#351, #353, #354, #355, #356, #357, #360, #362, #367, #368, #369, #371, #373, #378, #379, #380, #381, #382, #383, #386, #387, #388, #390, #391)
5+
- Major: Added experimental Kick support (#351, #353, #354, #355, #356, #357, #360, #362, #367, #368, #369, #371, #373, #378, #379, #380, #381, #382, #383, #386, #387, #388, #390, #391, #395)
66
- Minor: Increased radius of drop-shadows in paints to match the browser extension (#339, a4a86c9ca6e1bccf9253dce75bdfe838c15e71fd)
77
- Bugfix: Fixed 7TV users with multiple connections to the same platform not having cosmetics applied on all connected accounts (#389)
88
- Bugfix: Fixed URL paints on mentions not being scaled correctly in high-dpi settings (57627edf4af49111bf5e0c1bc942ad4db3b85d96)

src/providers/kick/KickAccount.cpp

Lines changed: 102 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -156,52 +156,26 @@ void KickAccount::refreshIfNeeded()
156156
}
157157

158158
auto now = QDateTime::currentDateTimeUtc() + CHECK_REFRESH_INTERVAL +
159-
std::chrono::seconds{30};
159+
std::chrono::seconds{60};
160160
if (now < this->expiresAt_)
161161
{
162162
return;
163163
}
164+
qCDebug(chatterinoKick) << "Attempting to refresh" << this->username()
165+
<< "expires:" << this->expiresAt_;
164166

165-
QUrlQuery payload{
166-
{"refresh_token"_L1, this->refreshToken_},
167-
{"client_id"_L1, this->clientID_},
168-
{"client_secret"_L1, this->clientSecret_},
169-
{"grant_type"_L1, "refresh_token"_L1},
170-
};
171-
172-
auto weak = this->weak_from_this();
173-
NetworkRequest(u"https://id.kick.com/oauth/token"_s,
174-
NetworkRequestType::Post)
175-
.header("Content-Type", "application/x-www-form-urlencoded")
176-
.payload(payload.toString(QUrl::FullyEncoded).toUtf8())
177-
.onSuccess([weak](const NetworkResult &res) {
178-
auto self = weak.lock();
179-
if (!self)
180-
{
181-
return;
182-
}
183-
184-
const auto json = res.parseJson();
185-
self->authToken_ = json["access_token"_L1].toString();
186-
self->refreshToken_ = json["refresh_token"_L1].toString();
187-
auto expiresInSec =
188-
std::clamp<qint64>(json["expires_in"_L1].toInteger(), 0,
189-
std::numeric_limits<qint32>::max());
190-
self->expiresAt_ =
191-
QDateTime::currentDateTimeUtc().addSecs(expiresInSec);
192-
self->save();
193-
self->authUpdated.invoke();
194-
})
195-
.onError([weak](const NetworkResult &res) {
196-
auto self = weak.lock();
197-
if (!self)
198-
{
199-
return;
200-
}
201-
qCWarning(chatterinoKick) << "Failed to refresh" << self->username()
202-
<< "error:" << res.formatError();
203-
})
204-
.execute();
167+
// Hack: check the current token first to ensure we're connected. Otherwise,
168+
// we might miss the response.
169+
// FIXME: queue re-check earlier
170+
this->check([this](CheckResult res) {
171+
if (res == CheckResult::NetworkError)
172+
{
173+
qCWarning(chatterinoKick)
174+
<< "Skipping refresh, because of network error";
175+
return;
176+
}
177+
this->doRefresh();
178+
});
205179
}
206180

207181
void KickAccount::loadSeventvUser()
@@ -271,4 +245,91 @@ void KickAccount::loadSeventvUser()
271245
});
272246
}
273247

248+
void KickAccount::check(const std::function<void(CheckResult)> &cb)
249+
{
250+
auto weak = this->weak_from_this();
251+
NetworkRequest(u"https://id.kick.com/oauth/token/introspect"_s,
252+
NetworkRequestType::Post)
253+
.timeout(10'000)
254+
.onSuccess([cb, weak](const NetworkResult & /*res*/) {
255+
auto self = weak.lock();
256+
if (!self)
257+
{
258+
return;
259+
}
260+
qCDebug(chatterinoKick)
261+
<< "[Refresh]: Successfully checked previous token";
262+
cb(CheckResult::Valid);
263+
})
264+
.onError([weak, cb](const NetworkResult &res) {
265+
auto self = weak.lock();
266+
if (!self)
267+
{
268+
return;
269+
}
270+
qCDebug(chatterinoKick)
271+
<< "[Refresh; Error] Network response:" << res.formatError();
272+
auto status = res.status();
273+
if (!status || *status < 100)
274+
{
275+
cb(CheckResult::NetworkError);
276+
return;
277+
}
278+
if (*status == 401)
279+
{
280+
cb(CheckResult::Expired);
281+
return;
282+
}
283+
cb(CheckResult::OtherHttp);
284+
})
285+
.execute();
286+
}
287+
288+
void KickAccount::doRefresh()
289+
{
290+
QUrlQuery payload{
291+
{"refresh_token"_L1, this->refreshToken_},
292+
{"client_id"_L1, this->clientID_},
293+
{"client_secret"_L1, this->clientSecret_},
294+
{"grant_type"_L1, "refresh_token"_L1},
295+
};
296+
297+
auto weak = this->weak_from_this();
298+
NetworkRequest(u"https://id.kick.com/oauth/token"_s,
299+
NetworkRequestType::Post)
300+
.header("Content-Type", "application/x-www-form-urlencoded")
301+
.payload(payload.toString(QUrl::FullyEncoded).toUtf8())
302+
.timeout(20'000)
303+
.onSuccess([weak](const NetworkResult &res) {
304+
auto self = weak.lock();
305+
if (!self)
306+
{
307+
return;
308+
}
309+
310+
const auto json = res.parseJson();
311+
self->authToken_ = json["access_token"_L1].toString();
312+
self->refreshToken_ = json["refresh_token"_L1].toString();
313+
auto expiresInSec =
314+
std::clamp<qint64>(json["expires_in"_L1].toInteger(), 0,
315+
std::numeric_limits<qint32>::max());
316+
self->expiresAt_ =
317+
QDateTime::currentDateTimeUtc().addSecs(expiresInSec);
318+
self->save();
319+
self->authUpdated.invoke();
320+
qCDebug(chatterinoKick)
321+
<< "[Refresh] Successful, next expiry:" << self->expiresAt_;
322+
})
323+
.onError([weak](const NetworkResult &res) {
324+
auto self = weak.lock();
325+
if (!self)
326+
{
327+
return;
328+
}
329+
qCWarning(chatterinoKick) << "Failed to refresh" << self->username()
330+
<< "error:" << res.formatError();
331+
})
332+
.execute();
333+
}
334+
274335
} // namespace chatterino

src/providers/kick/KickAccount.hpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class KickAccount : public Account,
3232
KickAccount(const KickAccountData &args);
3333
~KickAccount() override;
3434

35-
constexpr static std::chrono::minutes CHECK_REFRESH_INTERVAL{5};
35+
constexpr static std::chrono::minutes CHECK_REFRESH_INTERVAL{2};
3636

3737
Q_DISABLE_COPY_MOVE(KickAccount);
3838

@@ -83,6 +83,23 @@ class KickAccount : public Account,
8383
pajlada::Signals::NoArgSignal authUpdated;
8484

8585
private:
86+
enum class CheckResult : uint8_t {
87+
/// Returned 2xx
88+
Valid,
89+
/// 401 Unauthorized
90+
///
91+
/// Expected if Chatterino just started. The tokens have an expiry of
92+
/// 2h.
93+
Expired,
94+
/// Error code >=100, but not 401
95+
OtherHttp,
96+
/// A Qt error code <100
97+
NetworkError,
98+
};
99+
100+
void check(const std::function<void(CheckResult)> &cb);
101+
void doRefresh();
102+
86103
QString username_;
87104
uint64_t userID_ = 0;
88105
QString clientID_;

0 commit comments

Comments
 (0)