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 committed by GitHub
parent 7fb3289b21
commit 0163941a2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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 return err
end 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() function M.auth()
client:auth() client:auth()
end end
function M.push() function M.push()
oauth.async(function() with_token(function(access_token)
local access_token = client:get_access_token()
if not access_token then
return
end
local calendars, cal_err = get_all_calendars(access_token) local calendars, cal_err = get_all_calendars(access_token)
if cal_err or not calendars then if cal_err or not calendars then
log.error(cal_err or 'failed to fetch calendars') 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 return created, updated
end end
---@return string? access_token ---@param access_token string
---@return table<string, string>? tasklists ---@return table<string, string>? tasklists
---@return pending.Store? store ---@return pending.Store? s
---@return string? now_ts ---@return string? now_ts
local function sync_setup() local function sync_setup(access_token)
local access_token = client:get_access_token()
if not access_token then
return nil
end
local tasklists, tl_err = get_all_tasklists(access_token) local tasklists, tl_err = get_all_tasklists(access_token)
if tl_err or not tasklists then if tl_err or not tasklists then
log.error(tl_err or 'failed to fetch task lists') log.error(tl_err or 'failed to fetch task lists')
return nil return nil, nil, nil
end end
local s = require('pending').store() local s = require('pending').store()
local now_ts = os.date('!%Y-%m-%dT%H:%M:%SZ') --[[@as string]] 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 end
function M.auth() function M.auth()
@ -361,12 +376,11 @@ function M.auth()
end end
function M.push() function M.push()
oauth.async(function() with_token(function(access_token)
local access_token, tasklists, s, now_ts = sync_setup() local tasklists, s, now_ts = sync_setup(access_token)
if not access_token then if not tasklists then
return return
end end
---@cast tasklists table<string, string>
---@cast s pending.Store ---@cast s pending.Store
---@cast now_ts string ---@cast now_ts string
local by_gtasks_id = build_id_index(s) local by_gtasks_id = build_id_index(s)
@ -382,12 +396,11 @@ function M.push()
end end
function M.pull() function M.pull()
oauth.async(function() with_token(function(access_token)
local access_token, tasklists, s, now_ts = sync_setup() local tasklists, s, now_ts = sync_setup(access_token)
if not access_token then if not tasklists then
return return
end end
---@cast tasklists table<string, string>
---@cast s pending.Store ---@cast s pending.Store
---@cast now_ts string ---@cast now_ts string
local by_gtasks_id = build_id_index(s) local by_gtasks_id = build_id_index(s)
@ -403,12 +416,11 @@ function M.pull()
end end
function M.sync() function M.sync()
oauth.async(function() with_token(function(access_token)
local access_token, tasklists, s, now_ts = sync_setup() local tasklists, s, now_ts = sync_setup(access_token)
if not access_token then if not tasklists then
return return
end end
---@cast tasklists table<string, string>
---@cast s pending.Store ---@cast s pending.Store
---@cast now_ts string ---@cast now_ts string
local by_gtasks_id = build_id_index(s) local by_gtasks_id = build_id_index(s)

View file

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