|
1 | 1 | // ==UserScript== |
2 | 2 | // @name Igloo Site Sync |
3 | 3 | // @namespace local.igloo.site.sync |
4 | | -// @version 8.0.34 |
| 4 | +// @version 8.0.35 |
5 | 5 | // @author screwys |
6 | 6 | // @description Follow X, TikTok, Instagram, and YouTube channels in Igloo; includes the full X media workflow. |
7 | 7 | // @homepageURL https://github.com/screwys/Igloo |
|
36 | 36 |
|
37 | 37 | (function () { |
38 | 38 | "use strict"; |
39 | | - const SCRIPT_VERSION = "8.0.34"; |
| 39 | + const SCRIPT_VERSION = "8.0.35"; |
40 | 40 |
|
41 | 41 | const SETTINGS = { |
42 | 42 | apiBase: "xsync_api_base", |
|
476 | 476 | return true; |
477 | 477 | } |
478 | 478 |
|
| 479 | + function responseErrorCode(resp) { |
| 480 | + return String(resp?.json?.error_code || ""); |
| 481 | + } |
| 482 | + |
| 483 | + function responseErrorDetail(resp) { |
| 484 | + return ( |
| 485 | + resp?.json?.error_message || |
| 486 | + resp?.json?.error_code || |
| 487 | + resp?.error || |
| 488 | + resp?.status || |
| 489 | + "error" |
| 490 | + ); |
| 491 | + } |
| 492 | + |
| 493 | + function isHTMLAuthRedirect(resp) { |
| 494 | + return ( |
| 495 | + resp?.status === 303 || |
| 496 | + (resp?.ok && resp?.json === null && resp?.text?.includes("<!DOCTYPE")) |
| 497 | + ); |
| 498 | + } |
| 499 | + |
| 500 | + function shouldRefreshAuthResponse(resp) { |
| 501 | + if (!resp) return false; |
| 502 | + if (isHTMLAuthRedirect(resp)) return true; |
| 503 | + if (resp.status !== 401) return false; |
| 504 | + const code = responseErrorCode(resp); |
| 505 | + if (code === "access_token_expired" || code === "legacy_token_invalid") { |
| 506 | + return true; |
| 507 | + } |
| 508 | + return !code && !!getToken(); |
| 509 | + } |
| 510 | + |
| 511 | + function authFailureNotice(resp, refreshResp) { |
| 512 | + const code = responseErrorCode(refreshResp) || responseErrorCode(resp); |
| 513 | + if (code === "refresh_token_expired" || code === "access_token_expired") { |
| 514 | + return "Session expired; use Tampermonkey menu \u2192 Log in to server"; |
| 515 | + } |
| 516 | + if (code === "refresh_token_replayed" || code === "session_revoked") { |
| 517 | + return "Session revoked; use Tampermonkey menu \u2192 Log in to server"; |
| 518 | + } |
| 519 | + if ( |
| 520 | + code === "legacy_token_invalid" || |
| 521 | + code === "refresh_token_invalid" || |
| 522 | + code === "access_token_invalid" |
| 523 | + ) { |
| 524 | + return "Saved login was rejected; use Tampermonkey menu \u2192 Log in to server"; |
| 525 | + } |
| 526 | + if (code === "unauthenticated") { |
| 527 | + return "Not logged in; use Tampermonkey menu \u2192 Log in to server"; |
| 528 | + } |
| 529 | + return `Authentication failed (${responseErrorDetail(refreshResp || resp)}); use Tampermonkey menu \u2192 Log in to server`; |
| 530 | + } |
| 531 | + |
479 | 532 | function forgetLegacyDashboardPassword() { |
480 | 533 | try { |
481 | 534 | if (typeof GM_deleteValue === "function") { |
|
982 | 1035 |
|
983 | 1036 | async function _refreshToken() { |
984 | 1037 | const refresh = getRefresh(); |
985 | | - if (!refresh) return false; |
| 1038 | + const access = getToken(); |
| 1039 | + if (!refresh) return { ok: false, response: null }; |
986 | 1040 | const r = await _rawApiRequest( |
987 | 1041 | "POST", |
988 | 1042 | "/api/auth/refresh", |
|
991 | 1045 | ); |
992 | 1046 | if (r.ok && storeAuthTokens(r.json)) { |
993 | 1047 | console.log("[XSync] token rotated"); |
994 | | - return true; |
| 1048 | + return { ok: true, response: r }; |
| 1049 | + } |
| 1050 | + if (getRefresh() !== refresh && getToken() !== access) { |
| 1051 | + console.log("[XSync] refresh superseded by newer login"); |
| 1052 | + return { ok: true, response: r, superseded: true }; |
995 | 1053 | } |
996 | 1054 | if (r.status === 401) { |
997 | | - GM_setValue(SETTINGS.authRefresh, ""); |
998 | | - GM_setValue(SETTINGS.authToken, ""); |
| 1055 | + if (getRefresh() === refresh) GM_setValue(SETTINGS.authRefresh, ""); |
| 1056 | + if (getToken() === access) GM_setValue(SETTINGS.authToken, ""); |
999 | 1057 | } |
1000 | | - return false; |
| 1058 | + return { ok: false, response: r }; |
1001 | 1059 | } |
1002 | 1060 |
|
1003 | 1061 | async function apiRequest(method, path, body, withAuth = true) { |
1004 | 1062 | const resp = await _rawApiRequest(method, path, body, withAuth); |
1005 | | - // Detect expired token: 303 redirect to /login, or 401 |
1006 | | - if ( |
1007 | | - withAuth && |
1008 | | - (resp.status === 303 || |
1009 | | - resp.status === 401 || |
1010 | | - (resp.ok && resp.json === null && resp.text.includes("<!DOCTYPE"))) |
1011 | | - ) { |
| 1063 | + if (withAuth && shouldRefreshAuthResponse(resp)) { |
1012 | 1064 | if (!_refreshingToken) { |
1013 | | - _refreshingToken = _refreshToken().then((ok) => { |
| 1065 | + _refreshingToken = _refreshToken().then((result) => { |
1014 | 1066 | _refreshingToken = null; |
1015 | | - return ok; |
| 1067 | + return result; |
1016 | 1068 | }); |
1017 | 1069 | } |
1018 | | - const refreshed = await _refreshingToken; |
1019 | | - if (refreshed) return _rawApiRequest(method, path, body, withAuth); |
1020 | | - notify("Token expired — use Tampermonkey menu → Log in to server"); |
| 1070 | + const refreshResult = await _refreshingToken; |
| 1071 | + if (refreshResult.ok) return _rawApiRequest(method, path, body, withAuth); |
| 1072 | + notify(authFailureNotice(resp, refreshResult.response)); |
| 1073 | + } else if (withAuth && resp.status === 401) { |
| 1074 | + notify(authFailureNotice(resp, null)); |
1021 | 1075 | } |
1022 | 1076 | return resp; |
1023 | 1077 | } |
|
0 commit comments