Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refresh connection flow #157

Merged
merged 30 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9fcfcd8
adding cf to gitignore
pellicceama Dec 14, 2024
5a3525f
adding perform connection check to pipelineRouter
pellicceama Dec 15, 2024
cddefc1
WIP
pellicceama Dec 15, 2024
81dc8da
comments
pellicceama Dec 16, 2024
46c3e5a
abstracting connection error
pellicceama Dec 16, 2024
b1ee617
merging main
pellicceama Dec 22, 2024
ac613ba
WIP
pellicceama Dec 22, 2024
3c5748a
adding error status and message
pellicceama Dec 23, 2024
0594e72
Adding reconnection to connection card
pellicceama Dec 23, 2024
888cb5f
WIP FE changes
pellicceama Dec 23, 2024
498b5d6
merging main
pellicceama Dec 23, 2024
4e29f2a
adding spinner for reconnect
pellicceama Dec 23, 2024
24a713d
making buttons clickable
pellicceama Dec 24, 2024
16bcdf3
only setting errors if they exist
pellicceama Dec 24, 2024
b80e77d
bringing back refresh on add
pellicceama Dec 24, 2024
4e1da75
Merge branch 'main' of github.com:openintegrations/openint into refre…
pellicceama Dec 24, 2024
d7b6d4b
renames and cleanups
pellicceama Dec 24, 2024
5b7928a
tightening types
pellicceama Dec 24, 2024
1050ca7
improving parse and removing settings from being returned
pellicceama Dec 24, 2024
5522ffe
improving loadings
pellicceama Dec 24, 2024
369ef62
Merge branch 'main' of github.com:openintegrations/openint into refre…
pellicceama Dec 26, 2024
b556bb5
merging main
pellicceama Dec 26, 2024
e08a7d1
making display name optional in docs
pellicceama Dec 26, 2024
62c7b64
updating settings on postConnect to clear any errors
pellicceama Dec 26, 2024
12d72c5
casting
pellicceama Dec 26, 2024
7216433
restricting to oauth
pellicceama Dec 26, 2024
592742d
lints
pellicceama Dec 26, 2024
922c59f
improving refreshes
pellicceama Dec 27, 2024
11c3037
Merge branch 'main' of github.com:openintegrations/openint into refre…
pellicceama Dec 30, 2024
0afc98a
cleaning up reset flow
pellicceama Dec 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
# testing
/coverage

# Cloudflare
.dev.vars
pellicceama marked this conversation as resolved.
Show resolved Hide resolved
.open-next
.wrangler

# next.js
/.next/
/out/
Expand Down
10 changes: 5 additions & 5 deletions kits/cdk/base-links.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {
AnyEntityPayload,
ConnectionUpdateData,
Link,
ResoUpdateData,
StateUpdateData,
SyncOperation,
} from '@openint/sync'
Expand All @@ -13,11 +13,11 @@ type OperationType = SyncOperation['type']
export type OpHandlers<
TRet,
T = any,
TResoUpdate extends object = ResoUpdateData,
TConnUpdate extends object = ConnectionUpdateData,
TStateUpdate extends object = StateUpdateData,
> = Partial<{
[k in OperationType]: (
op: Extract<SyncOperation<T, TResoUpdate, TStateUpdate>, {type: k}>,
op: Extract<SyncOperation<T, TConnUpdate, TStateUpdate>, {type: k}>,
) => TRet | Promise<TRet>
}>

