Compare commits

..
Author SHA1 Message Date
John O'Keefe a8df8dcce7 chore: ignore Task cache directory
Task (used by wails3 via Taskfile.yml) stores incremental-build fingerprints under .task/checksum/. Purely local cache — hashes of sources/deps, machine-generated, recreated on next wails3 dev/build. Keep it out of git like node_modules/frontend/dist/build/bin (already ignored). No files were tracked; this just stops it showing as untracked on branches that have run wails3.
2026-09-13 18:01:01 -04:00
John O'Keefe 2bf8d3887e fix(ci): track frontend lockfile for reproducible installs
Release / release (push) Successful in 2m42s
The blanket package-lock.json gitignore rule meant the frontend lockfile was never committed, so every release installed whatever the registry handed it (silent transitive drift) and setup-node npm caching failed outright with unable to cache dependencies. Scope the ignore rule to the repo root (/package-lock.json; no root package.json exists) and commit the freshly updated frontend lockfile: all deps at max in-range versions (autoprefixer 10.5.6 -> 10.6.0), majors intentionally deferred to the v3 frontend refresh. Verified with a green vite build before committing.
2026-09-12 22:40:21 -04:00
John O'Keefe efe45f39e7 ci(release): cache npm, Go build cache, and Wails CLI
Release / release (push) Failing after 11s
Release runs were fully cold every time: ~13s npm install, ~39s full cgo/WebKit compile, and ~34s rebuilding the Wails CLI from source on each tag. Now that the runner cache backend is reachable again, persist all three across runs. Expected total drops from ~3.5 min toward ~2.5 min with apt as the remaining floor.

- Set up Node: cache npm keyed on frontend/package-lock.json, so the npm install inside wails build stops fetching cold
- New Cache Go build cache step: setup-go only persists modules (GOMODCACHE); this persists compiled packages (GOCACHE) with a go.sum-hashed key plus a version-prefix restore-key fallback, turning the WebKit compile incremental from the second run on
- Wails CLI: split install into cache (key wails-v2.15.0-Linux, bump with the version pin), conditional go install on miss, and an unconditional PATH step so cache hits still land on PATH

First run after this still compiles cold (it saves); the payoff shows from the second run on. Verify with the throwaway-tag loop and compare Build Linux binary times.
2026-09-12 22:26:12 -04:00
John O'Keefe 336a1f353f chore(release): bump version to 1.6.8
Release / release (push) Canceled after 20s
2026-09-12 21:27:12 -04:00
John O'Keefe a19080ef39 Revert "docs: add Wails v3 + Android migration plan"
The migration plan belongs to the wailsv3 branch, not main. It was merged forward into wailsv3 (now at origin/wailsv3) before this revert, so no content is lost. Main keeps only the v2 keyring error-handling fix.
2026-09-12 21:26:25 -04:00
John O'Keefe 2bddc6a224 docs: add Wails v3 + Android migration plan
Skimmable phased plan for getting AniTrack onto Android for personal
sideload use (no Play Store, no official F-Droid). Written for both the
maintainer and as working context for AI-assisted implementation.

Covers: desktop v3 parity as a mechanical step (main.go, runtime call
mapping, wailsjs import rewrite), per-file Android storage shims
(keyring on desktop vs EncryptedSharedPreferences via
Android.Secure*), the localhost :6734 to anitrack:// deep-link OAuth
redesign with per-provider dashboard changes, and the sideload run/
package commands plus Obtainium updates. Includes guardrails: small
diffs, per-service file ownership, build-tagged platform files only,
runnable check per phase, and open decisions (first provider, desktop
parallel builds, redirect URI registration).
2026-09-12 21:23:14 -04:00
John O'Keefe 3bf5d8290e fix(auth): handle keyring open/set errors per service
Stop ignoring keyring.Open failures in all three login files. A failed
Open previously left a nil ring behind the blank identifier, so the next
Get/Set panicked with no message. Each service now opens its ring in
init, logs a service-prefixed warning when storage is unavailable, and
guards every use with a Ready check that fails safe to logged-out.

- AniListUserFunctions.go: add aniRingReady/aniRingSet helpers, log
  Open and per-key Set failures, reject invalid ExpiresIn instead of
  silently storing 0, clear in-memory JWT on logout even when storage
  is missing
- MALUserFunctions.go: add malRingReady/malRingSet helpers covering
  login, OAuth callback, and token-refresh saves; same Open/Set/
  ExpiresIn/logout treatment
