fix(sync): trigger auth then resume operation when not authenticated (#69)

* fix(sync): trigger auth then resume operation when not authenticated

Problem: `get_access_token()` called `auth()` then immediately tried to
load tokens, but `auth()` is async (TCP server + browser redirect), so
tokens were never present at that point. All sync operations silently
aborted when unauthenticated.

Solution: Remove the inline auth attempt from `get_access_token()` and
add an `on_complete` callback to `auth()` / `_exchange_code()`. Add a
`with_token(callback)` helper in `gtasks.lua` and `gcal.lua` that
triggers auth with the sync operation as the continuation, so
`push`/`pull`/`sync` resume automatically after the OAuth flow
completes.

* ci: format
This commit is contained in:
Barrett Ruth 2026-03-05 13:24:43 -05:00
parent 710cf562c9
commit 715b6d4d12
3 changed files with 62 additions and 35 deletions

View file

@ -136,17 +136,31 @@ local function delete_event(access_token, calendar_id, event_id)
return err
end
---@param callback fun(access_token: string): nil
local function with_token(callback)
oauth.async(function()
local token = client:get_access_token()
if not token then
client:auth(function()
oauth.async(function()
local fresh = client:get_access_token()
if fresh then
callback(fresh)
end
end)
end)
return
end
callback(token)
end)
end
function M.auth()
client:auth()
end
function M.push()
oauth.async(function()
local access_token = client:get_access_token()
if not access_token then
return
end
with_token(function(access_token)
local calendars, cal_err = get_all_calendars(access_token)
if cal_err or not calendars then
log.error(cal_err or 'failed to fetch calendars')

View file

@ -337,23 +337,38 @@ local function pull_pass(access_token, tasklists, s, now_ts, by_gtasks_id)
return created, updated
end
---@return string? access_token
---@param access_token string
---@return table<string, string>? tasklists
---@return pending.Store? store
---@return pending.Store? s
---@return string? now_ts
local function sync_setup()
local access_token = client:get_access_token()
if not access_token then
return nil
end
local function sync_setup(access_token)
local tasklists, tl_err = get_all_tasklists(access_token)
if tl_err or not tasklists then
log.error(tl_err or 'failed to fetch task lists')
return nil
return nil, nil, nil
end
local s = require('pending').store()
local now_ts = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]]
return access_token, tasklists, s, now_ts
return tasklists, s, now_ts
end
---@param callback fun(access_token: string): nil
local function with_token(callback)
oauth.async(function()
local token = client:get_access_token()
if not token then
client:auth(function()
oauth.async(function()
local fresh = client:get_access_token()
if fresh then
callback(fresh)
end
end)
end)
return
end
callback(token)
end)
end
function M.auth()
@ -361,12 +376,11 @@ function M.auth()
end
function M.push()
oauth.async(function()
local access_token, tasklists, s, now_ts = sync_setup()
if not access_token then
with_token(function(access_token)
local tasklists, s, now_ts = sync_setup(access_token)
if not tasklists then
return
end
---@cast tasklists table<string, string>
---@cast s pending.Store
---@cast now_ts string
local by_gtasks_id = build_id_index(s)
@ -382,12 +396,11 @@ function M.push()
end
function M.pull()
oauth.async(function()
local access_token, tasklists, s, now_ts = sync_setup()
if not access_token then
with_token(function(access_token)
local tasklists, s, now_ts = sync_setup(access_token)
if not tasklists then
return
end
---@cast tasklists table<string, string>
---@cast s pending.Store
---@cast now_ts string
local by_gtasks_id = build_id_index(s)
@ -403,12 +416,11 @@ function M.pull()
end
function M.sync()
oauth.async(function()
local access_token, tasklists, s, now_ts = sync_setup()
if not access_token then
with_token(function(access_token)
local tasklists, s, now_ts = sync_setup(access_token)
if not tasklists then
return
end
---@cast tasklists table<string, string>
---@cast s pending.Store
---@cast now_ts string
local by_gtasks_id = build_id_index(s)

View file

@ -236,11 +236,7 @@ function OAuthClient:get_access_token()
local creds = self:resolve_credentials()
local tokens = self:load_tokens()
if not tokens or not tokens.refresh_token then
self:auth()
tokens = self:load_tokens()
if not tokens then
return nil
end
return nil
end
local now = os.time()
local obtained = tokens.obtained_at or 0
@ -255,8 +251,9 @@ function OAuthClient:get_access_token()
return tokens.access_token
end
---@param on_complete? fun(): nil
---@return nil
function OAuthClient:auth()
function OAuthClient:auth(on_complete)
local creds = self:resolve_credentials()
local port = self.port
@ -329,7 +326,7 @@ function OAuthClient:auth()
close_server()
if code then
vim.schedule(function()
self:_exchange_code(creds, code, code_verifier, port)
self:_exchange_code(creds, code, code_verifier, port, on_complete)
end)
end
end)
@ -347,8 +344,9 @@ end
---@param code string
---@param code_verifier string
---@param port integer
---@param on_complete? fun(): nil
---@return nil
function OAuthClient:_exchange_code(creds, code, code_verifier, port)
function OAuthClient:_exchange_code(creds, code, code_verifier, port, on_complete)
local body = 'client_id='
.. M.url_encode(creds.client_id)
.. '&client_secret='
@ -387,6 +385,9 @@ function OAuthClient:_exchange_code(creds, code, code_verifier, port)
decoded.obtained_at = os.time()
self:save_tokens(decoded)
log.info(self.name .. ' authorized successfully.')
if on_complete then
on_complete()
end
end
---@param opts { name: string, scope: string, port: integer, config_key: string }