Expand All @@ -27,13 +27,13 @@ export type OpHandlers<
*/
export function handlersLink<
TData = any,
TResoUpdate extends object = ResoUpdateData,
TConnUpdate extends object = ConnectionUpdateData,
TStateUpdate extends object = StateUpdateData,
>(
handlers: OpHandlers<
rxjs.ObservableInput<SyncOperation<TData>> | void,
TData,
TResoUpdate,
TConnUpdate,
TStateUpdate
>,
): Link<TData> {
Expand Down
13 changes: 10 additions & 3 deletions kits/cdk/connector-meta.types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import type {NangoProvider} from '@opensdks/sdk-nango/src/nango.oas'
import {z} from '@opensdks/util-zod'
import type {oas30, oas31} from 'openapi3-ts'
import type {AnyEntityPayload, ResoUpdateData, Source} from '@openint/sync'
import type {
AnyEntityPayload,
ConnectionUpdateData,
Source,
} from '@openint/sync'
import {castIs} from '@openint/util'
import type {ConnHelpers} from './connector.types'
import type {CustomerId, ExtCustomerId, ExternalId, Id} from './id.types'
Expand Down Expand Up @@ -111,9 +115,12 @@ export const zCheckConnectionOptions = z.object({
})

/** Extra props not on ResoUpdateData */
export interface ConnectionUpdate<TEntity = AnyEntityPayload, TSettings = unknown>
export interface ConnectionUpdate<
TEntity = AnyEntityPayload,
TSettings = unknown,
>
// make `ResoUpdateData.id` not prefixed so we can have better inheritance
extends Omit<ResoUpdateData<TSettings>, 'id'> {
extends Omit<ConnectionUpdateData<TSettings>, 'id'> {
// Subset of connUpdate
connectionExternalId: ExternalId
// Can we inherit types used by metaLinks?
Expand Down
6 changes: 3 additions & 3 deletions kits/cdk/connector.types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type {Link as FetchLink} from '@opensdks/runtime'
import type {z} from '@opensdks/util-zod'
import type {
ConnectionUpdateData,
Destination,
EntityPayload,
ResoUpdateData,
Source,
StateUpdateData,
SyncOperation,
Expand All @@ -14,10 +14,10 @@ import type {
CheckConnectionContext,
CheckConnectionOptions,
ConnectContext,
ConnectionUpdate,
ConnectOptions,
ConnectorMetadata,
OpenDialogFn,
ConnectionUpdate,
WebhookReturnType,
zPassthroughInput,
} from './connector-meta.types'
Expand Down Expand Up @@ -312,7 +312,7 @@ export function connHelpers<TSchemas extends ConnectorSchemas>(
}>,
{type: 'data'}
>
type connUpdate = ResoUpdateData<
type connUpdate = ConnectionUpdateData<
_types['connectionSettings'],
_types['integrationData']
>
Expand Down
2 changes: 2 additions & 0 deletions kits/cdk/internal/nangoProxyLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {mergeHeaders, modifyRequest} from '@opensdks/fetch-links'
import {initNangoSDK} from '@opensdks/sdk-nango'
import {z} from '@opensdks/util-zod'
import {isHttpError, NotAuthenticatedError} from '@openint/trpc'
import {zOauthConnectionError} from './oauthConnector'

const kBaseUrlOverride = 'base-url-override'

Expand Down Expand Up @@ -206,4 +207,5 @@ export const nangoConnectionWithCredentials = z.object({
deleted_at: z.string().nullish(),
last_fetched_at: z.string().nullish(),
config_id: z.number().nullish(),
error: zOauthConnectionError.nullish(),
})
20 changes: 18 additions & 2 deletions kits/cdk/internal/oauthConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export const zAuthMode = z
.enum(['OAUTH2', 'OAUTH1', 'BASIC', 'API_KEY'])
.openapi({ref: 'AuthMode'})

export const zOauthConnectionError = z.object({
code: z.enum(['refresh_token_external_error']).or(z.string()),
message: z.string().nullish(),
})

export const oauthBaseSchema = {
name: z.literal('__oauth__'), // TODO: This is a noop
connectorConfig: z.object({
Expand All @@ -33,6 +38,7 @@ export const oauthBaseSchema = {
}),
}),
connectionSettings: z.object({
// equivalent to nango /v1/connections data.connection object with certain fields removed like id
oauth: z.object({
credentials: z.object({
type: zAuthMode,
Expand Down Expand Up @@ -61,6 +67,8 @@ export const oauthBaseSchema = {
.nullish(),
metadata: z.record(z.unknown()).nullable(),
}),
// TODO: add error fields here or maybe at a higher level to capture when connection needs to be refreshed
error: zOauthConnectionError.nullish(),
}),
connectOutput: z.object({
providerConfigKey: zId('ccfg'),
Expand Down Expand Up @@ -147,8 +155,16 @@ export function makeOauthConnectorServer({
},
},
})
.then((r) => r.data)
return {connectionExternalId: extractId(connId)[2], settings: {oauth: res}}
.then((r) => r.data as OauthBaseTypes['connectionSettings'])
return {
connectionExternalId: extractId(connId)[2],
settings: {
oauth: res as any,
...(res?.error?.code || res?.error?.message
? {error: {code: res?.error?.code, message: res?.error?.message}}
: {}),
},
}
},
} satisfies ConnectorServer<typeof oauthBaseSchema>
return {
Expand Down
8 changes: 7 additions & 1 deletion kits/cdk/internal/remote-procedure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ export async function getRemoteContext(ctx: ProtectedContext) {
},
},
})
.then((r) => nangoConnectionWithCredentials.parse(r.data)),
.then((r: any) => {
return nangoConnectionWithCredentials.parse(r.data)
})
.catch((error) => {
console.error('nangoConnectionWithCredentials error', error)
throw error
}),
}),
}

