changes
This commit is contained in:
@@ -465,7 +465,7 @@ class RcCallHistory(models.Model):
|
||||
|
||||
media_url = f'{content_uri}?access_token={config.access_token}'
|
||||
data = config._api_post(
|
||||
'/ai/speech-to-text/v1/async?webhook=false',
|
||||
'/ai/audio/v1/async/speech-to-text?webhook=false',
|
||||
data={
|
||||
'contentUri': media_url,
|
||||
'source': 'CallRecording',
|
||||
|
||||
@@ -172,46 +172,84 @@ class RcConfig(models.Model):
|
||||
return False
|
||||
|
||||
def _refresh_token(self):
|
||||
"""Refresh the access token using the refresh token."""
|
||||
"""Refresh the access token using the refresh token.
|
||||
|
||||
Uses SELECT ... FOR UPDATE to prevent concurrent cron workers
|
||||
from racing on the same refresh token (RC rotates tokens on
|
||||
each refresh, so only the first caller wins).
|
||||
"""
|
||||
self.ensure_one()
|
||||
if not self.refresh_token:
|
||||
self.write({'state': 'error'})
|
||||
return False
|
||||
|
||||
self.env.cr.execute(
|
||||
"SELECT refresh_token, state, token_expiry "
|
||||
"FROM rc_config WHERE id = %s FOR UPDATE SKIP LOCKED",
|
||||
(self.id,),
|
||||
)
|
||||
row = self.env.cr.fetchone()
|
||||
if not row:
|
||||
_logger.debug("RC config %s locked by another worker, skipping refresh", self.id)
|
||||
return True
|
||||
if not row[0]:
|
||||
return False
|
||||
if row[1] == 'connected' and row[2] and row[2] > datetime.utcnow() + timedelta(minutes=2):
|
||||
return True
|
||||
|
||||
current_refresh = row[0]
|
||||
|
||||
try:
|
||||
resp = requests.post(
|
||||
f'{self.server_url}/restapi/oauth/token',
|
||||
data={
|
||||
'grant_type': 'refresh_token',
|
||||
'refresh_token': self.refresh_token,
|
||||
'refresh_token': current_refresh,
|
||||
},
|
||||
auth=(self.client_id, self.client_secret),
|
||||
timeout=15,
|
||||
verify=self.ssl_verify,
|
||||
proxies=self._get_proxies(),
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
if resp.status_code != 200:
|
||||
try:
|
||||
err_data = resp.json()
|
||||
rc_error = err_data.get('error', 'unknown')
|
||||
rc_desc = err_data.get('error_description', '')
|
||||
rc_codes = [e.get('errorCode', '') for e in err_data.get('errors', [])]
|
||||
_logger.error(
|
||||
"RingCentral token refresh failed: %s - %s (codes: %s). "
|
||||
"Manual OAuth reconnect required.",
|
||||
rc_error, rc_desc, ', '.join(rc_codes),
|
||||
)
|
||||
except Exception:
|
||||
_logger.error(
|
||||
"RingCentral token refresh failed: HTTP %d", resp.status_code,
|
||||
)
|
||||
self.write({'state': 'error'})
|
||||
return False
|
||||
|
||||
data = resp.json()
|
||||
expires_in = data.get('expires_in', 3600)
|
||||
self.write({
|
||||
'access_token': data.get('access_token'),
|
||||
'refresh_token': data.get('refresh_token', self.refresh_token),
|
||||
'refresh_token': data.get('refresh_token', current_refresh),
|
||||
'token_expiry': datetime.utcnow() + timedelta(seconds=expires_in),
|
||||
'state': 'connected',
|
||||
})
|
||||
return True
|
||||
except Exception:
|
||||
_logger.exception("RingCentral token refresh failed")
|
||||
_logger.exception("RingCentral token refresh failed (network error)")
|
||||
self.write({'state': 'error'})
|
||||
return False
|
||||
|
||||
def _ensure_token(self):
|
||||
"""Ensure we have a valid access token, refreshing if needed."""
|
||||
"""Ensure we have a valid access token, refreshing proactively."""
|
||||
self.ensure_one()
|
||||
if not self.access_token:
|
||||
raise UserError(_('RingCentral is not connected. Please connect via OAuth first.'))
|
||||
if self.token_expiry and self.token_expiry < fields.Datetime.now():
|
||||
buffer = fields.Datetime.now() + timedelta(minutes=5)
|
||||
if self.token_expiry and self.token_expiry < buffer:
|
||||
if not self._refresh_token():
|
||||
raise UserError(_('Failed to refresh RingCentral token. Please reconnect.'))
|
||||
|
||||
@@ -643,7 +681,7 @@ class RcConfig(models.Model):
|
||||
|
||||
@api.model
|
||||
def _cron_refresh_tokens(self):
|
||||
"""Refresh tokens for all connected configs nearing expiry."""
|
||||
"""Refresh tokens for connected configs nearing expiry and recover error configs."""
|
||||
soon = fields.Datetime.now() + timedelta(minutes=10)
|
||||
configs = self.search([
|
||||
('state', '=', 'connected'),
|
||||
@@ -656,6 +694,17 @@ class RcConfig(models.Model):
|
||||
except Exception:
|
||||
_logger.exception("Cron: Failed to refresh token for config %s", cfg.name)
|
||||
|
||||
error_configs = self.search([
|
||||
('state', '=', 'error'),
|
||||
('refresh_token', '!=', False),
|
||||
])
|
||||
for cfg in error_configs:
|
||||
try:
|
||||
if cfg._refresh_token():
|
||||
_logger.info("Cron: Recovered RC config %s from error state", cfg.name)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ──────────────────────────────────────────────────────────
|
||||
# Cron: webhook renewal
|
||||
# ──────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user