Volver a la wiki

functions/_lib/zoho.ts — Cliente Zoho API per-user (s68)

Librería central para la integración Zoho en CreaRack Workspace. Reescrita en s68 (commit 7617371, 2026-05-17) para pasar de Service Account singleton a modelo per-user con team_member como PK en D1.

⚠️ Cambio de modelo en s68: todas las funciones añaden parámetro teamMember: TeamMember. Ver tabla de migración al final.

Interfaces principales

ZohoEnv

interface ZohoEnv {
  DB: D1Database;
  ZOHO_CLIENT_ID: string;
  ZOHO_CLIENT_SECRET: string;
}

ZohoTokenRow

interface ZohoTokenRow {
  team_member: TeamMember;       // PK (era: id INTEGER)
  zoho_email: string;            // email Zoho real resuelto en callback
  refresh_token: string;
  access_token: string | null;
  access_token_expires_at: string | null;
  dc: string;                    // 'eu' | 'com' | 'in'
  granted_by_cf_email: string;   // era: granted_by
  granted_at: string;
  updated_at: string;
}

Scopes OAuth activos (s68)

export const ZOHO_SCOPES = [
  'ZohoCalendar.event.READ',
  'ZohoCalendar.event.CREATE',   // nuevo en s68
  'ZohoCalendar.calendar.READ',
  'ZohoMail.messages.READ',      // nuevo en s68 (sustituye tasks.READ)
  'ZohoMail.accounts.READ',
].join(',');

Retirados en s68: ZohoMail.tasks.READ, ZohoMail.tasks.CREATE.

API pública

getStoredToken(env, teamMember) → ZohoTokenRow | null

Lee el row del miembro desde D1.

getValidAccessToken(env, teamMember) → { token, dc, zoho_email }

Punto de entrada preferido. Auto-refresca el token si expirado. Lanza error si el miembro no ha autorizado.

zohoFetch(env, teamMember, url, init?) → Response

Wrapper HTTP con auth automática. Añade Authorization: Zoho-oauthtoken <token>.

upsertToken(env, teamMember, zohoEmail, refreshToken, accessToken, expiresIn, dc, grantedByCfEmail) → void

Insert-or-update en zoho_oauth_tokens. PK conflict en team_member hace UPDATE.

deleteToken(env, teamMember) → void

Borra el row del miembro (desconectar desde Settings).

getAllTokensSummary(env) → Array<{team_member, zoho_email, dc, ...}>

Resumen de todos los miembros autorizados. Sin tokens. Usado en GET /api/oauth/zoho/status.

fetchPrimaryZohoEmail(env, teamMember) → string

Llama /api/accounts Zoho Mail para resolver email primario real. Devuelve 'unknown' si falla (soft fail). Usado en callback OAuth.

exchangeCodeForTokens(env, code, redirectUri, dc) → ExchangeResult

Intercambia authorization code por tokens.

buildAuthorizeUrl(env, state, redirectUri, dc?) → string

Construye URL consent screen Zoho con access_type=offline.

Helpers de host

calendarApiHost(dc) // 'calendar.zoho.eu' | 'calendar.zoho.com' | ...
mailApiHost(dc)     // 'mail.zoho.eu' | ...

Migración s67 → s68

Antes (s67)Después (s68)
WHERE id = 1WHERE team_member = ?
getValidAccessToken(env)getValidAccessToken(env, teamMember)
zohoFetch(env, url, init)zohoFetch(env, teamMember, url, init)
upsertToken(env, rt, at, exp, dc, by)upsertToken(env, tm, email, rt, at, exp, dc, by)
deleteToken(env)deleteToken(env, teamMember)
getStoredToken(env)getStoredToken(env, teamMember)

Véase también

Subir