Expand Down
2 changes: 1 addition & 1 deletion kits/cdk/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const zStandard = {
}),
connection: z.object({
id: zId('conn'),
displayName: z.string(),
displayName: z.string().nullish(),
/**
* This correspond to the connection status.
* Pipeline shall have a separate syncStatus */
Expand Down
5 changes: 2 additions & 3 deletions kits/sdk/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -8063,7 +8063,7 @@
"type": ["object", "null"],
"properties": {
"displayName": {
"type": "string"
"type": ["string", "null"]
},
"status": {
"type": ["string", "null"],
Expand All @@ -8078,8 +8078,7 @@
"type": "string"
}
}
},
"required": ["displayName"]
}
},
"disabled": {
"type": "boolean"
Expand Down
2 changes: 1 addition & 1 deletion kits/sdk/openapi.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ export interface components {
[key: string]: unknown
} | null
standard?: {
displayName: string
displayName?: string | null
/** @enum {string|null} */
status?: 'healthy' | 'disconnected' | 'error' | 'manual'
statusMessage?: string | null
Expand Down
27 changes: 15 additions & 12 deletions kits/sync/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface AnyEntityPayload {
id: string // ExternalId
}

export interface ResoUpdateData<
export interface ConnectionUpdateData<
TSettings = {},
TInsData = {},
TVariant extends 'partial' | 'complete' = 'partial',
Expand All @@ -43,6 +43,9 @@ export interface ResoUpdateData<
externalId: ExternalId
data: TInsData
}
// QQ: Extend this or calculate it before displaying based on settings?
// status?: ZStandard['connection']['status']
// statusMessage?: string
}
export interface StateUpdateData<TSrcOptions = {}, TDestOptions = {}> {
sourceState?: ObjectPartialDeep<NoInfer<TSrcOptions>>
Expand All @@ -55,10 +58,10 @@ type NullableEntity<T> = T extends AnyEntityPayload

export type SyncOperation<
TData = any,
TResoUpdate extends object = ResoUpdateData,
TConnUpdate extends object = ConnectionUpdateData,
TStateUpdate extends object = StateUpdateData,
> =
| (TResoUpdate & {type: 'connUpdate'})
| (TConnUpdate & {type: 'connUpdate'})
// TODO: We should separate state from options, and perhaps make state
// less black box also, see airbyte protocol v2 for inspiration
// Also consider merging fields below into a single field
Expand All @@ -71,9 +74,9 @@ export type AnySyncOperation = NonDiscriminatedUnion<SyncOperation>

export type Source<
T,
TResoUpdate extends object = ResoUpdateData,
TConnUpdate extends object = ConnectionUpdateData,
TStateUpdate extends object = StateUpdateData,
> = rxjs.Observable<SyncOperation<T, TResoUpdate, TStateUpdate>>
> = rxjs.Observable<SyncOperation<T, TConnUpdate, TStateUpdate>>

/**
* Adapted from TRPC link and Apollo Link
Expand All @@ -82,30 +85,30 @@ export type Source<
export type Link<
TDataIn = any,
TDataOut = TDataIn,
TResoUpdate extends object = ResoUpdateData,
TConnUpdate extends object = ConnectionUpdateData,
TStateUpdate extends object = StateUpdateData,
> = (
obs: rxjs.Observable<SyncOperation<TDataIn, TResoUpdate, TStateUpdate>>,
) => rxjs.Observable<SyncOperation<TDataOut, TResoUpdate, TStateUpdate>>
obs: rxjs.Observable<SyncOperation<TDataIn, TConnUpdate, TStateUpdate>>,
) => rxjs.Observable<SyncOperation<TDataOut, TConnUpdate, TStateUpdate>>

export type LinkFactory<
TDataIn = any,
TDataOut = TDataIn,
TResoUpdate extends object = ResoUpdateData,
TConnUpdate extends object = ConnectionUpdateData,
TStateUpdate extends object = StateUpdateData,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
TArg = any,
> = (arg: TArg) => Link<TDataIn, TDataOut, TResoUpdate, TStateUpdate>
> = (arg: TArg) => Link<TDataIn, TDataOut, TConnUpdate, TStateUpdate>

/**
* Terminating link is just a link... It can still emit things like ready event
* for the engine to listen to. The resulting event may not be the same as the input events
*/
export type Destination<
T = any,
TResoUpdate extends object = ResoUpdateData,
TConnUpdate extends object = ConnectionUpdateData,
TStateUpdate extends object = StateUpdateData,
> = Link<T, T, TResoUpdate, TStateUpdate>
> = Link<T, T, TConnUpdate, TStateUpdate>

// @deprecated?

Expand Down
10 changes: 5 additions & 5 deletions kits/sync/sync.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {R, Rx, toCompletion} from '@openint/util'
import type {
AnyEntityPayload,
ConnectionUpdateData,
Destination,
Link,
ResoUpdateData,
Source,
StateUpdateData,
SyncOperation,
Expand All @@ -17,12 +17,12 @@ const COMMIT: Extract<SyncOperation, {type: 'commit'}> = {type: 'commit'}
/** The most fundamental implementation of using using protocol */
export async function sync<
T extends Data = Data,
TResoUpdate extends object = ResoUpdateData,
TConnUpdate extends object = ConnectionUpdateData,
TStateUpdate extends object = StateUpdateData,
>(input: {
source: Source<T, TResoUpdate, TStateUpdate>
destination: Destination<T, TResoUpdate, TStateUpdate>
links?: Array<Link<T, T, TResoUpdate, TStateUpdate>>
source: Source<T, TConnUpdate, TStateUpdate>
destination: Destination<T, TConnUpdate, TStateUpdate>
links?: Array<Link<T, T, TConnUpdate, TStateUpdate>>
watch?: boolean
}) {
const start = Date.now()
Expand Down
1 change: 1 addition & 0 deletions packages/api/proxyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const proxyHandler = async (req: Request) => {
new Date()
: false

// TODO: this logic is potentially being duplicated in the getRemoteContext call
if (credentialsExpired && remoteContext.remoteConnectionId) {
const nango = initNangoSDK({
headers: {authorization: `Bearer ${process.env['NANGO_SECRET_KEY']}`},
Expand Down
15 changes: 12 additions & 3 deletions packages/engine-backend/router/connectionRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ const zExpandConnector = z.object({
logoUrl: z.string().url(),
})

async function performConnectionCheck(ctx: any, connId: string, opts: any) {
export async function performConnectionCheck(
ctx: any,
connId: string,
opts: any,
) {
const remoteCtx = await getRemoteContext({
...ctx,
remoteConnectionId: connId,
Expand All @@ -51,8 +55,13 @@ async function performConnectionCheck(ctx: any, connId: string, opts: any) {
webhookBaseUrl: joinPath(ctx.apiUrl, parseWebhookRequest.pathOf(int.id)),
},
})

if (connUpdate || opts?.import !== false) {
if (
conn?.settings?.error ||
connUpdate ||
opts?.import !== false ||
remoteCtx.remote.settings?.oauth?.error
) {
/** Do not update the `customerId` here... */
await ctx.asOrgIfNeeded._syncConnectionUpdate(int, {
customerId: conn.customerId ?? undefined,
integration: conn.integrationId
Expand Down
4 changes: 4 additions & 0 deletions packages/engine-backend/router/customerRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ export const customerRouter = trpc.router({
customerId:
ctx.viewer.role === 'customer' ? ctx.viewer.customerId : null,
triggerDefaultSync,
settings: {
...connUpdate?.settings,
error: connUpdate?.settings?.['error'] || null,
},
},
)

Expand Down
Loading
Loading