@hostwebhook/platform-contracts 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/operadores.d.ts +60 -0
- package/dist/operadores.js +123 -0
- package/dist/servicios/almacenes-vectoriales.d.ts +105 -0
- package/dist/servicios/almacenes-vectoriales.js +26 -0
- package/dist/servicios/aprobaciones-pendientes.d.ts +207 -0
- package/dist/servicios/aprobaciones-pendientes.js +51 -0
- package/dist/servicios/avisos-en-vivo.d.ts +106 -0
- package/dist/servicios/avisos-en-vivo.js +60 -0
- package/dist/servicios/canal-de-stream.d.ts +78 -0
- package/dist/servicios/canal-de-stream.js +5 -0
- package/dist/servicios/conversaciones-del-chat.d.ts +139 -0
- package/dist/servicios/conversaciones-del-chat.js +51 -0
- package/dist/servicios/corridas-programadas.d.ts +29 -0
- package/dist/servicios/corridas-programadas.js +5 -0
- package/dist/servicios/credencial/permiso-sobre-credencial.d.ts +61 -0
- package/dist/servicios/credencial/permiso-sobre-credencial.js +42 -0
- package/dist/servicios/credenciales-del-gateway.d.ts +722 -0
- package/dist/servicios/credenciales-del-gateway.js +114 -0
- package/dist/servicios/cuentas-de-atlassian.d.ts +22 -0
- package/dist/servicios/cuentas-de-atlassian.js +5 -0
- package/dist/servicios/descarga-de-drive.d.ts +74 -0
- package/dist/servicios/descarga-de-drive.js +41 -0
- package/dist/servicios/ejecucion-de-ia.d.ts +81 -0
- package/dist/servicios/ejecucion-de-ia.js +39 -0
- package/dist/servicios/enlaces-de-flujo.d.ts +59 -0
- package/dist/servicios/enlaces-de-flujo.js +27 -0
- package/dist/servicios/ficheros-del-gateway.d.ts +210 -0
- package/dist/servicios/ficheros-del-gateway.js +8 -0
- package/dist/servicios/formas-compartidas.d.ts +57 -0
- package/dist/servicios/formas-compartidas.js +22 -0
- package/dist/servicios/historial-de-corridas.d.ts +75 -0
- package/dist/servicios/historial-de-corridas.js +7 -0
- package/dist/servicios/index.d.ts +57 -0
- package/dist/servicios/index.js +44 -0
- package/dist/servicios/limites-del-plan.d.ts +82 -0
- package/dist/servicios/limites-del-plan.js +41 -0
- package/dist/servicios/motor-de-ejecucion.d.ts +197 -0
- package/dist/servicios/motor-de-ejecucion.js +11 -0
- package/dist/servicios/plantillas-de-origen.d.ts +25 -0
- package/dist/servicios/plantillas-de-origen.js +5 -0
- package/dist/servicios/posts-sociales.d.ts +69 -0
- package/dist/servicios/posts-sociales.js +23 -0
- package/dist/servicios/registro-de-entregas.d.ts +93 -0
- package/dist/servicios/registro-de-entregas.js +46 -0
- package/dist/servicios/registro-de-eventos.d.ts +135 -0
- package/dist/servicios/registro-de-eventos.js +62 -0
- package/dist/servicios/restauracion-de-nodos.d.ts +53 -0
- package/dist/servicios/restauracion-de-nodos.js +4 -0
- package/dist/servicios/salud-del-webhook.d.ts +26 -0
- package/dist/servicios/salud-del-webhook.js +6 -0
- package/dist/servicios/secretos-de-firma.d.ts +26 -0
- package/dist/servicios/secretos-de-firma.js +5 -0
- package/dist/servicios/telemetria.d.ts +114 -0
- package/dist/servicios/telemetria.js +60 -0
- package/dist/servicios/trazas-de-llm.d.ts +79 -0
- package/dist/servicios/trazas-de-llm.js +23 -0
- package/dist/servicios/tuneles.d.ts +106 -0
- package/dist/servicios/tuneles.js +87 -0
- package/dist/servicios/workspace/acceso-al-recurso.d.ts +39 -0
- package/dist/servicios/workspace/acceso-al-recurso.js +5 -0
- package/dist/servicios/zona-horaria-del-usuario.d.ts +40 -0
- package/dist/servicios/zona-horaria-del-usuario.js +7 -0
- package/package.json +9 -3
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
import { BadRequestException } from '@nestjs/common';
|
|
2
|
+
import type { CredentialType } from '@hostwebhook/node-types';
|
|
3
|
+
/**
|
|
4
|
+
* Lo que un nodo pregunta al gateway sobre una credencial.
|
|
5
|
+
*
|
|
6
|
+
* ## Esta costura NO se corta como las otras diecinueve
|
|
7
|
+
*
|
|
8
|
+
* En todas las anteriores bastaba con invertir la dependencia: declarar la
|
|
9
|
+
* superficie usada y atarla con un token. Aquí eso sería un error de diseño.
|
|
10
|
+
*
|
|
11
|
+
* Un `getDecrypted(id)` por token funcionaría perfectamente dentro del repo, y
|
|
12
|
+
* el día que la costura fuera HTTP significaría **hw-nodes pidiendo secretos
|
|
13
|
+
* descifrados por la red en cada ejecución, sin que nadie compruebe que ese
|
|
14
|
+
* nodo tenga algo que ver con esa credencial**. Es justo lo que el
|
|
15
|
+
* `hw-credentials` de la Fase 4 existe para evitar. Cortarla así sería trabajo
|
|
16
|
+
* que hay que rehacer.
|
|
17
|
+
*
|
|
18
|
+
* Así que este contrato se construye en la forma FINAL desde el principio:
|
|
19
|
+
*
|
|
20
|
+
* describir() metadatos. No trae el secreto, y no puede traerlo.
|
|
21
|
+
* material() el secreto, y SIEMPRE diciendo quién lo pide. (PR C)
|
|
22
|
+
* tokenDe…() tokens ya refrescados; el cliente se monta en el nodo. (PR C)
|
|
23
|
+
*
|
|
24
|
+
* Este PR trae sólo `describir`. Cada método llega cuando su implementación
|
|
25
|
+
* está atada — un contrato con métodos que nadie provee es una mentira que
|
|
26
|
+
* compila.
|
|
27
|
+
*
|
|
28
|
+
* ## Por qué `describir` no lleva quién pide, y `material` sí
|
|
29
|
+
*
|
|
30
|
+
* Porque no son la misma pregunta. `credentials.service.findOne` hace
|
|
31
|
+
* `.select('-encryptedData')`: devuelve el tipo, el nombre y los metadatos, y
|
|
32
|
+
* el secreto NO sale por ahí. Exigir atribución para leer el tipo de una
|
|
33
|
+
* credencial sería ruido en el registro de accesos, que existe para responder
|
|
34
|
+
* «quién leyó este secreto».
|
|
35
|
+
*/
|
|
36
|
+
/**
|
|
37
|
+
* Los metadatos de una credencial. Medido dos veces, por caminos distintos:
|
|
38
|
+
* de los 53 sitios que llaman a `findOne`, lo único que se lee del documento
|
|
39
|
+
* es `type` (161 veces), `metadata` (25) y `name` (6).
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ Los `accessToken`, `phoneNumberId` y `organizationId` que aparecen al
|
|
42
|
+
* barrer NO salen de aquí: son blobs ya descifrados, o documentos que devuelve
|
|
43
|
+
* `SlackOAuthService.findCredentialsByTeamId`. Se comprobó uno por uno.
|
|
44
|
+
*/
|
|
45
|
+
export interface DescripcionDeCredencial {
|
|
46
|
+
id: string;
|
|
47
|
+
/**
|
|
48
|
+
* ⚠️ El tipo viene de `@hostwebhook/node-types`, no de la entidad. Es un
|
|
49
|
+
* paquete, así que los dos lados lo alcanzan y este contrato no toca
|
|
50
|
+
* Mongoose ni de refilón.
|
|
51
|
+
*/
|
|
52
|
+
type: CredentialType;
|
|
53
|
+
name: string;
|
|
54
|
+
/**
|
|
55
|
+
* ⚠️ Los metadatos SIN pasar por `toJSON()`.
|
|
56
|
+
*
|
|
57
|
+
* El `toJSON` de la entidad los borra cuando la credencial está marcada
|
|
58
|
+
* como sensible — eso es para la respuesta HTTP, no para el motor. Quien
|
|
59
|
+
* los lee aquí es el enrutado por túnel y la zona horaria del calendario,
|
|
60
|
+
* que necesitan el valor real. Aplanar con `toJSON()` los dejaría vacíos
|
|
61
|
+
* para las credenciales sensibles, en silencio y sólo para algunas.
|
|
62
|
+
*/
|
|
63
|
+
metadata: Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Quién pide un secreto, y con qué derecho. Las dos formas son EXPLÍCITAS.
|
|
67
|
+
*
|
|
68
|
+
* ## Por qué es obligatorio y no un `context?` opcional
|
|
69
|
+
*
|
|
70
|
+
* Porque opcional es lo que hay hoy y no funciona. De las 59 lecturas de
|
|
71
|
+
* secretos en `nodes/` + `common/`:
|
|
72
|
+
*
|
|
73
|
+
* 19 dicen de verdad qué nodo fue
|
|
74
|
+
* 7 pasan `relatedEntity` con `id: ctx.entityId ?? ''` — el campo está
|
|
75
|
+
* y el id viene VACÍO, así que la fila del registro dice
|
|
76
|
+
* «un social_media_action, id ''», que no enlaza con nada
|
|
77
|
+
* 8 sólo un `purpose`
|
|
78
|
+
* 25 nada
|
|
79
|
+
*
|
|
80
|
+
* El registro de accesos del api#433 existe y funciona; dos tercios de las
|
|
81
|
+
* lecturas no lo alimentan. Ponerlo en el TIPO lo arregla de una vez y sin
|
|
82
|
+
* riesgo de ejecución: lo que falta no compila.
|
|
83
|
+
*
|
|
84
|
+
* ⚠️ El caso del id vacío no lo caza TypeScript —`'' `es un `string` válido—,
|
|
85
|
+
* así que el adaptador lo comprueba y lo apunta. Ver `comoAtribuir`.
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* Los tipos de entidad que pueden pedir una credencial.
|
|
89
|
+
*
|
|
90
|
+
* ## Por qué es una unión y no un `string`
|
|
91
|
+
*
|
|
92
|
+
* Porque con `string` la grafía se tuerce sola. El registro de accesos tenía
|
|
93
|
+
* las dos mezcladas —`telegramAction` y `discordAction` en camel;
|
|
94
|
+
* `ai_node`, `social_media_action`, `vector_store` y `voice_agent` en snake—
|
|
95
|
+
* y `relatedEntityType` se agrupa con
|
|
96
|
+
* `$group: { _id: { type: '$relatedEntityType', id: '$relatedEntityId' } }`.
|
|
97
|
+
* Dos grafías del mismo tipo son dos entidades distintas al mirar el registro.
|
|
98
|
+
*
|
|
99
|
+
* ⚠️ Se elige CAMEL porque es lo que casa con el `nodeType` real que registra
|
|
100
|
+
* cada nodo (`socialMediaAction`, `docsAction`, `sheetsAction`…). Snake era
|
|
101
|
+
* ad hoc. Con esto, una grafía nueva no compila hasta que se declara aquí, que
|
|
102
|
+
* es el momento de decidirla.
|
|
103
|
+
*
|
|
104
|
+
* ⚠️ Y sí, cambiar `ai_node` por `aiNode` parte la agrupación de las filas
|
|
105
|
+
* viejas. El `id` sigue siendo el mismo —que es la clave de verdad— y el
|
|
106
|
+
* registro es forense, no un índice: se prefiere que de hoy en adelante diga
|
|
107
|
+
* una sola cosa.
|
|
108
|
+
*/
|
|
109
|
+
export type TipoDeEntidad = 'trigger' | 'webhook' | 'aiNode' | 'socialMediaAction' | 'socialPost' | 'calendarAction' | 'docsAction' | 'driveAction' | 'sheetsAction' | 'emailAction' | 'gmailAction' | 'approvalNode' | 'telegramAction' | 'whatsappAction' | 'discordAction' | 'slackAction' | 'mongoAction' | 'postgresAction' | 'httpAction' | 'githubAction' | 'notionAction' | 'jiraAction' | 'shopifyAction' | 'mailchimpAction' | 'firecrawlAction' | 'notificationAction' | 'googleAnalyticsAction' | 'googleContactsAction' | 'vectorStore' | 'voiceAgent';
|
|
110
|
+
/**
|
|
111
|
+
* Por qué esta lectura NO se puede comprobar contra el documento de hoy.
|
|
112
|
+
*
|
|
113
|
+
* ⚠️ Son excepciones NOMBRADAS, no agujeros. En los dos casos el documento
|
|
114
|
+
* correcto contra el que comprobar **ya no está en la base**, así que releerlo
|
|
115
|
+
* no arregla nada: daría `sin_vinculo` sobre una lectura perfectamente
|
|
116
|
+
* legítima. Y como van con nombre, se cuentan aparte en el registro y no se
|
|
117
|
+
* confunden nunca con un `ok`.
|
|
118
|
+
*
|
|
119
|
+
* `baja-de-suscripcion` Al cambiar o desconectar la credencial de un
|
|
120
|
+
* trigger, la baja se da con el documento ANTERIOR —a propósito, porque
|
|
121
|
+
* hay que hablar con el proveedor usando la credencial VIEJA— mientras la
|
|
122
|
+
* base ya tiene la nueva. Comprobar contra la base denegaría el 100% de
|
|
123
|
+
* las veces, y el `catch` de ese camino sólo hace `warn`: sería un fallo
|
|
124
|
+
* MUDO con el proveedor siguiendo empujando a un trigger muerto.
|
|
125
|
+
*
|
|
126
|
+
* `plan-congelado` La aprobación de social media guarda el plan con sus
|
|
127
|
+
* `credentialId` y lo despacha horas después. Si en medio alguien quita
|
|
128
|
+
* una cuenta del selector, el nodo ya no la referencia — pero la
|
|
129
|
+
* publicación que se aprobó sí la incluía.
|
|
130
|
+
*
|
|
131
|
+
* ⚠️ Añadir un motivo nuevo es abrir una puerta: hay un guardián que enumera
|
|
132
|
+
* quién puede usar cada uno, para que la lista no crezca sin que se vea.
|
|
133
|
+
*/
|
|
134
|
+
export type MotivoSinComprobar = 'baja-de-suscripcion' | 'plan-congelado';
|
|
135
|
+
export type QuienPide =
|
|
136
|
+
/**
|
|
137
|
+
* Un nodo, trigger o entidad del dominio. Es el caso normal.
|
|
138
|
+
*
|
|
139
|
+
* `type` e `id` son los que ya usa `relatedEntity` del registro de accesos,
|
|
140
|
+
* para que las filas de antes y las de ahora se lean igual.
|
|
141
|
+
*/
|
|
142
|
+
{
|
|
143
|
+
modo: 'nodo';
|
|
144
|
+
type: TipoDeEntidad;
|
|
145
|
+
id: string;
|
|
146
|
+
motivo?: MotivoSinComprobar;
|
|
147
|
+
/**
|
|
148
|
+
* Quién lo disparó, cuando lo disparó alguien.
|
|
149
|
+
*
|
|
150
|
+
* 🔥 No es redundante con `modo: 'usuario'`, y lo parecía. Los dos modos
|
|
151
|
+
* responden a preguntas distintas: `modo` dice contra QUÉ se comprueba el
|
|
152
|
+
* vínculo —un nodo o una persona—, y esto dice quién apretó el botón.
|
|
153
|
+
*
|
|
154
|
+
* Existe porque el gateway ya hacía las dos cosas a la vez y la unión no
|
|
155
|
+
* lo dejaba decir: `vector_store_embed` pasa `userId` Y
|
|
156
|
+
* `relatedEntity: {type:'vectorStore'}`, porque una persona pidió
|
|
157
|
+
* incrustar en un almacén concreto. Con la unión a secas había que elegir
|
|
158
|
+
* una de las dos, y elegir es perder una columna del registro sin que
|
|
159
|
+
* nada falle.
|
|
160
|
+
*/
|
|
161
|
+
userId?: string;
|
|
162
|
+
/** Desde dónde. Ver `sourceIp` en el modo `usuario`. */
|
|
163
|
+
sourceIp?: string;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Una persona con sesión que eligió la credencial a mano.
|
|
167
|
+
*
|
|
168
|
+
* ⚠️ Es la excepción, y existe porque hay un caso que no puede ser `nodo`:
|
|
169
|
+
* `ai-nodes.controller` lista voces para una credencial que la persona
|
|
170
|
+
* acaba de elegir y cuyo nodo puede no existir todavía. Sin este modo, ese
|
|
171
|
+
* endpoint tendría que inventarse un nodo o quedarse sin atribuir — y
|
|
172
|
+
* inventárselo es peor, porque ensucia el registro con filas que apuntan a
|
|
173
|
+
* cosas que no existen.
|
|
174
|
+
*
|
|
175
|
+
* La lista de quién usa este modo la fija un guardián, para que no crezca
|
|
176
|
+
* sin que se vea.
|
|
177
|
+
*/
|
|
178
|
+
| {
|
|
179
|
+
modo: 'usuario';
|
|
180
|
+
userId: string;
|
|
181
|
+
/**
|
|
182
|
+
* La IP desde la que se pidió, si el llamante la tiene.
|
|
183
|
+
*
|
|
184
|
+
* ⚠️ El registro de accesos SIEMPRE ha tenido esta columna y seis
|
|
185
|
+
* llamantes del gateway la rellenaban. El contrato no la llevaba, así que
|
|
186
|
+
* pasarlos a él la habría vaciado — sin error, sin test rojo, y sin que
|
|
187
|
+
* nadie mirara esa columna hasta el día que hiciera falta.
|
|
188
|
+
*/
|
|
189
|
+
sourceIp?: string;
|
|
190
|
+
};
|
|
191
|
+
/** El secreto, con el tipo que dice cómo interpretarlo. */
|
|
192
|
+
export interface MaterialDeCredencial {
|
|
193
|
+
type: CredentialType;
|
|
194
|
+
/**
|
|
195
|
+
* Los metadatos, que ya viajan por `describir` y aquí ahorran un viaje.
|
|
196
|
+
*
|
|
197
|
+
* ⚠️ No es comodidad: la familia de bases de datos leía la MISMA metadata
|
|
198
|
+
* dos veces por despacho —una con `findOne`, para la base efectiva, y otra
|
|
199
|
+
* con `getTunnelIdForCredential`, que sólo mira `metadata.tunnelId`— además
|
|
200
|
+
* del `getDecrypted`. Tres consultas para una credencial. Con esto es una.
|
|
201
|
+
*
|
|
202
|
+
* ⚠️ Y llega SIN pasar por `toJSON()`, igual que en `describir`: ese
|
|
203
|
+
* transform borra `metadata` en las credenciales marcadas sensibles, que es
|
|
204
|
+
* justo de donde sale el `tunnelId` del enrutado. Es la tercera vez que esa
|
|
205
|
+
* decisión del adaptador resulta ser carga.
|
|
206
|
+
*/
|
|
207
|
+
metadata: Record<string, unknown>;
|
|
208
|
+
/**
|
|
209
|
+
* ⚠️ Una cadena, siempre — es lo que devuelve `getDecrypted` hoy. Quien la
|
|
210
|
+
* necesita como objeto la parsea, que es lo que ya hace: las credenciales
|
|
211
|
+
* de cuenta de servicio de Google llegan como JSON en texto.
|
|
212
|
+
*/
|
|
213
|
+
material: string;
|
|
214
|
+
}
|
|
215
|
+
/** Las siete redes que despachan por `common/social-providers`. */
|
|
216
|
+
export type RedSocial = 'bluesky' | 'twitter' | 'mastodon' | 'linkedin' | 'threads' | 'instagram' | 'facebook';
|
|
217
|
+
/** Una página o empresa de LinkedIn en nombre de la que se puede publicar. */
|
|
218
|
+
export interface OrganizacionDeLinkedIn {
|
|
219
|
+
urn: string;
|
|
220
|
+
name: string;
|
|
221
|
+
vanityName?: string;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Lo MÍNIMO con lo que se puede llamar a la API de cada red.
|
|
225
|
+
*
|
|
226
|
+
* ## ⚠️ Esto NO es el blob guardado, y la diferencia es de seguridad
|
|
227
|
+
*
|
|
228
|
+
* En Google el error fue que lo guardado era MÁS DÉBIL que lo utilizable: un
|
|
229
|
+
* refresh token con el que no se llama a nada. Aquí, en Meta, está invertido —
|
|
230
|
+
* **lo guardado es más fuerte**. El blob de `instagram_oauth2` y
|
|
231
|
+
* `facebook_oauth2` lleva dos tokens:
|
|
232
|
+
*
|
|
233
|
+
* `pageAccessToken` el que el proveedor usa, y el único que necesita
|
|
234
|
+
* `userAccessToken` 60 días, y es con el que se piden por `/me/accounts`
|
|
235
|
+
* los tokens de TODAS las páginas del usuario
|
|
236
|
+
*
|
|
237
|
+
* Los dos son `string`. Una firma `Promise<string>`, o un `{ token: string }`,
|
|
238
|
+
* **compila con el que no toca**, y el resultado no es una caída: es hw-nodes
|
|
239
|
+
* sosteniendo por la red, en cada publicación, la credencial que acuña tokens
|
|
240
|
+
* de página. Se comprobó que los proveedores declaran `userAccessToken` y no
|
|
241
|
+
* lo leen NUNCA.
|
|
242
|
+
*
|
|
243
|
+
* El tercer caso, más callado: el blob de `mastodon_oauth2` lleva el
|
|
244
|
+
* `clientId`/`clientSecret` de la app que HostWebhook registró **en esa
|
|
245
|
+
* instancia**. El proveedor tampoco los toca. Sacarlos por `material()` rompe
|
|
246
|
+
* «los client secrets se quedan en el gateway» sin que nada falle.
|
|
247
|
+
*
|
|
248
|
+
* ## Por qué es una unión discriminada y no una bolsa de opcionales
|
|
249
|
+
*
|
|
250
|
+
* Un `{ token: string; instanceUrl?: string; userId?: string }` compila para
|
|
251
|
+
* las siete y falla en ejecución, por red y de una en una: mastodon llamando a
|
|
252
|
+
* `https://undefined/api/v1/statuses`, threads a `/undefined/threads`. El
|
|
253
|
+
* identificador va en la RUTA, no en la cabecera, y mastodon ni siquiera tiene
|
|
254
|
+
* host fijo. Aquí, la red que se olvida de su campo no compila.
|
|
255
|
+
*
|
|
256
|
+
* ⚠️ Cada variante es la interfaz `Session` que el proveedor YA tenía por
|
|
257
|
+
* dentro. El estrechamiento no se inventa: se muda al gateway, que es donde
|
|
258
|
+
* está el blob.
|
|
259
|
+
*/
|
|
260
|
+
export type SesionSocial =
|
|
261
|
+
/**
|
|
262
|
+
* ⚠️ Bluesky no manda un token: manda una app password DURADERA y de cuenta
|
|
263
|
+
* completa. Se llama como lo que es. El `createSession` lo hace el nodo
|
|
264
|
+
* porque el canje no necesita nada del gateway —no hay OAuth de bluesky en
|
|
265
|
+
* el repo, es app password— y porque el `did` sólo existe después.
|
|
266
|
+
*/
|
|
267
|
+
{
|
|
268
|
+
red: 'bluesky';
|
|
269
|
+
identifier: string;
|
|
270
|
+
appPassword: string;
|
|
271
|
+
pdsUrl: string;
|
|
272
|
+
}
|
|
273
|
+
/** El ÚNICO que llega refrescado: el gateway lo rota y lo persiste. */
|
|
274
|
+
| {
|
|
275
|
+
red: 'twitter';
|
|
276
|
+
accessToken: string;
|
|
277
|
+
userId: string;
|
|
278
|
+
}
|
|
279
|
+
/** ⚠️ `instanceUrl` no es adorno: el fediverso no tiene host fijo. */
|
|
280
|
+
| {
|
|
281
|
+
red: 'mastodon';
|
|
282
|
+
accessToken: string;
|
|
283
|
+
instanceUrl: string;
|
|
284
|
+
userId: string;
|
|
285
|
+
fullHandle: string;
|
|
286
|
+
} | {
|
|
287
|
+
red: 'linkedin';
|
|
288
|
+
accessToken: string;
|
|
289
|
+
/** A quién se publica: persona o página. Sin esto no hay `ugcPosts`. */
|
|
290
|
+
authorUrn: string;
|
|
291
|
+
memberId: string;
|
|
292
|
+
memberName: string;
|
|
293
|
+
/** Derivado de `authorUrn` con `authorKindOf`. */
|
|
294
|
+
authorKind: 'person' | 'organization' | null;
|
|
295
|
+
scopes: readonly string[];
|
|
296
|
+
/** El nombre de la PÁGINA cuando se publica como página; si no, el del
|
|
297
|
+
* miembro. Con el del miembro a secas, los logs de una publicación
|
|
298
|
+
* como página decían el nombre de la persona. */
|
|
299
|
+
authorName: string;
|
|
300
|
+
}
|
|
301
|
+
/** `userId` va en la RUTA (`/{userId}/threads`), no en la cabecera. */
|
|
302
|
+
| {
|
|
303
|
+
red: 'threads';
|
|
304
|
+
accessToken: string;
|
|
305
|
+
userId: string;
|
|
306
|
+
username: string;
|
|
307
|
+
}
|
|
308
|
+
/** ⚠️ El token de PÁGINA, nunca el de usuario. Ver la cabecera. */
|
|
309
|
+
| {
|
|
310
|
+
red: 'instagram';
|
|
311
|
+
pageAccessToken: string;
|
|
312
|
+
igUserId: string;
|
|
313
|
+
igUsername: string;
|
|
314
|
+
}
|
|
315
|
+
/** ⚠️ Igual: el de página. */
|
|
316
|
+
| {
|
|
317
|
+
red: 'facebook';
|
|
318
|
+
pageAccessToken: string;
|
|
319
|
+
pageId: string;
|
|
320
|
+
pageName: string;
|
|
321
|
+
};
|
|
322
|
+
/** La sesión de UNA red, para que el proveedor no tenga que estrechar. */
|
|
323
|
+
export type SesionDe<R extends RedSocial> = Extract<SesionSocial, {
|
|
324
|
+
red: R;
|
|
325
|
+
}>;
|
|
326
|
+
/**
|
|
327
|
+
* Un canal de Discord, tal y como lo enseña el selector del trigger.
|
|
328
|
+
*
|
|
329
|
+
* ⚠️ Vive en el contrato y no en `credentials.service.ts` porque los dos lados
|
|
330
|
+
* lo necesitan: quien lo produce está en `hw-credentials` y quien lo consume en
|
|
331
|
+
* `nodes/`. Tenerlo en el servicio obligaba al consumidor a importar las 4.270
|
|
332
|
+
* líneas del dominio para nombrar diez campos.
|
|
333
|
+
*
|
|
334
|
+
* `type` es el entero de Discord (0=texto, 2=voz, 4=categoría, 5=anuncios,
|
|
335
|
+
* 10/11/12=hilos, 13=stage, 15=foro); el panel ramifica sobre él para filtrar
|
|
336
|
+
* y elegir icono.
|
|
337
|
+
*/
|
|
338
|
+
export interface DiscordChannelSummary {
|
|
339
|
+
id: string;
|
|
340
|
+
name: string;
|
|
341
|
+
type: number;
|
|
342
|
+
parentId: string | null;
|
|
343
|
+
position: number;
|
|
344
|
+
topic: string | null;
|
|
345
|
+
guildId: string;
|
|
346
|
+
guildName: string;
|
|
347
|
+
guildIcon: string | null;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Los proveedores cuyo token se refresca con el `client_secret` de nuestra app.
|
|
351
|
+
*
|
|
352
|
+
* ⚠️ Una unión cerrada y no una cadena. Con `string`, un proveedor mal escrito
|
|
353
|
+
* llega hasta el gateway y vuelve como «no existe» en tiempo de ejecución; así
|
|
354
|
+
* no compila. Crece cuando se cierre slack y atlassian, que tienen más
|
|
355
|
+
* superficie que un refresco.
|
|
356
|
+
*/
|
|
357
|
+
/**
|
|
358
|
+
* Los proveedores cuyo access token vive detrás del `client_secret` de nuestra
|
|
359
|
+
* app y hay que pedirlo, no descifrarlo.
|
|
360
|
+
*
|
|
361
|
+
* ⚠️ Distinto de `ProveedorConRefresco`: allí el llamante YA tiene un token y
|
|
362
|
+
* pide su sustituto; aquí no tiene ninguno y pide uno vivo. Son dos verbos
|
|
363
|
+
* distintos y por eso son dos uniones y dos métodos.
|
|
364
|
+
*/
|
|
365
|
+
export type ProveedorConToken = 'slack' | 'atlassian';
|
|
366
|
+
export type ProveedorConRefresco = 'notion' | 'shopify';
|
|
367
|
+
export interface CredencialesDelGateway {
|
|
368
|
+
/**
|
|
369
|
+
* Los metadatos de una credencial de la organización.
|
|
370
|
+
*
|
|
371
|
+
* ⚠️ **Lanza `NotFoundException` si no existe**, igual que hoy. No devuelve
|
|
372
|
+
* `null`: hay un llamante —`common/discord-operations.service.ts`— que
|
|
373
|
+
* envuelve esta llamada en un `.catch(onFailureReturn(...))` a propósito.
|
|
374
|
+
* Convertir el fallo en `null` dejaría ese `catch` sin nada que atrapar y
|
|
375
|
+
* el resto de llamantes sin error donde hoy lo hay.
|
|
376
|
+
*/
|
|
377
|
+
describir(id: string, orgId: string): Promise<DescripcionDeCredencial>;
|
|
378
|
+
/**
|
|
379
|
+
* El secreto, y el tipo con el que interpretarlo.
|
|
380
|
+
*
|
|
381
|
+
* ⚠️ Devuelve los DOS juntos a propósito. El patrón real, idéntico en 28
|
|
382
|
+
* ficheros, es `findOne` para el tipo → ramificar → descifrar. Devolverlos
|
|
383
|
+
* juntos quita un viaje de red por ejecución cuando esta costura sea HTTP.
|
|
384
|
+
*
|
|
385
|
+
* ⚠️ `tipoEsperado` NO es una comodidad: es dónde se comprueba.
|
|
386
|
+
*
|
|
387
|
+
* Muchos llamantes hacen hoy `findOne` → «¿es del tipo que espero?» → lanzar
|
|
388
|
+
* → y sólo entonces descifrar. Ese orden importa y se pierde si el contrato
|
|
389
|
+
* trae las dos cosas de un tirón: una credencial del tipo equivocado
|
|
390
|
+
* acabaría descifrada igual, con su fila en el registro de accesos, para
|
|
391
|
+
* luego tirarla. Pasándole el tipo, la comprobación se hace **en el
|
|
392
|
+
* gateway y antes de descifrar**, así que la que no toca no se descifra
|
|
393
|
+
* nunca. Es mejor que hoy, no igual.
|
|
394
|
+
*/
|
|
395
|
+
material(id: string, orgId: string, quien: QuienPide, purpose: string, tipoEsperado?: CredentialType | readonly CredentialType[]): Promise<MaterialDeCredencial>;
|
|
396
|
+
/**
|
|
397
|
+
* Apunta que la credencial se usó. Para la columna «último uso» del panel.
|
|
398
|
+
*
|
|
399
|
+
* ⚠️ Devuelve una promesa que los llamantes NO esperan: todos hacen
|
|
400
|
+
* `.catch(…)` y siguen. Que fallar en apuntar el uso tumbe una publicación
|
|
401
|
+
* en Mastodon sería absurdo, y así está hoy.
|
|
402
|
+
*/
|
|
403
|
+
marcarUsada(id: string): Promise<void>;
|
|
404
|
+
/**
|
|
405
|
+
* Los canales que el bot ve, para el selector del trigger de Discord.
|
|
406
|
+
*
|
|
407
|
+
* ⚠️ Esto es un LISTADO, no un secreto, y por eso no lleva `purpose`: no
|
|
408
|
+
* devuelve material que nadie pueda usar para autenticarse. Sí lleva `quien`,
|
|
409
|
+
* porque el token del bot con el que se pregunta SÍ es de alguien y la
|
|
410
|
+
* pregunta se hace en su nombre.
|
|
411
|
+
*
|
|
412
|
+
* 🔥 Y por eso no vale «que el nodo pida el token y liste él». Ese token abre
|
|
413
|
+
* el servidor entero; lo que el nodo necesita es la lista de canales. Sacar
|
|
414
|
+
* el secreto para derivar una lista es justo lo que este contrato existe para
|
|
415
|
+
* no hacer.
|
|
416
|
+
*/
|
|
417
|
+
canalesDeDiscord(id: string, orgId: string, quien: QuienPide): Promise<DiscordChannelSummary[]>;
|
|
418
|
+
/**
|
|
419
|
+
* Un access token fresco de un proveedor cuyo `client_secret` vive en el
|
|
420
|
+
* gateway.
|
|
421
|
+
*
|
|
422
|
+
* 🔥 Por esto los cinco servicios de OAuth NO se mudan. Refrescar necesita el
|
|
423
|
+
* secreto de NUESTRA app, y ése no puede salir a `hw-nodes`: si saliera, cada
|
|
424
|
+
* nodo tendría con qué hacerse pasar por la aplicación entera. Lo que cruza
|
|
425
|
+
* es el token ya refrescado, que sirve para una cuenta y caduca.
|
|
426
|
+
*
|
|
427
|
+
* ⚠️ `staleToken` no es un detalle: es el token que ACABA de recibir un 401.
|
|
428
|
+
* Distinguirlo del guardado es lo que evita gastar una rotación cuando otro
|
|
429
|
+
* proceso ya refrescó — y en Shopify las rotaciones son finitas.
|
|
430
|
+
*
|
|
431
|
+
* Devuelve `null` cuando no hay nada que refrescar (una app custom, un token
|
|
432
|
+
* que no caduca, una credencial que ya no existe). `null` es una respuesta,
|
|
433
|
+
* no un fallo: el llamante sigue con el token que tenía.
|
|
434
|
+
*/
|
|
435
|
+
refrescarTokenDeProveedor(proveedor: ProveedorConRefresco, params: {
|
|
436
|
+
credentialId: string;
|
|
437
|
+
orgId: string;
|
|
438
|
+
staleToken: string;
|
|
439
|
+
}): Promise<string | null>;
|
|
440
|
+
/**
|
|
441
|
+
* Apaga las credenciales de una tienda que desinstaló la app.
|
|
442
|
+
*
|
|
443
|
+
* ⚠️ La tienda sale de la CREDENCIAL, nunca de la cabecera del webhook: si
|
|
444
|
+
* no, la cabecera decidiría a quién se le apaga la conexión. Devuelve cuántas
|
|
445
|
+
* se marcaron, que es lo que el llamante apunta en telemetría.
|
|
446
|
+
*/
|
|
447
|
+
marcarTiendaDesinstalada(shop: string, orgId: string): Promise<number>;
|
|
448
|
+
/**
|
|
449
|
+
* Un access token VIVO de Slack o Atlassian, refrescado si hacía falta.
|
|
450
|
+
*
|
|
451
|
+
* ⚠️ Lleva `quien` porque esto SÍ es una lectura: entrega al llamante una
|
|
452
|
+
* credencial usable, y esa entrega tiene que quedar en el registro de
|
|
453
|
+
* accesos.
|
|
454
|
+
*
|
|
455
|
+
* 🔥 Y hasta ahora no quedaba. Estos dos servicios se saltan `getDecrypted`
|
|
456
|
+
* a propósito —necesitan el documento crudo para guardar el token
|
|
457
|
+
* refrescado— y sólo sellaban `lastUsedAt`. O sea que cada resolución de
|
|
458
|
+
* token de Slack o Jira era INVISIBLE para el registro, con `quien` o sin él.
|
|
459
|
+
* El adaptador lo apunta ahora explícitamente.
|
|
460
|
+
*/
|
|
461
|
+
tokenDeProveedor(proveedor: ProveedorConToken, id: string, orgId: string, quien: QuienPide): Promise<string>;
|
|
462
|
+
/**
|
|
463
|
+
* Los datos del espacio de Slack conectado.
|
|
464
|
+
*
|
|
465
|
+
* ⚠️ SIN `quien`, y no por descuido: no devuelve nada con lo que
|
|
466
|
+
* autenticarse. El id del equipo y el nombre del espacio son los mismos que
|
|
467
|
+
* el panel ya enseña en la tarjeta de la credencial.
|
|
468
|
+
*/
|
|
469
|
+
espacioDeSlack(id: string, orgId: string): Promise<{
|
|
470
|
+
teamId: string;
|
|
471
|
+
teamName: string;
|
|
472
|
+
botUserId: string;
|
|
473
|
+
scopes: string[];
|
|
474
|
+
}>;
|
|
475
|
+
/**
|
|
476
|
+
* Un installation token de la GitHub App, ya acuñado.
|
|
477
|
+
*
|
|
478
|
+
* 🔥 La llave privada de una GitHub App es el secreto más gordo que tenemos:
|
|
479
|
+
* con ella se firma un JWT como la aplicación y se acuñan tokens para
|
|
480
|
+
* CUALQUIER instalación — todas las de todos los clientes. Lo que sale de
|
|
481
|
+
* aquí es un token de UNA instalación que caduca en una hora.
|
|
482
|
+
*
|
|
483
|
+
* ⚠️ Esto no existía y el api tenía la llave. Era una incoherencia con Slack
|
|
484
|
+
* y Atlassian, que sí pasaron por el contrato con este mismo razonamiento,
|
|
485
|
+
* y encima con el secreto más peligroso de los tres. Ver el PR que lo trajo.
|
|
486
|
+
*
|
|
487
|
+
* Y de paso el api deja de ver el blob descifrado: antes pedía `material` y
|
|
488
|
+
* se lo pasaba a `tokenFor`. Ahora no pide ni el secreto ni la llave.
|
|
489
|
+
*/
|
|
490
|
+
tokenDeInstalacionDeGithub(id: string, orgId: string, quien: QuienPide): Promise<string>;
|
|
491
|
+
/**
|
|
492
|
+
* Las credenciales de una organización, para que el agente pueda nombrarlas.
|
|
493
|
+
*
|
|
494
|
+
* ⚠️ `id`, `name` y `type`, y NADA más. El llamante hacía
|
|
495
|
+
* `.select('name type')` contra el modelo — la barrera estaba en la consulta
|
|
496
|
+
* y no en el serializador, a propósito, para que no dependiera de que nadie
|
|
497
|
+
* rompiera un `toJSON`. Aquí la barrera es el tipo: `encryptedData` y
|
|
498
|
+
* `metadata` (host, correo, URL del servidor…) no existen en la respuesta.
|
|
499
|
+
*/
|
|
500
|
+
credencialesDeLaOrganizacion(orgId: string): Promise<Array<{
|
|
501
|
+
id: string;
|
|
502
|
+
name: string;
|
|
503
|
+
type: string;
|
|
504
|
+
}>>;
|
|
505
|
+
/**
|
|
506
|
+
* Qué carpetas de credencial ve esta persona, y cuántas hay en total.
|
|
507
|
+
*
|
|
508
|
+
* ⚠️ Los dos números juntos porque el llamante necesita los dos y por una
|
|
509
|
+
* razón concreta: si ve TODAS, no hay que filtrar nada y se ahorra el filtro
|
|
510
|
+
* entero. Pedirlos por separado eran dos viajes y una carrera entre ellos.
|
|
511
|
+
*
|
|
512
|
+
* 🔥 Y quita un acceso CRUDO a la colección: `canvas` contaba con
|
|
513
|
+
* `connection.collection('credentialfolders')`. Eso es el gateway leyendo
|
|
514
|
+
* una tabla que ya no es suya.
|
|
515
|
+
*/
|
|
516
|
+
carpetasDeCredencialAccesibles(userId: string, orgId: string): Promise<{
|
|
517
|
+
total: number;
|
|
518
|
+
accesibles: string[];
|
|
519
|
+
}>;
|
|
520
|
+
/**
|
|
521
|
+
* Las credenciales que el lienzo enseña, ya recortadas.
|
|
522
|
+
*
|
|
523
|
+
* 🔥 Esto NO entraba por un import, y por eso se me escapó tres veces: el
|
|
524
|
+
* lienzo barre un mapa de nombres de colección y `credentials` era una
|
|
525
|
+
* entrada más. Medir por imports no lo veía.
|
|
526
|
+
*
|
|
527
|
+
* ⚠️ Y lo importante no es de dónde salen los datos, es **quién decide qué se
|
|
528
|
+
* esconde**. Hoy lo decidía el gateway: aplicaba una proyección y luego
|
|
529
|
+
* recortaba `metadata` de las credenciales sensibles. Esa regla es del dueño
|
|
530
|
+
* del dato, y el dueño es este contrato. Dos sitios decidiendo qué es
|
|
531
|
+
* sensible es uno que se queda atrás.
|
|
532
|
+
*
|
|
533
|
+
* `carpetas` es la lista de carpetas que la persona puede abrir, o `null` si
|
|
534
|
+
* las ve todas. `folderId: null` —«Sin carpeta»— es visible para cualquiera.
|
|
535
|
+
*/
|
|
536
|
+
credencialesParaElLienzo(orgId: string, carpetas: string[] | null): Promise<Array<Record<string, unknown>>>;
|
|
537
|
+
/**
|
|
538
|
+
* Refresca el token de LA APP de Atlassian, la del Marketplace.
|
|
539
|
+
*
|
|
540
|
+
* ⚠️ No es la credencial de nadie: `atlassian-privacy` tiene su propia
|
|
541
|
+
* colección, con el token que HostWebhook usa para las llamadas de app que
|
|
542
|
+
* Atlassian exige (borrado de datos personales). Sólo reusaba el intercambio
|
|
543
|
+
* OAuth, que necesita el `client_secret` de nuestra app.
|
|
544
|
+
*
|
|
545
|
+
* 🔥 Y por eso pasa por aquí, como Slack, Shopify y la GitHub App: lo que
|
|
546
|
+
* cruza es el token refrescado, que vale para una cuenta y caduca. El
|
|
547
|
+
* `client_secret` —que sirve para cualquiera— no sale de este servicio.
|
|
548
|
+
*
|
|
549
|
+
* Devuelve `null` si no hay nada que refrescar; el llamante sigue con lo que
|
|
550
|
+
* tenía en vez de quedarse sin token.
|
|
551
|
+
*/
|
|
552
|
+
refrescarTokenDeAppDeAtlassian(oauth: Record<string, unknown>): Promise<Record<string, unknown> | null>;
|
|
553
|
+
/** El sitio de Atlassian conectado. Mismo criterio: metadatos, no secreto. */
|
|
554
|
+
sitioDeAtlassian(id: string, orgId: string): Promise<{
|
|
555
|
+
cloudId: string;
|
|
556
|
+
siteUrl: string;
|
|
557
|
+
siteName: string;
|
|
558
|
+
}>;
|
|
559
|
+
/** La URL de webhook entrante que Slack dio al conectar, si la hay. */
|
|
560
|
+
urlDeWebhookDeSlack(id: string, orgId: string): Promise<string | null>;
|
|
561
|
+
/**
|
|
562
|
+
* Los ids de las credenciales de un espacio de Slack.
|
|
563
|
+
*
|
|
564
|
+
* 🔥 Ids y organización, NO documentos. El método del gateway devuelve
|
|
565
|
+
* `CredentialDocument[]` —con el blob cifrado dentro— y los llamantes usan el
|
|
566
|
+
* `_id` y, en un sitio, el `organizationId`. Mandar el documento por la red
|
|
567
|
+
* habría sacado el secreto de todo un espacio para no usarlo.
|
|
568
|
+
*
|
|
569
|
+
* ⚠️ La primera versión devolvía sólo `string[]` y `tsc` la tumbó: un tercer
|
|
570
|
+
* llamante emite telemetría POR ORGANIZACIÓN. Medir dos usos y no el tercero
|
|
571
|
+
* es cómo un contrato nace ya corto.
|
|
572
|
+
*
|
|
573
|
+
* ⚠️ Y sin `orgId`, que es lo raro y es correcto: el evento entrante de Slack
|
|
574
|
+
* sólo trae `team_id`, y de ESO se deduce la organización. Es la única
|
|
575
|
+
* búsqueda del contrato que va al revés.
|
|
576
|
+
*/
|
|
577
|
+
credencialesDeEquipoDeSlack(teamId: string): Promise<Array<{
|
|
578
|
+
id: string;
|
|
579
|
+
organizationId: string;
|
|
580
|
+
}>>;
|
|
581
|
+
/**
|
|
582
|
+
* Las credenciales de un tipo, para elegir cuenta.
|
|
583
|
+
*
|
|
584
|
+
* 🔥 Sólo `id` y `name`, que es lo único que el llamante usa. El método del
|
|
585
|
+
* gateway devuelve la página entera de documentos —con `encryptedData`
|
|
586
|
+
* dentro— y quien la pedía hacía `data.map((c) => ({ id, name }))` y tiraba
|
|
587
|
+
* el resto. Por la red eso habría sacado hasta 50 secretos para enseñar una
|
|
588
|
+
* lista de nombres.
|
|
589
|
+
*
|
|
590
|
+
* ⚠️ Sin `quien` y sin `purpose`: no descifra nada. Es la lista que el panel
|
|
591
|
+
* ya enseña.
|
|
592
|
+
*/
|
|
593
|
+
credencialesDeTipo(orgId: string, tipo: CredentialType): Promise<Array<{
|
|
594
|
+
id: string;
|
|
595
|
+
name: string;
|
|
596
|
+
}>>;
|
|
597
|
+
/**
|
|
598
|
+
* La configuración con la que el AI Node habla con un servidor MCP.
|
|
599
|
+
*
|
|
600
|
+
* ⚠️ Éste SÍ es método del contrato y `configDeOllama` no, aunque se
|
|
601
|
+
* parezcan. Ollama es derivación pura del material —parsear y validar— y se
|
|
602
|
+
* hace del lado del nodo. Éste, cuando el blob trae `oauth`, delega en
|
|
603
|
+
* `McpOAuthService` para REFRESCAR el token, y eso necesita el secreto de la
|
|
604
|
+
* app, que no sale del gateway. Derivar no es refrescar: es la misma
|
|
605
|
+
* distinción que costó cara en Google.
|
|
606
|
+
*/
|
|
607
|
+
configDeMcp(id: string, orgId: string, quien: QuienPide, purpose: string): Promise<{
|
|
608
|
+
url: string;
|
|
609
|
+
authHeader?: string;
|
|
610
|
+
}>;
|
|
611
|
+
/**
|
|
612
|
+
* La sesión mínima con la que un nodo social puede llamar a su API.
|
|
613
|
+
*
|
|
614
|
+
* ⚠️ `red` NO es una comodidad: es el `tipoEsperado` de `material()`. El
|
|
615
|
+
* gateway lo traduce al `CredentialType` que toca y comprueba ANTES de
|
|
616
|
+
* descifrar, así que la credencial equivocada no se descifra ni deja fila en
|
|
617
|
+
* el registro. Y es lo que estrecha el tipo de vuelta.
|
|
618
|
+
*
|
|
619
|
+
* ⚠️ El nombre dice «sesión» y no «token» a propósito. Sólo en X hay trabajo
|
|
620
|
+
* de refresco aquí dentro —2 h de vida, rotación con el `client_secret` de
|
|
621
|
+
* HostWebhook, y la rotación se PERSISTE—. En las otras seis esto devuelve
|
|
622
|
+
* lo guardado, estrechado: si el token está muerto, la primera señal es el
|
|
623
|
+
* 401 de la API. Threads y Meta los renuevan crones del gateway, fuera de
|
|
624
|
+
* aquí.
|
|
625
|
+
*
|
|
626
|
+
* ⚠️ NO cachear el resultado del lado del nodo. El cron de Meta reescribe el
|
|
627
|
+
* blob por debajo y `setAuthor`/`refreshOrganizations` de LinkedIn cambian
|
|
628
|
+
* el `authorUrn` que el despacho lee. Este contrato no tiene invalidación.
|
|
629
|
+
*/
|
|
630
|
+
sesionSocial<R extends RedSocial>(red: R, id: string, orgId: string, quien: QuienPide, purpose: string): Promise<SesionDe<R>>;
|
|
631
|
+
/**
|
|
632
|
+
* Un access token de Google, ya refrescado.
|
|
633
|
+
*
|
|
634
|
+
* ⚠️ Esto reemplaza a `getGoogleAuthClient`, que devolvía un `OAuth2Client`
|
|
635
|
+
* VIVO. Ese objeto no cruza una red — pero tampoco hace falta que cruce: el
|
|
636
|
+
* método original son doce líneas que piden un token y hacen
|
|
637
|
+
* `new google.auth.OAuth2()`. La fábrica se queda del lado del nodo
|
|
638
|
+
* (`clienteDeGoogle`, aquí al lado) y lo que viaja es la cadena.
|
|
639
|
+
*/
|
|
640
|
+
tokenDeGoogle(id: string, orgId: string, quien: QuienPide, purpose: string): Promise<string>;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Traduce `QuienPide` a lo que el registro de accesos entiende hoy.
|
|
644
|
+
*
|
|
645
|
+
* ⚠️ Y comprueba el id vacío, que es el agujero que el tipo no puede cerrar.
|
|
646
|
+
* Devuelve también si la atribución es utilizable, para que el adaptador lo
|
|
647
|
+
* apunte en vez de dejarlo pasar callando.
|
|
648
|
+
*/
|
|
649
|
+
export declare function comoAtribuir(quien: QuienPide): {
|
|
650
|
+
relatedEntity?: {
|
|
651
|
+
type: string;
|
|
652
|
+
id: string;
|
|
653
|
+
};
|
|
654
|
+
userId?: string;
|
|
655
|
+
sourceIp?: string;
|
|
656
|
+
motivo?: MotivoSinComprobar;
|
|
657
|
+
sirve: boolean;
|
|
658
|
+
};
|
|
659
|
+
/** ⚠️ Cadena y no `Symbol`, igual que el resto de tokens de la Fase 2. */
|
|
660
|
+
/**
|
|
661
|
+
* La credencial existe, pero no es del tipo que el llamante pide.
|
|
662
|
+
*
|
|
663
|
+
* ⚠️ Extiende `BadRequestException` A PROPÓSITO: mismo estado y mismo texto que
|
|
664
|
+
* lanzaba el adaptador, así que las familias que YA pasan `tipoEsperado` —mongo,
|
|
665
|
+
* postgres, las memorias del AI Node— no cambian ni una letra. Lo que añade es
|
|
666
|
+
* un `instanceof` con el que un llamante puede volver a poner SU mensaje.
|
|
667
|
+
*
|
|
668
|
+
* ## Por qué hacía falta
|
|
669
|
+
*
|
|
670
|
+
* `tipoEsperado` no es una comodidad: es dónde se comprueba, y comprobar en el
|
|
671
|
+
* gateway es lo que evita descifrar una credencial que se va a rechazar. Pero
|
|
672
|
+
* los helpers de Mailchimp y Shopify lanzaban un `ForbiddenException` con un
|
|
673
|
+
* mensaje accionable en inglés —«Connect a Shopify store from Settings →
|
|
674
|
+
* Credentials»— que SALE POR LA API al usuario. Pasar `tipoEsperado` cambiaba
|
|
675
|
+
* el mensaje y el código HTTP; no pasarlo obligaba a descifrar primero, que es
|
|
676
|
+
* peor que hoy. Con el `instanceof`, el llamante recupera su mensaje sin
|
|
677
|
+
* renunciar a la comprobación temprana.
|
|
678
|
+
*
|
|
679
|
+
* ⚠️ Lo que NUNCA hay que hacer es olfatear la prosa del mensaje con un regex:
|
|
680
|
+
* el texto del adaptador no es contrato, y el día que se reescriba, el `catch`
|
|
681
|
+
* deja de reconocerlo EN SILENCIO.
|
|
682
|
+
*
|
|
683
|
+
* ⚠️ Y `instanceof` no cruza HTTP. El día que esta costura sea red, la clase
|
|
684
|
+
* hay que rehidratarla desde el código de estado en el cliente del transporte.
|
|
685
|
+
* Es UN sitio y está a la vista; olfatear la prosa serían N escondidos.
|
|
686
|
+
*/
|
|
687
|
+
/**
|
|
688
|
+
* 🔥 El discriminador que hace que la clase sobreviva al cable.
|
|
689
|
+
*
|
|
690
|
+
* `instanceof` no cruza HTTP, y el código de estado tampoco basta: esta
|
|
691
|
+
* excepción es un **400**, igual que la validación de entrada y media docena
|
|
692
|
+
* de errores más de hw-credentials. Rehidratar «por el 400» convertiría
|
|
693
|
+
* cualquier petición mal formada en «la credencial es de otro tipo».
|
|
694
|
+
*
|
|
695
|
+
* ⚠️ Y lo que NUNCA hay que hacer es olfatear la prosa del mensaje: el texto
|
|
696
|
+
* no es contrato, y el día que se reescriba el `catch` deja de reconocerlo EN
|
|
697
|
+
* SILENCIO. Por eso va un campo propio en el CUERPO.
|
|
698
|
+
*/
|
|
699
|
+
export declare const MOTIVO_TIPO_EQUIVOCADO = "credencial-de-tipo-equivocado";
|
|
700
|
+
export declare class CredencialDeTipoEquivocado extends BadRequestException {
|
|
701
|
+
readonly credentialId: string;
|
|
702
|
+
readonly tipoReal: string;
|
|
703
|
+
readonly tiposEsperados: readonly string[];
|
|
704
|
+
constructor(credentialId: string, tipoReal: string, tiposEsperados: readonly string[]);
|
|
705
|
+
}
|
|
706
|
+
export declare const CREDENCIALES_DEL_GATEWAY = "CREDENCIALES_DEL_GATEWAY";
|
|
707
|
+
/**
|
|
708
|
+
* El túnel por el que hay que salir para esta credencial, si lo hay.
|
|
709
|
+
*
|
|
710
|
+
* ⚠️ Deliberadamente NO es un método del contrato. `getTunnelIdForCredential`
|
|
711
|
+
* lo era en el gateway, y al medirlo resultó ser `metadata.tunnelId` y nada
|
|
712
|
+
* más — y `describir()` ya trae `metadata`. Añadir un método habría sido una
|
|
713
|
+
* llamada de red por cada consulta para leer un campo que ya venía en la
|
|
714
|
+
* anterior.
|
|
715
|
+
*
|
|
716
|
+
* Vive aquí y no repetido en los cinco sitios que lo derivan porque «leer un
|
|
717
|
+
* campo de un mapa» es exactamente donde una copia se queda con el nombre
|
|
718
|
+
* viejo el día que el campo cambie.
|
|
719
|
+
*/
|
|
720
|
+
export declare function tunelDe(cred: {
|
|
721
|
+
metadata?: Record<string, unknown>;
|
|
722
|
+
}): string | null;
|