From 7adb8de0aa09ac14d2f49a1273d830d840fafca8 Mon Sep 17 00:00:00 2001 From: Samuel Bodin <1637651+bodinsamuel@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:40:27 +0100 Subject: [PATCH] fix(sdk): expose getIntegration() (#3080) ## Changes - Expose `getIntegration()` in the SDK The shape of the response is determined by the include, which is maybe a bad idea I never know. On one hand it's pretty standard, on the other it makes caching and types hard :\ --- .../model.service.unit.test.ts.snap | 16 +++---- packages/node-client/lib/index.ts | 6 ++- packages/shared/lib/sdk/sync.ts | 48 +++++++++---------- 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/packages/cli/lib/services/__snapshots__/model.service.unit.test.ts.snap b/packages/cli/lib/services/__snapshots__/model.service.unit.test.ts.snap index 2eafad8f53..da0b6169ac 100644 --- a/packages/cli/lib/services/__snapshots__/model.service.unit.test.ts.snap +++ b/packages/cli/lib/services/__snapshots__/model.service.unit.test.ts.snap @@ -42,9 +42,9 @@ exports[`buildModelTs > should return empty (with sdk) 1`] = ` // ------ SDK import { Nango } from '@nangohq/node'; -import type { AxiosInstance, AxiosInterceptorManager, AxiosRequestConfig } from 'axios'; +import type { AxiosInstance, AxiosInterceptorManager, AxiosRequestConfig, AxiosResponse } from 'axios'; import type { SyncConfig } from '../models/Sync.js'; -import type { DBTeam, RunnerFlags } from '@nangohq/types'; +import type { DBTeam, GetPublicIntegration, RunnerFlags } from '@nangohq/types'; export declare const oldLevelToNewLevel: { readonly debug: "debug"; readonly info: "info"; @@ -76,14 +76,6 @@ interface ParamsSerializerOptions extends SerializerOptions { encode?: ParamEncoder; serialize?: CustomParamsSerializer; } -export interface AxiosResponse { - data: T; - status: number; - statusText: string; - headers: any; - config: D; - request?: any; -} interface Pagination { type: string; limit?: number; @@ -366,6 +358,10 @@ export declare class NangoAction { patch(config: Omit): Promise>; delete(config: Omit): Promise>; getToken(): Promise; + /** + * Get current integration + */ + getIntegration(queries?: GetPublicIntegration['Querystring']): Promise; getConnection(providerConfigKeyOverride?: string, connectionIdOverride?: string): Promise; setMetadata(metadata: Metadata): Promise>; updateMetadata(metadata: Metadata): Promise>; diff --git a/packages/node-client/lib/index.ts b/packages/node-client/lib/index.ts index 9e8f008ac7..31e1e715ea 100644 --- a/packages/node-client/lib/index.ts +++ b/packages/node-client/lib/index.ts @@ -198,15 +198,17 @@ export class Nango { params: string | GetPublicIntegration['Params'], queries?: boolean | GetPublicIntegration['Querystring'] ): Promise<{ config: Integration | IntegrationWithCreds } | GetPublicIntegration['Success']> { + const headers = { 'Content-Type': 'application/json' }; + if (typeof params === 'string') { const url = `${this.serverUrl}/config/${params}`; - const response = await this.http.get(url, { headers: this.enrichHeaders({}), params: { include_creds: queries } }); + const response = await this.http.get(url, { headers: this.enrichHeaders(headers), params: { include_creds: queries } }); return response.data; } else { const url = new URL(`${this.serverUrl}/integrations/${params.uniqueKey}`); addQueryParams(url, queries as GetPublicIntegration['Querystring']); - const response = await this.http.get(url.href, { headers: this.enrichHeaders({}) }); + const response = await this.http.get(url.href, { headers: this.enrichHeaders(headers) }); return response.data; } } diff --git a/packages/shared/lib/sdk/sync.ts b/packages/shared/lib/sdk/sync.ts index 0218de2b6d..a765576123 100644 --- a/packages/shared/lib/sdk/sync.ts +++ b/packages/shared/lib/sdk/sync.ts @@ -3,7 +3,7 @@ import { Nango, getUserAgent } from '@nangohq/node'; import type { AdminAxiosProps } from '@nangohq/node'; import paginateService from '../services/paginate.service.js'; import proxyService from '../services/proxy.service.js'; -import type { AxiosInstance, AxiosInterceptorManager, AxiosRequestConfig } from 'axios'; +import type { AxiosInstance, AxiosInterceptorManager, AxiosRequestConfig, AxiosResponse } from 'axios'; import axios, { AxiosError } from 'axios'; import { getPersistAPIUrl } from '../utils/utils.js'; import type { UserProvidedProxyConfiguration } from '../models/Proxy.js'; @@ -90,15 +90,6 @@ interface ParamsSerializerOptions extends SerializerOptions { serialize?: CustomParamsSerializer; } -export interface AxiosResponse { - data: T; - status: number; - statusText: string; - headers: any; - config: D; - request?: any; -} - interface UserLogParameters { level?: LogLevel; } @@ -407,6 +398,7 @@ export interface EnvironmentVariable { } const MEMOIZED_CONNECTION_TTL = 60000; +const MEMOIZED_INTEGRATION_TTL = 10 * 60 * 1000; const RECORDS_VALIDATION_SAMPLE = 5; export const defaultPersistApi = axios.create({ @@ -442,11 +434,8 @@ export class NangoAction { public ActionError = ActionError; - private memoizedConnections: Map = new Map< - string, - { connection: Connection; timestamp: number } - >(); - private memoizedIntegration: GetPublicIntegration['Success']['data'] | undefined; + private memoizedConnections = new Map(); + private memoizedIntegration = new Map(); constructor(config: NangoProps, { persistApi }: { persistApi: AxiosInstance } = { persistApi: defaultPersistApi }) { this.connectionId = config.connectionId; @@ -658,6 +647,23 @@ export class NangoAction { return this.nango.getToken(this.providerConfigKey, this.connectionId); } + /** + * Get current integration + */ + public async getIntegration(queries?: GetPublicIntegration['Querystring']): Promise { + this.throwIfAborted(); + + const key = queries?.include?.join(',') || 'default'; + const has = this.memoizedIntegration.get(key); + if (has && MEMOIZED_INTEGRATION_TTL > Date.now() - has.timestamp) { + return has.integration; + } + + const { data: integration } = await this.nango.getIntegration({ uniqueKey: this.providerConfigKey }, queries); + this.memoizedIntegration.set(key, { integration, timestamp: Date.now() }); + return integration; + } + public async getConnection(providerConfigKeyOverride?: string, connectionIdOverride?: string): Promise { this.throwIfAborted(); @@ -709,16 +715,8 @@ export class NangoAction { public async getWebhookURL(): Promise { this.throwIfAborted(); - if (this.memoizedIntegration) { - return this.memoizedIntegration.webhook_url; - } - - const { data: integration } = await this.nango.getIntegration({ uniqueKey: this.providerConfigKey }, { include: ['webhook'] }); - if (!integration || !integration.provider) { - throw Error(`There was no provider found for the provider config key: ${this.providerConfigKey}`); - } - this.memoizedIntegration = integration; - return this.memoizedIntegration.webhook_url; + const integration = await this.getIntegration({ include: ['webhook'] }); + return integration.webhook_url; } /**