- SimklUserFunctions.go: add simklRingReady/simklRingSet helpers;
  same Open/Set/logout treatment

No wallet, key names, or login flow changed. Same ServiceName
AniTrack, same keys, same OAuth callback behavior.
2026-09-12 21:22:58 -04:00
John O'Keefe a5da544dd7 chore(release): bump version to 1.6.7
Release / release (push) Successful in 11m52s
2026-09-11 16:22:45 -04:00
7 changed files with 2919 additions and 91 deletions
+30 -1
View File
@@ -80,10 +80,27 @@ jobs:
go-version: '1.25' go-version: '1.25'
cache-dependency-path: go.sum cache-dependency-path: go.sum
- name: Cache Go build cache
uses: actions/cache@v4
with:
# setup-go only caches modules (GOMODCACHE). This caches compiled
# packages (GOCACHE) so the cgo/WebKit compile goes incremental.
# restore-keys gives a close cache instead of a cold one when
# go.sum changes. First run after adding this still compiles cold
# (it saves); the payoff shows from the second run on.
path: ~/.cache/go-build
key: go-build-1.25-${{ runner.os }}-${{ hashFiles('go.sum') }}
restore-keys: |
go-build-1.25-${{ runner.os }}-
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
# Caches npm's download cache keyed on the frontend lockfile, so
# the `npm install` inside `wails build` stops fetching cold.
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install Wails Linux build dependencies - name: Install Wails Linux build dependencies
run: | run: |
@@ -94,11 +111,23 @@ jobs:
libgtk-3-dev libwebkit2gtk-4.1-dev \ libgtk-3-dev libwebkit2gtk-4.1-dev \
jq jq
- name: Cache Wails CLI
uses: actions/cache@v4
id: wails-cli
with:
# The compiled CLI binary. Bump the key whenever the @version pin
# below changes, or the old CLI will be silently reused.
path: ~/go/bin/wails
key: wails-v2.15.0-${{ runner.os }}
- name: Install Wails CLI - name: Install Wails CLI
if: steps.wails-cli.outputs.cache-hit != 'true'
run: | run: |
set -euo pipefail set -euo pipefail
go install github.com/wailsapp/wails/v2/cmd/wails@v2.15.0 go install github.com/wailsapp/wails/v2/cmd/wails@v2.15.0
echo "${HOME}/go/bin" >> "${GITHUB_PATH}"
- name: Add Go bin to PATH
run: echo "${HOME}/go/bin" >> "${GITHUB_PATH}"
- name: Write environment.go from secrets - name: Write environment.go from secrets
env: env:
+4 -1
View File
@@ -23,10 +23,13 @@ go.work
# ---> Wails # ---> Wails
build/bin build/bin
.task/
node_modules node_modules
frontend/dist frontend/dist
package.json.md5 package.json.md5
package-lock.json # Root-level lockfile only: the frontend lockfile IS tracked so CI installs
# are reproducible and setup-node's npm cache has something to hash.
/package-lock.json
.idea .idea
.env .env
environment.go environment.go
+56 -21
View File
@@ -19,18 +19,48 @@ import (
var aniListJwt AniListJWT var aniListJwt AniListJWT
var aniRing, _ = keyring.Open(keyring.Config{ var aniRing keyring.Keyring
func init() {
var err error
aniRing, err = keyring.Open(keyring.Config{
ServiceName: "AniTrack", ServiceName: "AniTrack",
KeychainName: "AniTrack", KeychainName: "AniTrack",
KeychainSynchronizable: false, KeychainSynchronizable: false,
KeychainTrustApplication: true, KeychainTrustApplication: true,
KeychainAccessibleWhenUnlocked: true, KeychainAccessibleWhenUnlocked: true,
}) })
if err != nil {
log.Printf("anilist: secure storage unavailable: %s", err)
}
}
func aniRingReady() bool {
if aniRing == nil {
log.Println("anilist: secure storage unavailable")
return false
}
return true
}
func aniRingSet(key string, data []byte) error {
if !aniRingReady() {
return errors.New("anilist: secure storage unavailable")
}
if err := aniRing.Set(keyring.Item{Key: key, Data: data}); err != nil {
log.Printf("anilist: save %s failed: %s", key, err)
return err
}
return nil
}
var aniCtxShutdown, aniCancel = context.WithCancel(context.Background()) var aniCtxShutdown, aniCancel = context.WithCancel(context.Background())
func (a *App) CheckIfAniListLoggedIn() bool { func (a *App) CheckIfAniListLoggedIn() bool {
if (AniListJWT{} == aniListJwt) { if (AniListJWT{} == aniListJwt) {
if !aniRingReady() {
return false
}
tokenType, tokenErr := aniRing.Get("anilistTokenType") tokenType, tokenErr := aniRing.Get("anilistTokenType")
expiresIn, expiresInErr := aniRing.Get("anilistTokenExpiresIn") expiresIn, expiresInErr := aniRing.Get("anilistTokenExpiresIn")
refreshToken, refreshTokenErr := aniRing.Get("anilistRefreshToken") refreshToken, refreshTokenErr := aniRing.Get("anilistRefreshToken")
@@ -38,10 +68,15 @@ func (a *App) CheckIfAniListLoggedIn() bool {
if (tokenErr != nil || expiresInErr != nil || refreshTokenErr != nil || accessTokenErr != nil) || len(accessToken.Data) == 0 { if (tokenErr != nil || expiresInErr != nil || refreshTokenErr != nil || accessTokenErr != nil) || len(accessToken.Data) == 0 {
return false return false
} else { } else {
var expiresInConvertErr error
aniListJwt.TokenType = string(tokenType.Data) aniListJwt.TokenType = string(tokenType.Data)
aniListJwt.AccessToken = string(accessToken.Data) aniListJwt.AccessToken = string(accessToken.Data)
aniListJwt.RefreshToken = string(refreshToken.Data) aniListJwt.RefreshToken = string(refreshToken.Data)
aniListJwt.ExpiresIn, _ = strconv.Atoi(string(expiresIn.Data)) aniListJwt.ExpiresIn, expiresInConvertErr = strconv.Atoi(string(expiresIn.Data))
if expiresInConvertErr != nil {
log.Printf("anilist: invalid expiresIn %q: %s", string(expiresIn.Data), expiresInConvertErr)
return false
}
return true return true
} }
} else { } else {
@@ -51,6 +86,10 @@ func (a *App) CheckIfAniListLoggedIn() bool {
func (a *App) AniListLogin() { func (a *App) AniListLogin() {
if (AniListJWT{} == aniListJwt) { if (AniListJWT{} == aniListJwt) {
if !aniRingReady() {
log.Println("anilist: cannot check login, secure storage unavailable")
return
}
tokenType, tokenErr := aniRing.Get("anilistTokenType") tokenType, tokenErr := aniRing.Get("anilistTokenType")
expiresIn, expiresInErr := aniRing.Get("anilistTokenExpiresIn") expiresIn, expiresInErr := aniRing.Get("anilistTokenExpiresIn")
refreshToken, refreshTokenErr := aniRing.Get("anilistRefreshToken") refreshToken, refreshTokenErr := aniRing.Get("anilistRefreshToken")
@@ -64,10 +103,14 @@ func (a *App) AniListLogin() {
a.handleAniListCallback(serverDone) a.handleAniListCallback(serverDone)
serverDone.Wait() serverDone.Wait()
} else { } else {
var expiresInConvertErr error
aniListJwt.TokenType = string(tokenType.Data) aniListJwt.TokenType = string(tokenType.Data)
aniListJwt.AccessToken = string(accessToken.Data) aniListJwt.AccessToken = string(accessToken.Data)
aniListJwt.RefreshToken = string(refreshToken.Data) aniListJwt.RefreshToken = string(refreshToken.Data)
aniListJwt.ExpiresIn, _ = strconv.Atoi(string(expiresIn.Data)) aniListJwt.ExpiresIn, expiresInConvertErr = strconv.Atoi(string(expiresIn.Data))
if expiresInConvertErr != nil {
log.Printf("anilist: invalid expiresIn %q: %s", string(expiresIn.Data), expiresInConvertErr)
}
} }
} }
} }
@@ -85,22 +128,10 @@ func (a *App) handleAniListCallback(wg *sync.WaitGroup) {
content := r.FormValue("code") content := r.FormValue("code")
if content != "" { if content != "" {
aniListJwt = getAniListAuthorizationToken(content) aniListJwt = getAniListAuthorizationToken(content)
_ = aniRing.Set(keyring.Item{ _ = aniRingSet("anilistTokenType", []byte(aniListJwt.TokenType))
Key: "anilistTokenType", _ = aniRingSet("anilistTokenExpiresIn", []byte(strconv.Itoa(aniListJwt.ExpiresIn)))
Data: []byte(aniListJwt.TokenType), _ = aniRingSet("anilistAccessToken", []byte(aniListJwt.AccessToken))
}) _ = aniRingSet("anilistRefreshToken", []byte(aniListJwt.RefreshToken))
_ = aniRing.Set(keyring.Item{
Key: "anilistTokenExpiresIn",
Data: []byte(strconv.Itoa(aniListJwt.ExpiresIn)),
})
_ = aniRing.Set(keyring.Item{
Key: "anilistAccessToken",
Data: []byte(aniListJwt.AccessToken),
})
_ = aniRing.Set(keyring.Item{
Key: "anilistRefreshToken",
Data: []byte(aniListJwt.RefreshToken),
})
_, err := runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{ _, err := runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{
Title: "AniList Authorization", Title: "AniList Authorization",
Message: "It is now safe to close your browser tab", Message: "It is now safe to close your browser tab",
@@ -208,12 +239,16 @@ func (a *App) GetAniListLoggedInUser() AniListUser {
func (a *App) LogoutAniList() string { func (a *App) LogoutAniList() string {
if (AniListJWT{} != aniListJwt) { if (AniListJWT{} != aniListJwt) {
if !aniRingReady() {
aniListJwt = AniListJWT{}
return "AniList Logged Out Successfully"
}
typeErr := aniRing.Remove("anilistTokenType") typeErr := aniRing.Remove("anilistTokenType")
expiresInErr := aniRing.Remove("anilistTokenExpiresIn") expiresInErr := aniRing.Remove("anilistTokenExpiresIn")
accessTokenErr := aniRing.Remove("anilistAccessToken") accessTokenErr := aniRing.Remove("anilistAccessToken")
refreshTokenErr := aniRing.Remove("anilistRefreshToken") refreshTokenErr := aniRing.Remove("anilistRefreshToken")
if typeErr != nil || expiresInErr != nil || accessTokenErr != nil || refreshTokenErr != nil { if typeErr != nil || expiresInErr != nil || accessTokenErr != nil || refreshTokenErr != nil {
fmt.Println("AniList Logout Failed") log.Printf("anilist: logout cleanup failed (type=%v expires=%v access=%v refresh=%v)", typeErr, expiresInErr, accessTokenErr, refreshTokenErr)
} }
aniListJwt = AniListJWT{} aniListJwt = AniListJWT{}
} }
+52 -37
View File
@@ -23,13 +23,40 @@ import (
var myAnimeListJwt MyAnimeListJWT var myAnimeListJwt MyAnimeListJWT
var myAnimeListRing, _ = keyring.Open(keyring.Config{ var myAnimeListRing keyring.Keyring
func init() {
var err error
myAnimeListRing, err = keyring.Open(keyring.Config{
ServiceName: "AniTrack", ServiceName: "AniTrack",
KeychainName: "AniTrack", KeychainName: "AniTrack",
KeychainSynchronizable: false, KeychainSynchronizable: false,
KeychainTrustApplication: true, KeychainTrustApplication: true,
KeychainAccessibleWhenUnlocked: true, KeychainAccessibleWhenUnlocked: true,
}) })
if err != nil {
log.Printf("mal: secure storage unavailable: %s", err)
}
}
func malRingReady() bool {
if myAnimeListRing == nil {
log.Println("mal: secure storage unavailable")
return false
}
return true
}
func malRingSet(key string, data []byte) error {
if !malRingReady() {
return errors.New("mal: secure storage unavailable")
}
if err := myAnimeListRing.Set(keyring.Item{Key: key, Data: data}); err != nil {
log.Printf("mal: save %s failed: %s", key, err)
return err
}
return nil
}
var myAnimeListCtxShutdown, myAnimeListCancel = context.WithCancel(context.Background()) var myAnimeListCtxShutdown, myAnimeListCancel = context.WithCancel(context.Background())
@@ -72,6 +99,9 @@ func (v *CodeVerifier) CodeChallengeS256() string {
func (a *App) CheckIfMyAnimeListLoggedIn() bool { func (a *App) CheckIfMyAnimeListLoggedIn() bool {
if (MyAnimeListJWT{} == myAnimeListJwt) { if (MyAnimeListJWT{} == myAnimeListJwt) {
if !malRingReady() {
return false
}
tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType") tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn") expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn")
accessToken, accessTokenErr := myAnimeListRing.Get("MyAnimeListAccessToken") accessToken, accessTokenErr := myAnimeListRing.Get("MyAnimeListAccessToken")
@@ -83,7 +113,8 @@ func (a *App) CheckIfMyAnimeListLoggedIn() bool {
myAnimeListJwt.TokenType = string(tokenType.Data) myAnimeListJwt.TokenType = string(tokenType.Data)
myAnimeListJwt.ExpiresIn, expiresInConvertErr = strconv.Atoi(string(expiresIn.Data)) myAnimeListJwt.ExpiresIn, expiresInConvertErr = strconv.Atoi(string(expiresIn.Data))
if expiresInConvertErr != nil { if expiresInConvertErr != nil {
fmt.Println("unable to convert string to int") log.Printf("mal: invalid expiresIn %q: %s", string(expiresIn.Data), expiresInConvertErr)
return false
} }
myAnimeListJwt.AccessToken = string(accessToken.Data) myAnimeListJwt.AccessToken = string(accessToken.Data)
myAnimeListJwt.RefreshToken = string(refreshToken.Data) myAnimeListJwt.RefreshToken = string(refreshToken.Data)
@@ -97,6 +128,10 @@ func (a *App) CheckIfMyAnimeListLoggedIn() bool {
func (a *App) MyAnimeListLogin() { func (a *App) MyAnimeListLogin() {
if !a.CheckIfMyAnimeListLoggedIn() { if !a.CheckIfMyAnimeListLoggedIn() {
fmt.Println("check logged in function failed") fmt.Println("check logged in function failed")
if !malRingReady() {
log.Println("mal: cannot check login, secure storage unavailable")
return
}
tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType") tokenType, tokenErr := myAnimeListRing.Get("MyAnimeListTokenType")
expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn") expiresIn, expiresInErr := myAnimeListRing.Get("MyAnimeListExpiresIn")
accessToken, accessTokenErr := myAnimeListRing.Get("MyAnimeListAccessToken") accessToken, accessTokenErr := myAnimeListRing.Get("MyAnimeListAccessToken")
@@ -114,7 +149,7 @@ func (a *App) MyAnimeListLogin() {
myAnimeListJwt.TokenType = string(tokenType.Data) myAnimeListJwt.TokenType = string(tokenType.Data)
myAnimeListJwt.ExpiresIn, expiresInConvertErr = strconv.Atoi(string(expiresIn.Data)) myAnimeListJwt.ExpiresIn, expiresInConvertErr = strconv.Atoi(string(expiresIn.Data))
if expiresInConvertErr != nil { if expiresInConvertErr != nil {
fmt.Println("unable to convert string to int in Login function") log.Printf("mal: invalid expiresIn %q: %s", string(expiresIn.Data), expiresInConvertErr)
} }
myAnimeListJwt.AccessToken = string(accessToken.Data) myAnimeListJwt.AccessToken = string(accessToken.Data)
myAnimeListJwt.RefreshToken = string(refreshToken.Data) myAnimeListJwt.RefreshToken = string(refreshToken.Data)
@@ -136,22 +171,10 @@ func (a *App) handleMyAnimeListCallback(wg *sync.WaitGroup, verifier *CodeVerifi
if content != "" { if content != "" {
myAnimeListJwt = getMyAnimeListAuthorizationToken(content, verifier) myAnimeListJwt = getMyAnimeListAuthorizationToken(content, verifier)
_ = myAnimeListRing.Set(keyring.Item{ _ = malRingSet("MyAnimeListTokenType", []byte(myAnimeListJwt.TokenType))
Key: "MyAnimeListTokenType", _ = malRingSet("MyAnimeListExpiresIn", []byte(strconv.Itoa(myAnimeListJwt.ExpiresIn)))
Data: []byte(myAnimeListJwt.TokenType), _ = malRingSet("MyAnimeListAccessToken", []byte(myAnimeListJwt.AccessToken))
}) _ = malRingSet("MyAnimeListRefreshToken", []byte(myAnimeListJwt.RefreshToken))
_ = myAnimeListRing.Set(keyring.Item{
Key: "MyAnimeListExpiresIn",
Data: []byte(strconv.Itoa(myAnimeListJwt.ExpiresIn)),
})
_ = myAnimeListRing.Set(keyring.Item{
Key: "MyAnimeListAccessToken",
Data: []byte(myAnimeListJwt.AccessToken),
})
_ = myAnimeListRing.Set(keyring.Item{
Key: "MyAnimeListRefreshToken",
Data: []byte(myAnimeListJwt.RefreshToken),
})
_, err := runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{ _, err := runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{
Title: "MyAnimeList Authorization", Title: "MyAnimeList Authorization",
Message: "It is now safe to close your browser tab", Message: "It is now safe to close your browser tab",
@@ -292,22 +315,10 @@ func refreshMyAnimeListAuthorizationToken() bool {
myAnimeListJwt = refreshed myAnimeListJwt = refreshed
_ = myAnimeListRing.Set(keyring.Item{ _ = malRingSet("MyAnimeListTokenType", []byte(myAnimeListJwt.TokenType))
Key: "MyAnimeListTokenType", _ = malRingSet("MyAnimeListExpiresIn", []byte(strconv.Itoa(myAnimeListJwt.ExpiresIn)))
Data: []byte(myAnimeListJwt.TokenType), _ = malRingSet("MyAnimeListAccessToken", []byte(myAnimeListJwt.AccessToken))
}) _ = malRingSet("MyAnimeListRefreshToken", []byte(myAnimeListJwt.RefreshToken))
_ = myAnimeListRing.Set(keyring.Item{
Key: "MyAnimeListExpiresIn",
Data: []byte(strconv.Itoa(myAnimeListJwt.ExpiresIn)),
})
_ = myAnimeListRing.Set(keyring.Item{
Key: "MyAnimeListAccessToken",
Data: []byte(myAnimeListJwt.AccessToken),
})
_ = myAnimeListRing.Set(keyring.Item{
Key: "MyAnimeListRefreshToken",
Data: []byte(myAnimeListJwt.RefreshToken),
})
return true return true
} }
@@ -367,12 +378,16 @@ func createUser() MyAnimeListUser {
func (a *App) LogoutMyAnimeList() string { func (a *App) LogoutMyAnimeList() string {
if (MyAnimeListJWT{} != myAnimeListJwt) { if (MyAnimeListJWT{} != myAnimeListJwt) {
if !malRingReady() {
myAnimeListJwt = MyAnimeListJWT{}
return "MAL Logged Out Successfully"
}
typeErr := myAnimeListRing.Remove("MyAnimeListTokenType") typeErr := myAnimeListRing.Remove("MyAnimeListTokenType")
expiresInErr := myAnimeListRing.Remove("MyAnimeListExpiresIn") expiresInErr := myAnimeListRing.Remove("MyAnimeListExpiresIn")
accessTokenErr := myAnimeListRing.Remove("MyAnimeListAccessToken") accessTokenErr := myAnimeListRing.Remove("MyAnimeListAccessToken")
refreshTokenErr := myAnimeListRing.Remove("MyAnimeListRefreshToken") refreshTokenErr := myAnimeListRing.Remove("MyAnimeListRefreshToken")
if typeErr != nil || expiresInErr != nil || accessTokenErr != nil || refreshTokenErr != nil { if typeErr != nil || expiresInErr != nil || accessTokenErr != nil || refreshTokenErr != nil {
fmt.Println("MAL Logout Failed") log.Printf("mal: logout cleanup failed (type=%v expires=%v access=%v refresh=%v)", typeErr, expiresInErr, accessTokenErr, refreshTokenErr)
} }
myAnimeListJwt = MyAnimeListJWT{} myAnimeListJwt = MyAnimeListJWT{}
} }
+44 -15
View File
@@ -17,18 +17,48 @@ import (
var simklJwt SimklJWT var simklJwt SimklJWT
var simklRing, _ = keyring.Open(keyring.Config{ var simklRing keyring.Keyring
func init() {
var err error
simklRing, err = keyring.Open(keyring.Config{
ServiceName: "AniTrack", ServiceName: "AniTrack",
KeychainName: "AniTrack", KeychainName: "AniTrack",
KeychainSynchronizable: false, KeychainSynchronizable: false,
KeychainTrustApplication: true, KeychainTrustApplication: true,
KeychainAccessibleWhenUnlocked: true, KeychainAccessibleWhenUnlocked: true,
}) })
if err != nil {
log.Printf("simkl: secure storage unavailable: %s", err)
}
}
func simklRingReady() bool {
if simklRing == nil {
log.Println("simkl: secure storage unavailable")
return false
}
return true
}
func simklRingSet(key string, data []byte) error {
if !simklRingReady() {
return errors.New("simkl: secure storage unavailable")
}
if err := simklRing.Set(keyring.Item{Key: key, Data: data}); err != nil {
log.Printf("simkl: save %s failed: %s", key, err)
return err
}
return nil
}
var simklCtxShutdown, simklCancel = context.WithCancel(context.Background()) var simklCtxShutdown, simklCancel = context.WithCancel(context.Background())
func (a *App) CheckIfSimklLoggedIn() bool { func (a *App) CheckIfSimklLoggedIn() bool {
if (SimklJWT{} == simklJwt) { if (SimklJWT{} == simklJwt) {
if !simklRingReady() {
return false
}
tokenType, tokenTypeErr := simklRing.Get("SimklTokenType") tokenType, tokenTypeErr := simklRing.Get("SimklTokenType")
accessToken, accessTokenErr := simklRing.Get("SimklAccessToken") accessToken, accessTokenErr := simklRing.Get("SimklAccessToken")
scope, scopeErr := simklRing.Get("SimklScope") scope, scopeErr := simklRing.Get("SimklScope")
@@ -47,6 +77,10 @@ func (a *App) CheckIfSimklLoggedIn() bool {
func (a *App) SimklLogin() { func (a *App) SimklLogin() {
if !a.CheckIfSimklLoggedIn() { if !a.CheckIfSimklLoggedIn() {
if !simklRingReady() {
log.Println("simkl: cannot check login, secure storage unavailable")
return
}
tokenType, tokenTypeErr := simklRing.Get("SimklTokenType") tokenType, tokenTypeErr := simklRing.Get("SimklTokenType")
accessToken, accessTokenErr := simklRing.Get("SimklAccessToken") accessToken, accessTokenErr := simklRing.Get("SimklAccessToken")
scope, scopeErr := simklRing.Get("SimklScope") scope, scopeErr := simklRing.Get("SimklScope")
@@ -80,18 +114,9 @@ func (a *App) handleSimklCallback(wg *sync.WaitGroup) {
if content != "" { if content != "" {
simklJwt = getSimklAuthorizationToken(content) simklJwt = getSimklAuthorizationToken(content)
_ = simklRing.Set(keyring.Item{ _ = simklRingSet("SimklTokenType", []byte(simklJwt.TokenType))
Key: "SimklTokenType", _ = simklRingSet("SimklAccessToken", []byte(simklJwt.AccessToken))
Data: []byte(simklJwt.TokenType), _ = simklRingSet("SimklScope", []byte(simklJwt.Scope))
})
_ = simklRing.Set(keyring.Item{
Key: "SimklAccessToken",
Data: []byte(simklJwt.AccessToken),
})
_ = simklRing.Set(keyring.Item{
Key: "SimklScope",
Data: []byte(simklJwt.Scope),
})
_, err := runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{ _, err := runtime.MessageDialog(*wailsContext, runtime.MessageDialogOptions{
Title: "Simkl Authorization", Title: "Simkl Authorization",
Message: "It is now safe to close your browser tab", Message: "It is now safe to close your browser tab",
@@ -218,12 +243,16 @@ func (a *App) GetSimklLoggedInUser() SimklUser {
func (a *App) LogoutSimkl() string { func (a *App) LogoutSimkl() string {
if (SimklJWT{} != simklJwt) { if (SimklJWT{} != simklJwt) {
if !simklRingReady() {
simklJwt = SimklJWT{}
return "Simkl Logged Out Successfully"
}
tokenTypeErr := simklRing.Remove("SimklTokenType") tokenTypeErr := simklRing.Remove("SimklTokenType")
accessTokenErr := simklRing.Remove("SimklAccessToken") accessTokenErr := simklRing.Remove("SimklAccessToken")
scopeErr := simklRing.Remove("SimklScope") scopeErr := simklRing.Remove("SimklScope")
if tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil { if tokenTypeErr != nil || accessTokenErr != nil || scopeErr != nil {
fmt.Println("Simkl Logout Failed") log.Printf("simkl: logout cleanup failed (type=%v access=%v scope=%v)", tokenTypeErr, accessTokenErr, scopeErr)
} }
simklJwt = SimklJWT{} simklJwt = SimklJWT{}
} }
+2717
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -12,6 +12,6 @@
}, },
"info": { "info": {
"productName": "AniTrack", "productName": "AniTrack",
"productVersion": "1.6.6" "productVersion": "1.6.8"
} }
} }