@shipfox/api-integration-jira 12.2.0 → 12.4.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/.turbo/turbo-build.log +8 -7
- package/.turbo/turbo-type$colon$emit.log +0 -1
- package/CHANGELOG.md +17 -0
- package/README.md +5 -1
- package/dist/api/client.d.ts +11 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +57 -12
- package/dist/api/client.js.map +1 -1
- package/dist/core/disconnect.d.ts +12 -0
- package/dist/core/disconnect.d.ts.map +1 -1
- package/dist/core/disconnect.js +45 -1
- package/dist/core/disconnect.js.map +1 -1
- package/dist/core/install.d.ts +1 -0
- package/dist/core/install.d.ts.map +1 -1
- package/dist/core/install.js +2 -1
- package/dist/core/install.js.map +1 -1
- package/dist/core/tokens.d.ts +2 -0
- package/dist/core/tokens.d.ts.map +1 -1
- package/dist/core/tokens.js +2 -2
- package/dist/core/tokens.js.map +1 -1
- package/dist/core/webhook-registration.d.ts +1 -0
- package/dist/core/webhook-registration.d.ts.map +1 -1
- package/dist/core/webhook-registration.js +3 -1
- package/dist/core/webhook-registration.js.map +1 -1
- package/dist/core/webhook-url.d.ts +3 -0
- package/dist/core/webhook-url.d.ts.map +1 -0
- package/dist/core/webhook-url.js +8 -0
- package/dist/core/webhook-url.js.map +1 -0
- package/dist/db/installations.d.ts +13 -0
- package/dist/db/installations.d.ts.map +1 -1
- package/dist/db/installations.js +18 -1
- package/dist/db/installations.js.map +1 -1
- package/dist/index.d.ts +17 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -7
- package/dist/index.js.map +1 -1
- package/dist/presentation/routes/webhooks.d.ts +2 -1
- package/dist/presentation/routes/webhooks.d.ts.map +1 -1
- package/dist/presentation/routes/webhooks.js +2 -1
- package/dist/presentation/routes/webhooks.js.map +1 -1
- package/dist/temporal/activities/index.d.ts +8 -1
- package/dist/temporal/activities/index.d.ts.map +1 -1
- package/dist/temporal/activities/index.js +8 -1
- package/dist/temporal/activities/index.js.map +1 -1
- package/dist/temporal/activities/renew-jira-webhooks.d.ts +26 -0
- package/dist/temporal/activities/renew-jira-webhooks.d.ts.map +1 -0
- package/dist/temporal/activities/renew-jira-webhooks.js +96 -0
- package/dist/temporal/activities/renew-jira-webhooks.js.map +1 -0
- package/dist/temporal/constants.d.ts +2 -0
- package/dist/temporal/constants.d.ts.map +1 -1
- package/dist/temporal/constants.js +2 -0
- package/dist/temporal/constants.js.map +1 -1
- package/dist/temporal/worker.d.ts +3 -0
- package/dist/temporal/worker.d.ts.map +1 -1
- package/dist/temporal/worker.js +5 -0
- package/dist/temporal/worker.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +38 -2
- package/dist/temporal/workflows/index.d.ts +1 -0
- package/dist/temporal/workflows/index.d.ts.map +1 -1
- package/dist/temporal/workflows/index.js +1 -0
- package/dist/temporal/workflows/index.js.map +1 -1
- package/dist/temporal/workflows/renew-jira-webhooks-cron.d.ts +2 -0
- package/dist/temporal/workflows/renew-jira-webhooks-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/renew-jira-webhooks-cron.js +18 -0
- package/dist/temporal/workflows/renew-jira-webhooks-cron.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/api/client.test.ts +83 -4
- package/src/api/client.ts +70 -8
- package/src/core/disconnect.test.ts +107 -1
- package/src/core/disconnect.ts +64 -1
- package/src/core/install.test.ts +23 -0
- package/src/core/install.ts +5 -2
- package/src/core/tokens-refresh.test.ts +39 -0
- package/src/core/tokens.ts +4 -2
- package/src/core/webhook-registration.test.ts +32 -0
- package/src/core/webhook-registration.ts +4 -1
- package/src/core/webhook-url.ts +9 -0
- package/src/db/installations.test.ts +82 -0
- package/src/db/installations.ts +54 -1
- package/src/index.test.ts +21 -1
- package/src/index.ts +21 -9
- package/src/presentation/routes/webhooks.ts +3 -1
- package/src/temporal/activities/index.ts +20 -3
- package/src/temporal/activities/renew-jira-webhooks.test.ts +215 -0
- package/src/temporal/activities/renew-jira-webhooks.ts +144 -0
- package/src/temporal/constants.ts +2 -0
- package/src/temporal/worker.ts +11 -0
- package/src/temporal/workflows/index.ts +1 -0
- package/src/temporal/workflows/renew-jira-webhooks-cron.ts +17 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/core/tokens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/tokens.ts"],"sourcesContent":["import type {IntegrationConnectionLifecycleStatus} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {createJiraApiClient, type JiraApiClient} from '#api/client.js';\nimport {\n JiraAccessTokenMissingError,\n JiraConnectionNotFoundError,\n JiraIntegrationProviderError,\n JiraTokenUnrefreshableError,\n} from '#core/errors.js';\nimport {\n getJiraInstallationByConnectionId,\n updateJiraInstallationTokenExpiry,\n withJiraRefreshLock,\n withJiraRefreshLockAndWait,\n} from '#db/installations.js';\n\nconst ACCESS_TOKEN_KEY = 'ACCESS_TOKEN';\nconst REFRESH_TOKEN_KEY = 'REFRESH_TOKEN';\nconst TOKEN_REFRESH_MARGIN_MS = 5 * 60 * 1000;\n\nexport interface JiraConnectionResolverResult {\n workspaceId: string;\n lifecycleStatus: IntegrationConnectionLifecycleStatus;\n}\n\nexport interface JiraSecretsStore {\n getSecret(params: {workspaceId: string; namespace: string; key: string}): Promise<string | null>;\n setSecrets(params: {\n workspaceId: string;\n namespace: string;\n values: Record<string, string>;\n editedBy?: string | null | undefined;\n }): Promise<void>;\n}\n\nexport interface CreateJiraTokenStoreParams {\n resolveConnection(connectionId: string): Promise<JiraConnectionResolverResult | undefined>;\n secrets: JiraSecretsStore;\n client?: JiraApiClient | undefined;\n markConnectionError?: (params: {connectionId: string}) => Promise<void>;\n}\n\nexport interface StoreJiraTokensParams {\n connectionId: string;\n accessToken: string;\n refreshToken?: string | undefined;\n editedBy?: string | null | undefined;\n}\n\nexport interface GetJiraAccessTokenParams {\n connectionId: string;\n forceRefresh?: boolean | undefined;\n}\n\nexport interface JiraTokenStore {\n storeTokens(params: StoreJiraTokensParams): Promise<void>;\n getAccessToken(params: GetJiraAccessTokenParams): Promise<string>;\n}\n\nexport function jiraSecretsNamespace(connectionId: string): string {\n return `system/integrations/jira/${connectionId}`;\n}\n\nexport function createJiraTokenStore(params: CreateJiraTokenStoreParams): JiraTokenStore {\n const client = params.client ?? createJiraApiClient();\n const tokenRefreshes = new Map<string, Promise<string>>();\n const refreshStateUnknownConnections = new Set<string>();\n const credentialGenerations = new Map<string, number>();\n const credentialWriteQueues = new Map<string, Promise<void>>();\n const currentCredentialGeneration = (connectionId: string): number =>\n credentialGenerations.get(connectionId) ?? 0;\n const advanceCredentialGeneration = (connectionId: string): number => {\n const generation = currentCredentialGeneration(connectionId) + 1;\n credentialGenerations.set(connectionId, generation);\n return generation;\n };\n function withCredentialWrite<T>(connectionId: string, operation: () => Promise<T>): Promise<T> {\n const previous = credentialWriteQueues.get(connectionId) ?? Promise.resolve();\n const operationResult = previous.then(operation);\n const completion = operationResult.then(\n () => undefined,\n () => undefined,\n );\n credentialWriteQueues.set(connectionId, completion);\n return operationResult.finally(() => {\n if (credentialWriteQueues.get(connectionId) === completion) {\n credentialWriteQueues.delete(connectionId);\n }\n });\n }\n async function waitForCredentialWrites(connectionId: string): Promise<void> {\n await credentialWriteQueues.get(connectionId);\n }\n async function resolveConnection(connectionId: string): Promise<JiraConnectionResolverResult> {\n const connection = await params.resolveConnection(connectionId);\n if (!connection) throw new JiraConnectionNotFoundError(connectionId);\n return connection;\n }\n async function resolveWorkspaceId(connectionId: string): Promise<string> {\n return (await resolveConnection(connectionId)).workspaceId;\n }\n function clearTokenRefresh(connectionId: string, refresh: Promise<string>): void {\n if (tokenRefreshes.get(connectionId) === refresh) tokenRefreshes.delete(connectionId);\n }\n const readSecretToken = (\n workspaceId: string,\n connectionId: string,\n key: typeof ACCESS_TOKEN_KEY | typeof REFRESH_TOKEN_KEY,\n ) => params.secrets.getSecret({workspaceId, namespace: jiraSecretsNamespace(connectionId), key});\n async function readAccessToken(workspaceId: string, connectionId: string): Promise<string> {\n const token = await readSecretToken(workspaceId, connectionId, ACCESS_TOKEN_KEY);\n if (!token) throw new JiraAccessTokenMissingError(connectionId);\n return token;\n }\n\n return {\n async storeTokens(input) {\n const credentialGeneration = advanceCredentialGeneration(input.connectionId);\n tokenRefreshes.delete(input.connectionId);\n await withJiraRefreshLockAndWait(input.connectionId, () =>\n withCredentialWrite(input.connectionId, async () => {\n if (currentCredentialGeneration(input.connectionId) !== credentialGeneration) return;\n const workspaceId = await resolveWorkspaceId(input.connectionId);\n if (currentCredentialGeneration(input.connectionId) !== credentialGeneration) return;\n const values: Record<string, string> = {[ACCESS_TOKEN_KEY]: input.accessToken};\n if (input.refreshToken) values[REFRESH_TOKEN_KEY] = input.refreshToken;\n await params.secrets.setSecrets({\n workspaceId,\n namespace: jiraSecretsNamespace(input.connectionId),\n values,\n editedBy: input.editedBy,\n });\n if (currentCredentialGeneration(input.connectionId) === credentialGeneration) {\n refreshStateUnknownConnections.delete(input.connectionId);\n }\n }),\n );\n },\n async getAccessToken(input) {\n await waitForCredentialWrites(input.connectionId);\n const connection = await resolveConnection(input.connectionId);\n if (connection.lifecycleStatus !== 'active') {\n throw new JiraTokenUnrefreshableError(input.connectionId);\n }\n await waitForCredentialWrites(input.connectionId);\n if (refreshStateUnknownConnections.has(input.connectionId)) {\n throw new JiraTokenUnrefreshableError(input.connectionId);\n }\n const credentialGeneration = currentCredentialGeneration(input.connectionId);\n const workspaceId = connection.workspaceId;\n const accessToken = await readAccessToken(workspaceId, input.connectionId);\n if (\n !input.forceRefresh &&\n !shouldRefresh(\n (await getJiraInstallationByConnectionId(input.connectionId))?.tokenExpiresAt ?? null,\n )\n )\n return accessToken;\n const inFlight = tokenRefreshes.get(input.connectionId);\n if (inFlight) return inFlight;\n const refresh = refreshAccessTokenWithLock({\n connectionId: input.connectionId,\n workspaceId,\n originalAccessToken: accessToken,\n forceRefresh: input.forceRefresh === true,\n client,\n secrets: params.secrets,\n readAccessToken,\n readSecretToken,\n markConnectionError: params.markConnectionError,\n withCredentialWrite,\n waitForCredentialWrites,\n isCurrentCredentialGeneration: () =>\n currentCredentialGeneration(input.connectionId) === credentialGeneration,\n });\n tokenRefreshes.set(input.connectionId, refresh);\n void refresh.then(\n () => clearTokenRefresh(input.connectionId, refresh),\n (error) => {\n if (\n isTerminalRefreshFailure(error) &&\n currentCredentialGeneration(input.connectionId) === credentialGeneration\n ) {\n refreshStateUnknownConnections.add(input.connectionId);\n }\n clearTokenRefresh(input.connectionId, refresh);\n },\n );\n return refresh;\n },\n };\n}\n\nfunction shouldRefresh(expiresAt: Date | null): boolean {\n return expiresAt !== null && expiresAt.getTime() <= Date.now() + TOKEN_REFRESH_MARGIN_MS;\n}\n\nasync function refreshAccessTokenWithLock(params: {\n connectionId: string;\n workspaceId: string;\n originalAccessToken: string;\n forceRefresh: boolean;\n client: JiraApiClient;\n secrets: JiraSecretsStore;\n readAccessToken(workspaceId: string, connectionId: string): Promise<string>;\n readSecretToken(\n workspaceId: string,\n connectionId: string,\n key: typeof ACCESS_TOKEN_KEY | typeof REFRESH_TOKEN_KEY,\n ): Promise<string | null>;\n markConnectionError?: ((params: {connectionId: string}) => Promise<void>) | undefined;\n withCredentialWrite<T>(connectionId: string, operation: () => Promise<T>): Promise<T>;\n waitForCredentialWrites(connectionId: string): Promise<void>;\n isCurrentCredentialGeneration(): boolean;\n}): Promise<string> {\n const lock = await withJiraRefreshLock(params.connectionId, () =>\n refreshAccessTokenForConnection(params),\n );\n if (lock.acquired) return lock.value;\n if (!params.isCurrentCredentialGeneration()) {\n return params.readAccessToken(params.workspaceId, params.connectionId);\n }\n const reread = await params.readAccessToken(params.workspaceId, params.connectionId);\n if (reread !== params.originalAccessToken) return reread;\n throw new JiraIntegrationProviderError(\n 'provider-unavailable',\n 'Jira token refresh is already in progress',\n );\n}\n\nasync function refreshAccessTokenForConnection(params: {\n connectionId: string;\n workspaceId: string;\n originalAccessToken: string;\n forceRefresh: boolean;\n client: JiraApiClient;\n secrets: JiraSecretsStore;\n readAccessToken(workspaceId: string, connectionId: string): Promise<string>;\n readSecretToken(\n workspaceId: string,\n connectionId: string,\n key: typeof ACCESS_TOKEN_KEY | typeof REFRESH_TOKEN_KEY,\n ): Promise<string | null>;\n markConnectionError?: ((params: {connectionId: string}) => Promise<void>) | undefined;\n withCredentialWrite<T>(connectionId: string, operation: () => Promise<T>): Promise<T>;\n waitForCredentialWrites(connectionId: string): Promise<void>;\n isCurrentCredentialGeneration(): boolean;\n}): Promise<string> {\n if (!params.isCurrentCredentialGeneration()) {\n return params.readAccessToken(params.workspaceId, params.connectionId);\n }\n const current = await params.readAccessToken(params.workspaceId, params.connectionId);\n const installation = await getJiraInstallationByConnectionId(params.connectionId);\n if (\n current !== params.originalAccessToken ||\n (!params.forceRefresh && !shouldRefresh(installation?.tokenExpiresAt ?? null))\n )\n return current;\n const refreshToken = await params.readSecretToken(\n params.workspaceId,\n params.connectionId,\n REFRESH_TOKEN_KEY,\n );\n if (!refreshToken) {\n if (params.forceRefresh || shouldRefresh(installation?.tokenExpiresAt ?? null))\n throw new JiraTokenUnrefreshableError(params.connectionId);\n return current;\n }\n try {\n const refreshed = await params.client.refreshAccessToken({refreshToken});\n const refreshedRefreshToken = refreshed.refreshToken;\n if (!refreshedRefreshToken) throw new JiraTokenUnrefreshableError(params.connectionId);\n const persisted = await params.withCredentialWrite(params.connectionId, async () => {\n if (!params.isCurrentCredentialGeneration()) return false;\n await params.secrets.setSecrets({\n workspaceId: params.workspaceId,\n namespace: jiraSecretsNamespace(params.connectionId),\n values: {\n [ACCESS_TOKEN_KEY]: refreshed.accessToken,\n [REFRESH_TOKEN_KEY]: refreshedRefreshToken,\n },\n });\n if (!params.isCurrentCredentialGeneration()) return false;\n await updateJiraInstallationTokenExpiry({\n connectionId: params.connectionId,\n tokenExpiresAt: refreshed.expiresAt ?? null,\n scopes: refreshed.scopes.length > 0 ? refreshed.scopes : undefined,\n });\n return true;\n });\n if (!persisted || !params.isCurrentCredentialGeneration()) {\n await params.waitForCredentialWrites(params.connectionId);\n return params.readAccessToken(params.workspaceId, params.connectionId);\n }\n return refreshed.accessToken;\n } catch (error) {\n if (isTerminalRefreshFailure(error)) {\n await markConnectionError({\n callback: params.markConnectionError,\n connectionId: params.connectionId,\n isCurrentCredentialGeneration: params.isCurrentCredentialGeneration,\n withCredentialWrite: params.withCredentialWrite,\n });\n }\n throw error;\n }\n}\n\nfunction isTerminalRefreshFailure(error: unknown): boolean {\n return (\n error instanceof JiraIntegrationProviderError &&\n (error.reason === 'access-denied' || error.reason === 'timeout')\n );\n}\n\nasync function markConnectionError(params: {\n callback: CreateJiraTokenStoreParams['markConnectionError'];\n connectionId: string;\n isCurrentCredentialGeneration(): boolean;\n withCredentialWrite<T>(connectionId: string, operation: () => Promise<T>): Promise<T>;\n}): Promise<void> {\n if (!params.isCurrentCredentialGeneration()) return;\n try {\n await params.withCredentialWrite(params.connectionId, async () => {\n if (!params.isCurrentCredentialGeneration()) return;\n await params.callback?.({connectionId: params.connectionId});\n });\n } catch (error) {\n logger().warn(\n {err: error, connectionId: params.connectionId},\n 'Jira connection error-state update failed after token refresh failure',\n );\n }\n}\n"],"names":["logger","createJiraApiClient","JiraAccessTokenMissingError","JiraConnectionNotFoundError","JiraIntegrationProviderError","JiraTokenUnrefreshableError","getJiraInstallationByConnectionId","updateJiraInstallationTokenExpiry","withJiraRefreshLock","withJiraRefreshLockAndWait","ACCESS_TOKEN_KEY","REFRESH_TOKEN_KEY","TOKEN_REFRESH_MARGIN_MS","jiraSecretsNamespace","connectionId","createJiraTokenStore","params","client","tokenRefreshes","Map","refreshStateUnknownConnections","Set","credentialGenerations","credentialWriteQueues","currentCredentialGeneration","get","advanceCredentialGeneration","generation","set","withCredentialWrite","operation","previous","Promise","resolve","operationResult","then","completion","undefined","finally","delete","waitForCredentialWrites","resolveConnection","connection","resolveWorkspaceId","workspaceId","clearTokenRefresh","refresh","readSecretToken","key","secrets","getSecret","namespace","readAccessToken","token","storeTokens","input","credentialGeneration","values","accessToken","refreshToken","setSecrets","editedBy","getAccessToken","lifecycleStatus","has","forceRefresh","shouldRefresh","tokenExpiresAt","inFlight","refreshAccessTokenWithLock","originalAccessToken","markConnectionError","isCurrentCredentialGeneration","error","isTerminalRefreshFailure","add","expiresAt","getTime","Date","now","lock","refreshAccessTokenForConnection","acquired","value","reread","current","installation","refreshed","refreshAccessToken","refreshedRefreshToken","persisted","scopes","length","callback","reason","warn","err"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/core/tokens.ts"],"sourcesContent":["import type {IntegrationConnectionLifecycleStatus} from '@shipfox/api-integration-spi';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {createJiraApiClient, type JiraApiClient} from '#api/client.js';\nimport {\n JiraAccessTokenMissingError,\n JiraConnectionNotFoundError,\n JiraIntegrationProviderError,\n JiraTokenUnrefreshableError,\n} from '#core/errors.js';\nimport {\n getJiraInstallationByConnectionId,\n updateJiraInstallationTokenExpiry,\n withJiraRefreshLock,\n withJiraRefreshLockAndWait,\n} from '#db/installations.js';\n\nconst ACCESS_TOKEN_KEY = 'ACCESS_TOKEN';\nconst REFRESH_TOKEN_KEY = 'REFRESH_TOKEN';\nconst TOKEN_REFRESH_MARGIN_MS = 5 * 60 * 1000;\n\nexport interface JiraConnectionResolverResult {\n workspaceId: string;\n lifecycleStatus: IntegrationConnectionLifecycleStatus;\n}\n\nexport interface JiraSecretsStore {\n getSecret(params: {workspaceId: string; namespace: string; key: string}): Promise<string | null>;\n setSecrets(params: {\n workspaceId: string;\n namespace: string;\n values: Record<string, string>;\n editedBy?: string | null | undefined;\n }): Promise<void>;\n}\n\nexport interface CreateJiraTokenStoreParams {\n resolveConnection(connectionId: string): Promise<JiraConnectionResolverResult | undefined>;\n secrets: JiraSecretsStore;\n client?: JiraApiClient | undefined;\n markConnectionError?: (params: {connectionId: string}) => Promise<void>;\n}\n\nexport interface StoreJiraTokensParams {\n connectionId: string;\n accessToken: string;\n refreshToken?: string | undefined;\n editedBy?: string | null | undefined;\n}\n\nexport interface GetJiraAccessTokenParams {\n connectionId: string;\n forceRefresh?: boolean | undefined;\n /** Allows cleanup to use the stored token after a connection leaves the active state. */\n allowInactive?: boolean | undefined;\n}\n\nexport interface JiraTokenStore {\n storeTokens(params: StoreJiraTokensParams): Promise<void>;\n getAccessToken(params: GetJiraAccessTokenParams): Promise<string>;\n}\n\nexport function jiraSecretsNamespace(connectionId: string): string {\n return `system/integrations/jira/${connectionId}`;\n}\n\nexport function createJiraTokenStore(params: CreateJiraTokenStoreParams): JiraTokenStore {\n const client = params.client ?? createJiraApiClient();\n const tokenRefreshes = new Map<string, Promise<string>>();\n const refreshStateUnknownConnections = new Set<string>();\n const credentialGenerations = new Map<string, number>();\n const credentialWriteQueues = new Map<string, Promise<void>>();\n const currentCredentialGeneration = (connectionId: string): number =>\n credentialGenerations.get(connectionId) ?? 0;\n const advanceCredentialGeneration = (connectionId: string): number => {\n const generation = currentCredentialGeneration(connectionId) + 1;\n credentialGenerations.set(connectionId, generation);\n return generation;\n };\n function withCredentialWrite<T>(connectionId: string, operation: () => Promise<T>): Promise<T> {\n const previous = credentialWriteQueues.get(connectionId) ?? Promise.resolve();\n const operationResult = previous.then(operation);\n const completion = operationResult.then(\n () => undefined,\n () => undefined,\n );\n credentialWriteQueues.set(connectionId, completion);\n return operationResult.finally(() => {\n if (credentialWriteQueues.get(connectionId) === completion) {\n credentialWriteQueues.delete(connectionId);\n }\n });\n }\n async function waitForCredentialWrites(connectionId: string): Promise<void> {\n await credentialWriteQueues.get(connectionId);\n }\n async function resolveConnection(connectionId: string): Promise<JiraConnectionResolverResult> {\n const connection = await params.resolveConnection(connectionId);\n if (!connection) throw new JiraConnectionNotFoundError(connectionId);\n return connection;\n }\n async function resolveWorkspaceId(connectionId: string): Promise<string> {\n return (await resolveConnection(connectionId)).workspaceId;\n }\n function clearTokenRefresh(connectionId: string, refresh: Promise<string>): void {\n if (tokenRefreshes.get(connectionId) === refresh) tokenRefreshes.delete(connectionId);\n }\n const readSecretToken = (\n workspaceId: string,\n connectionId: string,\n key: typeof ACCESS_TOKEN_KEY | typeof REFRESH_TOKEN_KEY,\n ) => params.secrets.getSecret({workspaceId, namespace: jiraSecretsNamespace(connectionId), key});\n async function readAccessToken(workspaceId: string, connectionId: string): Promise<string> {\n const token = await readSecretToken(workspaceId, connectionId, ACCESS_TOKEN_KEY);\n if (!token) throw new JiraAccessTokenMissingError(connectionId);\n return token;\n }\n\n return {\n async storeTokens(input) {\n const credentialGeneration = advanceCredentialGeneration(input.connectionId);\n tokenRefreshes.delete(input.connectionId);\n await withJiraRefreshLockAndWait(input.connectionId, () =>\n withCredentialWrite(input.connectionId, async () => {\n if (currentCredentialGeneration(input.connectionId) !== credentialGeneration) return;\n const workspaceId = await resolveWorkspaceId(input.connectionId);\n if (currentCredentialGeneration(input.connectionId) !== credentialGeneration) return;\n const values: Record<string, string> = {[ACCESS_TOKEN_KEY]: input.accessToken};\n if (input.refreshToken) values[REFRESH_TOKEN_KEY] = input.refreshToken;\n await params.secrets.setSecrets({\n workspaceId,\n namespace: jiraSecretsNamespace(input.connectionId),\n values,\n editedBy: input.editedBy,\n });\n if (currentCredentialGeneration(input.connectionId) === credentialGeneration) {\n refreshStateUnknownConnections.delete(input.connectionId);\n }\n }),\n );\n },\n async getAccessToken(input) {\n await waitForCredentialWrites(input.connectionId);\n const connection = await resolveConnection(input.connectionId);\n if (!input.allowInactive && connection.lifecycleStatus !== 'active') {\n throw new JiraTokenUnrefreshableError(input.connectionId);\n }\n await waitForCredentialWrites(input.connectionId);\n if (!input.allowInactive && refreshStateUnknownConnections.has(input.connectionId)) {\n throw new JiraTokenUnrefreshableError(input.connectionId);\n }\n const credentialGeneration = currentCredentialGeneration(input.connectionId);\n const workspaceId = connection.workspaceId;\n const accessToken = await readAccessToken(workspaceId, input.connectionId);\n if (\n !input.forceRefresh &&\n !shouldRefresh(\n (await getJiraInstallationByConnectionId(input.connectionId))?.tokenExpiresAt ?? null,\n )\n )\n return accessToken;\n const inFlight = tokenRefreshes.get(input.connectionId);\n if (inFlight) return inFlight;\n const refresh = refreshAccessTokenWithLock({\n connectionId: input.connectionId,\n workspaceId,\n originalAccessToken: accessToken,\n forceRefresh: input.forceRefresh === true,\n client,\n secrets: params.secrets,\n readAccessToken,\n readSecretToken,\n markConnectionError: params.markConnectionError,\n withCredentialWrite,\n waitForCredentialWrites,\n isCurrentCredentialGeneration: () =>\n currentCredentialGeneration(input.connectionId) === credentialGeneration,\n });\n tokenRefreshes.set(input.connectionId, refresh);\n void refresh.then(\n () => clearTokenRefresh(input.connectionId, refresh),\n (error) => {\n if (\n isTerminalRefreshFailure(error) &&\n currentCredentialGeneration(input.connectionId) === credentialGeneration\n ) {\n refreshStateUnknownConnections.add(input.connectionId);\n }\n clearTokenRefresh(input.connectionId, refresh);\n },\n );\n return refresh;\n },\n };\n}\n\nfunction shouldRefresh(expiresAt: Date | null): boolean {\n return expiresAt !== null && expiresAt.getTime() <= Date.now() + TOKEN_REFRESH_MARGIN_MS;\n}\n\nasync function refreshAccessTokenWithLock(params: {\n connectionId: string;\n workspaceId: string;\n originalAccessToken: string;\n forceRefresh: boolean;\n client: JiraApiClient;\n secrets: JiraSecretsStore;\n readAccessToken(workspaceId: string, connectionId: string): Promise<string>;\n readSecretToken(\n workspaceId: string,\n connectionId: string,\n key: typeof ACCESS_TOKEN_KEY | typeof REFRESH_TOKEN_KEY,\n ): Promise<string | null>;\n markConnectionError?: ((params: {connectionId: string}) => Promise<void>) | undefined;\n withCredentialWrite<T>(connectionId: string, operation: () => Promise<T>): Promise<T>;\n waitForCredentialWrites(connectionId: string): Promise<void>;\n isCurrentCredentialGeneration(): boolean;\n}): Promise<string> {\n const lock = await withJiraRefreshLock(params.connectionId, () =>\n refreshAccessTokenForConnection(params),\n );\n if (lock.acquired) return lock.value;\n if (!params.isCurrentCredentialGeneration()) {\n return params.readAccessToken(params.workspaceId, params.connectionId);\n }\n const reread = await params.readAccessToken(params.workspaceId, params.connectionId);\n if (reread !== params.originalAccessToken) return reread;\n throw new JiraIntegrationProviderError(\n 'provider-unavailable',\n 'Jira token refresh is already in progress',\n );\n}\n\nasync function refreshAccessTokenForConnection(params: {\n connectionId: string;\n workspaceId: string;\n originalAccessToken: string;\n forceRefresh: boolean;\n client: JiraApiClient;\n secrets: JiraSecretsStore;\n readAccessToken(workspaceId: string, connectionId: string): Promise<string>;\n readSecretToken(\n workspaceId: string,\n connectionId: string,\n key: typeof ACCESS_TOKEN_KEY | typeof REFRESH_TOKEN_KEY,\n ): Promise<string | null>;\n markConnectionError?: ((params: {connectionId: string}) => Promise<void>) | undefined;\n withCredentialWrite<T>(connectionId: string, operation: () => Promise<T>): Promise<T>;\n waitForCredentialWrites(connectionId: string): Promise<void>;\n isCurrentCredentialGeneration(): boolean;\n}): Promise<string> {\n if (!params.isCurrentCredentialGeneration()) {\n return params.readAccessToken(params.workspaceId, params.connectionId);\n }\n const current = await params.readAccessToken(params.workspaceId, params.connectionId);\n const installation = await getJiraInstallationByConnectionId(params.connectionId);\n if (\n current !== params.originalAccessToken ||\n (!params.forceRefresh && !shouldRefresh(installation?.tokenExpiresAt ?? null))\n )\n return current;\n const refreshToken = await params.readSecretToken(\n params.workspaceId,\n params.connectionId,\n REFRESH_TOKEN_KEY,\n );\n if (!refreshToken) {\n if (params.forceRefresh || shouldRefresh(installation?.tokenExpiresAt ?? null))\n throw new JiraTokenUnrefreshableError(params.connectionId);\n return current;\n }\n try {\n const refreshed = await params.client.refreshAccessToken({refreshToken});\n const refreshedRefreshToken = refreshed.refreshToken;\n if (!refreshedRefreshToken) throw new JiraTokenUnrefreshableError(params.connectionId);\n const persisted = await params.withCredentialWrite(params.connectionId, async () => {\n if (!params.isCurrentCredentialGeneration()) return false;\n await params.secrets.setSecrets({\n workspaceId: params.workspaceId,\n namespace: jiraSecretsNamespace(params.connectionId),\n values: {\n [ACCESS_TOKEN_KEY]: refreshed.accessToken,\n [REFRESH_TOKEN_KEY]: refreshedRefreshToken,\n },\n });\n if (!params.isCurrentCredentialGeneration()) return false;\n await updateJiraInstallationTokenExpiry({\n connectionId: params.connectionId,\n tokenExpiresAt: refreshed.expiresAt ?? null,\n scopes: refreshed.scopes.length > 0 ? refreshed.scopes : undefined,\n });\n return true;\n });\n if (!persisted || !params.isCurrentCredentialGeneration()) {\n await params.waitForCredentialWrites(params.connectionId);\n return params.readAccessToken(params.workspaceId, params.connectionId);\n }\n return refreshed.accessToken;\n } catch (error) {\n if (isTerminalRefreshFailure(error)) {\n await markConnectionError({\n callback: params.markConnectionError,\n connectionId: params.connectionId,\n isCurrentCredentialGeneration: params.isCurrentCredentialGeneration,\n withCredentialWrite: params.withCredentialWrite,\n });\n }\n throw error;\n }\n}\n\nfunction isTerminalRefreshFailure(error: unknown): boolean {\n return (\n error instanceof JiraIntegrationProviderError &&\n (error.reason === 'access-denied' || error.reason === 'timeout')\n );\n}\n\nasync function markConnectionError(params: {\n callback: CreateJiraTokenStoreParams['markConnectionError'];\n connectionId: string;\n isCurrentCredentialGeneration(): boolean;\n withCredentialWrite<T>(connectionId: string, operation: () => Promise<T>): Promise<T>;\n}): Promise<void> {\n if (!params.isCurrentCredentialGeneration()) return;\n try {\n await params.withCredentialWrite(params.connectionId, async () => {\n if (!params.isCurrentCredentialGeneration()) return;\n await params.callback?.({connectionId: params.connectionId});\n });\n } catch (error) {\n logger().warn(\n {err: error, connectionId: params.connectionId},\n 'Jira connection error-state update failed after token refresh failure',\n );\n }\n}\n"],"names":["logger","createJiraApiClient","JiraAccessTokenMissingError","JiraConnectionNotFoundError","JiraIntegrationProviderError","JiraTokenUnrefreshableError","getJiraInstallationByConnectionId","updateJiraInstallationTokenExpiry","withJiraRefreshLock","withJiraRefreshLockAndWait","ACCESS_TOKEN_KEY","REFRESH_TOKEN_KEY","TOKEN_REFRESH_MARGIN_MS","jiraSecretsNamespace","connectionId","createJiraTokenStore","params","client","tokenRefreshes","Map","refreshStateUnknownConnections","Set","credentialGenerations","credentialWriteQueues","currentCredentialGeneration","get","advanceCredentialGeneration","generation","set","withCredentialWrite","operation","previous","Promise","resolve","operationResult","then","completion","undefined","finally","delete","waitForCredentialWrites","resolveConnection","connection","resolveWorkspaceId","workspaceId","clearTokenRefresh","refresh","readSecretToken","key","secrets","getSecret","namespace","readAccessToken","token","storeTokens","input","credentialGeneration","values","accessToken","refreshToken","setSecrets","editedBy","getAccessToken","allowInactive","lifecycleStatus","has","forceRefresh","shouldRefresh","tokenExpiresAt","inFlight","refreshAccessTokenWithLock","originalAccessToken","markConnectionError","isCurrentCredentialGeneration","error","isTerminalRefreshFailure","add","expiresAt","getTime","Date","now","lock","refreshAccessTokenForConnection","acquired","value","reread","current","installation","refreshed","refreshAccessToken","refreshedRefreshToken","persisted","scopes","length","callback","reason","warn","err"],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ export interface RegisterJiraWebhookParams {
|
|
|
11
11
|
getInstallation?: typeof getJiraInstallationByConnectionId;
|
|
12
12
|
updateInstallation?: typeof updateJiraInstallationWebhook;
|
|
13
13
|
withRegistrationLock?: JiraInstallationLock;
|
|
14
|
+
replaceExistingWebhooks?: boolean | undefined;
|
|
14
15
|
onRegistrationSuccess?: (input: {
|
|
15
16
|
tx?: unknown;
|
|
16
17
|
}) => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-registration.d.ts","sourceRoot":"","sources":["../../src/core/webhook-registration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAClD,OAAO,EACL,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,6BAA6B,EAE9B,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,mBAAmB,QAA2B,CAAC;AAE5D,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,wBAAwB,GAAG,sBAAsB,CAAC,CAAC;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,iCAAiC,CAAC;IAC3D,kBAAkB,CAAC,EAAE,OAAO,6BAA6B,CAAC;IAC1D,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAC,EAAE,CAAC,EAAE,OAAO,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAC,EAAE,CAAC,EAAE,OAAO,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED,qGAAqG;AACrG,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,IAAI,CAAA;CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"webhook-registration.d.ts","sourceRoot":"","sources":["../../src/core/webhook-registration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAClD,OAAO,EACL,iCAAiC,EACjC,KAAK,oBAAoB,EACzB,6BAA6B,EAE9B,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,mBAAmB,QAA2B,CAAC;AAE5D,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,wBAAwB,GAAG,sBAAsB,CAAC,CAAC;IAC7E,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,iCAAiC,CAAC;IAC3D,kBAAkB,CAAC,EAAE,OAAO,6BAA6B,CAAC;IAC1D,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,uBAAuB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9C,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAC,EAAE,CAAC,EAAE,OAAO,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,qBAAqB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAC,EAAE,CAAC,EAAE,OAAO,CAAA;KAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAClE;AAED,qGAAqG;AACrG,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,IAAI,CAAA;CAAC,CAAC,CAgGtD"}
|
|
@@ -25,7 +25,9 @@ export const JIRA_WEBHOOK_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
|
25
25
|
const updateInstallation = params.updateInstallation ?? updateJiraInstallationWebhook;
|
|
26
26
|
const updateInput = {
|
|
27
27
|
connectionId: params.connectionId,
|
|
28
|
-
webhookIds: [
|
|
28
|
+
webhookIds: params.replaceExistingWebhooks ? [
|
|
29
|
+
registration.webhookId
|
|
30
|
+
] : [
|
|
29
31
|
...new Set([
|
|
30
32
|
registration.webhookId,
|
|
31
33
|
...previous?.webhookIds ?? []
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/webhook-registration.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport type {JiraApiClient} from '#api/client.js';\nimport {\n getJiraInstallationByConnectionId,\n type JiraInstallationLock,\n updateJiraInstallationWebhook,\n withJiraInstallationLock,\n} from '#db/installations.js';\n\nexport const JIRA_WEBHOOK_TTL_MS = 30 * 24 * 60 * 60 * 1000;\n\nexport interface RegisterJiraWebhookParams {\n jira: Pick<JiraApiClient, 'registerDynamicWebhook' | 'deleteDynamicWebhook'>;\n connectionId: string;\n cloudId: string;\n accessToken: string;\n webhookUrl: string;\n now?: () => Date;\n getInstallation?: typeof getJiraInstallationByConnectionId;\n updateInstallation?: typeof updateJiraInstallationWebhook;\n withRegistrationLock?: JiraInstallationLock;\n onRegistrationSuccess?: (input: {tx?: unknown}) => Promise<void>;\n onRegistrationFailure?: (input: {tx?: unknown}) => Promise<void>;\n}\n\n/** Register Jira's curated webhook and replace local metadata as one serialized state transition. */\nexport async function registerJiraWebhook(\n params: RegisterJiraWebhookParams,\n): Promise<{webhookId: number; webhookExpiresAt: Date}> {\n const withRegistrationLock = params.withRegistrationLock ?? withJiraInstallationLock;\n let callbackFailed = false;\n let registeredWebhookId: number | undefined;\n let previous: Awaited<ReturnType<typeof getJiraInstallationByConnectionId>>;\n let metadataPersisted = false;\n let cleanupSupersededWebhooks: (() => Promise<void>) | undefined;\n let result: {webhookId: number; webhookExpiresAt: Date};\n try {\n result = await withRegistrationLock(params.cloudId, async () => {\n try {\n const getInstallation = params.getInstallation ?? getJiraInstallationByConnectionId;\n previous = await getInstallation(params.connectionId);\n const registration = await params.jira.registerDynamicWebhook({\n accessToken: params.accessToken,\n cloudId: params.cloudId,\n url: params.webhookUrl,\n });\n registeredWebhookId = registration.webhookId;\n const now = params.now ?? (() => new Date());\n const webhookExpiresAt = new Date(now().getTime() + JIRA_WEBHOOK_TTL_MS);\n\n const updateInstallation = params.updateInstallation ?? updateJiraInstallationWebhook;\n const updateInput = {\n connectionId: params.connectionId,\n webhookIds: [...new Set([registration.webhookId, ...(previous?.webhookIds ?? [])])],\n webhookExpiresAt,\n };\n const installation = await updateInstallation(updateInput);\n if (!installation)\n throw new Error('Jira webhook registration lost its installation record');\n metadataPersisted = true;\n await params.onRegistrationSuccess?.({});\n\n cleanupSupersededWebhooks = () =>\n finishSupersededWebhookCleanup(\n params,\n previous,\n registration.webhookId,\n webhookExpiresAt,\n );\n return {webhookId: registration.webhookId, webhookExpiresAt};\n } catch (error) {\n callbackFailed = true;\n if (registeredWebhookId !== undefined) {\n const deleted = await deleteNewWebhookAfterPersistenceFailure(\n params,\n registeredWebhookId,\n );\n if (metadataPersisted || !deleted) {\n await restorePreviousWebhookMetadata(\n params,\n previous,\n deleted ? undefined : registeredWebhookId,\n );\n }\n } else if (metadataPersisted) {\n await restorePreviousWebhookMetadata(params, previous);\n }\n await bestEffortRegistrationFailure(params);\n throw error;\n }\n });\n } catch (error) {\n if (!callbackFailed) {\n if (registeredWebhookId !== undefined) {\n const deleted = await deleteNewWebhookAfterPersistenceFailure(params, registeredWebhookId);\n if (metadataPersisted || !deleted) {\n await restorePreviousWebhookMetadata(\n params,\n previous,\n deleted ? undefined : registeredWebhookId,\n );\n }\n } else if (metadataPersisted) {\n await restorePreviousWebhookMetadata(params, previous);\n }\n await bestEffortRegistrationFailure(params);\n }\n throw error;\n }\n\n if (cleanupSupersededWebhooks) {\n try {\n await withRegistrationLock(params.cloudId, cleanupSupersededWebhooks);\n } catch (error) {\n logger().warn(\n {err: error, connectionId: params.connectionId},\n 'Jira superseded webhook cleanup persistence failed',\n );\n }\n }\n\n return result;\n}\n\nasync function finishSupersededWebhookCleanup(\n params: RegisterJiraWebhookParams,\n previous: Awaited<ReturnType<typeof getJiraInstallationByConnectionId>>,\n registeredWebhookId: number,\n webhookExpiresAt: Date,\n): Promise<void> {\n const supersededWebhookIds = (previous?.webhookIds ?? []).filter(\n (webhookId) => webhookId !== registeredWebhookId,\n );\n if (supersededWebhookIds.length === 0) return;\n\n const failedCleanupIds: number[] = [];\n for (const webhookId of supersededWebhookIds) {\n try {\n await params.jira.deleteDynamicWebhook({\n accessToken: params.accessToken,\n cloudId: params.cloudId,\n webhookId,\n });\n } catch (error) {\n failedCleanupIds.push(webhookId);\n logger().warn(\n {err: error, connectionId: params.connectionId, webhookId},\n 'Jira superseded webhook cleanup failed',\n );\n }\n }\n\n const retainedWebhookIds = [registeredWebhookId, ...failedCleanupIds];\n const updateInstallation = params.updateInstallation ?? updateJiraInstallationWebhook;\n const cleanupMetadata = await updateInstallation({\n connectionId: params.connectionId,\n webhookIds: retainedWebhookIds,\n webhookExpiresAt,\n });\n if (!cleanupMetadata)\n throw new Error('Jira webhook cleanup metadata persistence returned no installation');\n}\n\nasync function bestEffortRegistrationFailure(params: RegisterJiraWebhookParams): Promise<void> {\n try {\n await params.onRegistrationFailure?.({});\n } catch (error) {\n logger().warn(\n {err: error, connectionId: params.connectionId},\n 'Jira connection error-state update failed after webhook registration rejection',\n );\n }\n}\n\nasync function restorePreviousWebhookMetadata(\n params: RegisterJiraWebhookParams,\n previous: Awaited<ReturnType<typeof getJiraInstallationByConnectionId>>,\n retainedWebhookId?: number,\n): Promise<void> {\n try {\n const updateInstallation = params.updateInstallation ?? updateJiraInstallationWebhook;\n await updateInstallation({\n connectionId: params.connectionId,\n webhookIds: [\n ...(retainedWebhookId === undefined ? [] : [retainedWebhookId]),\n ...(previous?.webhookIds ?? []).filter((webhookId) => webhookId !== retainedWebhookId),\n ],\n webhookExpiresAt: previous?.webhookExpiresAt ?? null,\n });\n } catch (error) {\n logger().warn(\n {err: error, connectionId: params.connectionId},\n 'Jira webhook metadata restoration failed after registration rejection',\n );\n }\n}\n\nasync function deleteNewWebhookAfterPersistenceFailure(\n params: RegisterJiraWebhookParams,\n webhookId: number,\n): Promise<boolean> {\n try {\n await params.jira.deleteDynamicWebhook({\n accessToken: params.accessToken,\n cloudId: params.cloudId,\n webhookId,\n });\n return true;\n } catch (cleanupError) {\n logger().warn(\n {err: cleanupError, connectionId: params.connectionId, webhookId},\n 'Jira webhook cleanup failed after metadata persistence rejection',\n );\n return false;\n }\n}\n"],"names":["logger","getJiraInstallationByConnectionId","updateJiraInstallationWebhook","withJiraInstallationLock","JIRA_WEBHOOK_TTL_MS","registerJiraWebhook","params","withRegistrationLock","callbackFailed","registeredWebhookId","previous","metadataPersisted","cleanupSupersededWebhooks","result","cloudId","getInstallation","connectionId","registration","jira","registerDynamicWebhook","accessToken","url","webhookUrl","webhookId","now","Date","webhookExpiresAt","getTime","updateInstallation","updateInput","webhookIds","Set","installation","Error","onRegistrationSuccess","finishSupersededWebhookCleanup","error","undefined","deleted","deleteNewWebhookAfterPersistenceFailure","restorePreviousWebhookMetadata","bestEffortRegistrationFailure","warn","err","supersededWebhookIds","filter","length","failedCleanupIds","deleteDynamicWebhook","push","retainedWebhookIds","cleanupMetadata","onRegistrationFailure","retainedWebhookId","cleanupError"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/core/webhook-registration.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport type {JiraApiClient} from '#api/client.js';\nimport {\n getJiraInstallationByConnectionId,\n type JiraInstallationLock,\n updateJiraInstallationWebhook,\n withJiraInstallationLock,\n} from '#db/installations.js';\n\nexport const JIRA_WEBHOOK_TTL_MS = 30 * 24 * 60 * 60 * 1000;\n\nexport interface RegisterJiraWebhookParams {\n jira: Pick<JiraApiClient, 'registerDynamicWebhook' | 'deleteDynamicWebhook'>;\n connectionId: string;\n cloudId: string;\n accessToken: string;\n webhookUrl: string;\n now?: () => Date;\n getInstallation?: typeof getJiraInstallationByConnectionId;\n updateInstallation?: typeof updateJiraInstallationWebhook;\n withRegistrationLock?: JiraInstallationLock;\n replaceExistingWebhooks?: boolean | undefined;\n onRegistrationSuccess?: (input: {tx?: unknown}) => Promise<void>;\n onRegistrationFailure?: (input: {tx?: unknown}) => Promise<void>;\n}\n\n/** Register Jira's curated webhook and replace local metadata as one serialized state transition. */\nexport async function registerJiraWebhook(\n params: RegisterJiraWebhookParams,\n): Promise<{webhookId: number; webhookExpiresAt: Date}> {\n const withRegistrationLock = params.withRegistrationLock ?? withJiraInstallationLock;\n let callbackFailed = false;\n let registeredWebhookId: number | undefined;\n let previous: Awaited<ReturnType<typeof getJiraInstallationByConnectionId>>;\n let metadataPersisted = false;\n let cleanupSupersededWebhooks: (() => Promise<void>) | undefined;\n let result: {webhookId: number; webhookExpiresAt: Date};\n try {\n result = await withRegistrationLock(params.cloudId, async () => {\n try {\n const getInstallation = params.getInstallation ?? getJiraInstallationByConnectionId;\n previous = await getInstallation(params.connectionId);\n const registration = await params.jira.registerDynamicWebhook({\n accessToken: params.accessToken,\n cloudId: params.cloudId,\n url: params.webhookUrl,\n });\n registeredWebhookId = registration.webhookId;\n const now = params.now ?? (() => new Date());\n const webhookExpiresAt = new Date(now().getTime() + JIRA_WEBHOOK_TTL_MS);\n\n const updateInstallation = params.updateInstallation ?? updateJiraInstallationWebhook;\n const updateInput = {\n connectionId: params.connectionId,\n webhookIds: params.replaceExistingWebhooks\n ? [registration.webhookId]\n : [...new Set([registration.webhookId, ...(previous?.webhookIds ?? [])])],\n webhookExpiresAt,\n };\n const installation = await updateInstallation(updateInput);\n if (!installation)\n throw new Error('Jira webhook registration lost its installation record');\n metadataPersisted = true;\n await params.onRegistrationSuccess?.({});\n\n cleanupSupersededWebhooks = () =>\n finishSupersededWebhookCleanup(\n params,\n previous,\n registration.webhookId,\n webhookExpiresAt,\n );\n return {webhookId: registration.webhookId, webhookExpiresAt};\n } catch (error) {\n callbackFailed = true;\n if (registeredWebhookId !== undefined) {\n const deleted = await deleteNewWebhookAfterPersistenceFailure(\n params,\n registeredWebhookId,\n );\n if (metadataPersisted || !deleted) {\n await restorePreviousWebhookMetadata(\n params,\n previous,\n deleted ? undefined : registeredWebhookId,\n );\n }\n } else if (metadataPersisted) {\n await restorePreviousWebhookMetadata(params, previous);\n }\n await bestEffortRegistrationFailure(params);\n throw error;\n }\n });\n } catch (error) {\n if (!callbackFailed) {\n if (registeredWebhookId !== undefined) {\n const deleted = await deleteNewWebhookAfterPersistenceFailure(params, registeredWebhookId);\n if (metadataPersisted || !deleted) {\n await restorePreviousWebhookMetadata(\n params,\n previous,\n deleted ? undefined : registeredWebhookId,\n );\n }\n } else if (metadataPersisted) {\n await restorePreviousWebhookMetadata(params, previous);\n }\n await bestEffortRegistrationFailure(params);\n }\n throw error;\n }\n\n if (cleanupSupersededWebhooks) {\n try {\n await withRegistrationLock(params.cloudId, cleanupSupersededWebhooks);\n } catch (error) {\n logger().warn(\n {err: error, connectionId: params.connectionId},\n 'Jira superseded webhook cleanup persistence failed',\n );\n }\n }\n\n return result;\n}\n\nasync function finishSupersededWebhookCleanup(\n params: RegisterJiraWebhookParams,\n previous: Awaited<ReturnType<typeof getJiraInstallationByConnectionId>>,\n registeredWebhookId: number,\n webhookExpiresAt: Date,\n): Promise<void> {\n const supersededWebhookIds = (previous?.webhookIds ?? []).filter(\n (webhookId) => webhookId !== registeredWebhookId,\n );\n if (supersededWebhookIds.length === 0) return;\n\n const failedCleanupIds: number[] = [];\n for (const webhookId of supersededWebhookIds) {\n try {\n await params.jira.deleteDynamicWebhook({\n accessToken: params.accessToken,\n cloudId: params.cloudId,\n webhookId,\n });\n } catch (error) {\n failedCleanupIds.push(webhookId);\n logger().warn(\n {err: error, connectionId: params.connectionId, webhookId},\n 'Jira superseded webhook cleanup failed',\n );\n }\n }\n\n const retainedWebhookIds = [registeredWebhookId, ...failedCleanupIds];\n const updateInstallation = params.updateInstallation ?? updateJiraInstallationWebhook;\n const cleanupMetadata = await updateInstallation({\n connectionId: params.connectionId,\n webhookIds: retainedWebhookIds,\n webhookExpiresAt,\n });\n if (!cleanupMetadata)\n throw new Error('Jira webhook cleanup metadata persistence returned no installation');\n}\n\nasync function bestEffortRegistrationFailure(params: RegisterJiraWebhookParams): Promise<void> {\n try {\n await params.onRegistrationFailure?.({});\n } catch (error) {\n logger().warn(\n {err: error, connectionId: params.connectionId},\n 'Jira connection error-state update failed after webhook registration rejection',\n );\n }\n}\n\nasync function restorePreviousWebhookMetadata(\n params: RegisterJiraWebhookParams,\n previous: Awaited<ReturnType<typeof getJiraInstallationByConnectionId>>,\n retainedWebhookId?: number,\n): Promise<void> {\n try {\n const updateInstallation = params.updateInstallation ?? updateJiraInstallationWebhook;\n await updateInstallation({\n connectionId: params.connectionId,\n webhookIds: [\n ...(retainedWebhookId === undefined ? [] : [retainedWebhookId]),\n ...(previous?.webhookIds ?? []).filter((webhookId) => webhookId !== retainedWebhookId),\n ],\n webhookExpiresAt: previous?.webhookExpiresAt ?? null,\n });\n } catch (error) {\n logger().warn(\n {err: error, connectionId: params.connectionId},\n 'Jira webhook metadata restoration failed after registration rejection',\n );\n }\n}\n\nasync function deleteNewWebhookAfterPersistenceFailure(\n params: RegisterJiraWebhookParams,\n webhookId: number,\n): Promise<boolean> {\n try {\n await params.jira.deleteDynamicWebhook({\n accessToken: params.accessToken,\n cloudId: params.cloudId,\n webhookId,\n });\n return true;\n } catch (cleanupError) {\n logger().warn(\n {err: cleanupError, connectionId: params.connectionId, webhookId},\n 'Jira webhook cleanup failed after metadata persistence rejection',\n );\n return false;\n }\n}\n"],"names":["logger","getJiraInstallationByConnectionId","updateJiraInstallationWebhook","withJiraInstallationLock","JIRA_WEBHOOK_TTL_MS","registerJiraWebhook","params","withRegistrationLock","callbackFailed","registeredWebhookId","previous","metadataPersisted","cleanupSupersededWebhooks","result","cloudId","getInstallation","connectionId","registration","jira","registerDynamicWebhook","accessToken","url","webhookUrl","webhookId","now","Date","webhookExpiresAt","getTime","updateInstallation","updateInput","webhookIds","replaceExistingWebhooks","Set","installation","Error","onRegistrationSuccess","finishSupersededWebhookCleanup","error","undefined","deleted","deleteNewWebhookAfterPersistenceFailure","restorePreviousWebhookMetadata","bestEffortRegistrationFailure","warn","err","supersededWebhookIds","filter","length","failedCleanupIds","deleteDynamicWebhook","push","retainedWebhookIds","cleanupMetadata","onRegistrationFailure","retainedWebhookId","cleanupError"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-url.d.ts","sourceRoot":"","sources":["../../src/core/webhook-url.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,yBAAyB,gCAAgC,CAAC;AAIvE,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAE3D"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { config } from '#config.js';
|
|
2
|
+
export const JIRA_WEBHOOK_ROUTE_PREFIX = '/webhooks/integrations/jira';
|
|
3
|
+
const TRAILING_SLASHES_RE = /\/+$/;
|
|
4
|
+
export function jiraWebhookUrl(connectionId) {
|
|
5
|
+
return `${config.JIRA_WEBHOOK_BASE_URL.replace(TRAILING_SLASHES_RE, '')}${JIRA_WEBHOOK_ROUTE_PREFIX}/${connectionId}`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=webhook-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/webhook-url.ts"],"sourcesContent":["import {config} from '#config.js';\n\nexport const JIRA_WEBHOOK_ROUTE_PREFIX = '/webhooks/integrations/jira';\n\nconst TRAILING_SLASHES_RE = /\\/+$/;\n\nexport function jiraWebhookUrl(connectionId: string): string {\n return `${config.JIRA_WEBHOOK_BASE_URL.replace(TRAILING_SLASHES_RE, '')}${JIRA_WEBHOOK_ROUTE_PREFIX}/${connectionId}`;\n}\n"],"names":["config","JIRA_WEBHOOK_ROUTE_PREFIX","TRAILING_SLASHES_RE","jiraWebhookUrl","connectionId","JIRA_WEBHOOK_BASE_URL","replace"],"mappings":"AAAA,SAAQA,MAAM,QAAO,aAAa;AAElC,OAAO,MAAMC,4BAA4B,8BAA8B;AAEvE,MAAMC,sBAAsB;AAE5B,OAAO,SAASC,eAAeC,YAAoB;IACjD,OAAO,GAAGJ,OAAOK,qBAAqB,CAACC,OAAO,CAACJ,qBAAqB,MAAMD,0BAA0B,CAAC,EAAEG,cAAc;AACvH"}
|
|
@@ -48,6 +48,10 @@ export interface UpdateJiraInstallationWebhookParams {
|
|
|
48
48
|
webhookIds: number[];
|
|
49
49
|
webhookExpiresAt: Date | null;
|
|
50
50
|
}
|
|
51
|
+
export interface UpdateJiraInstallationWebhookIfUnchangedParams extends UpdateJiraInstallationWebhookParams {
|
|
52
|
+
expectedWebhookIds: number[];
|
|
53
|
+
expectedWebhookExpiresAt: Date | null;
|
|
54
|
+
}
|
|
51
55
|
export declare function upsertJiraInstallation(params: UpsertJiraInstallationParams, options?: {
|
|
52
56
|
tx?: unknown;
|
|
53
57
|
}): Promise<JiraInstallation>;
|
|
@@ -66,9 +70,18 @@ export declare function listJiraInstallationsDueForTokenRefresh(params: {
|
|
|
66
70
|
}, options?: {
|
|
67
71
|
tx?: unknown;
|
|
68
72
|
}): Promise<JiraInstallation[]>;
|
|
73
|
+
export declare function listJiraInstallationsDueForWebhookRenewal(params: {
|
|
74
|
+
before: Date;
|
|
75
|
+
limit?: number | undefined;
|
|
76
|
+
}, options?: {
|
|
77
|
+
tx?: unknown;
|
|
78
|
+
}): Promise<JiraInstallation[]>;
|
|
69
79
|
export declare function updateJiraInstallationWebhook(params: UpdateJiraInstallationWebhookParams, options?: {
|
|
70
80
|
tx?: unknown;
|
|
71
81
|
}): Promise<JiraInstallation | undefined>;
|
|
82
|
+
export declare function updateJiraInstallationWebhookIfUnchanged(params: UpdateJiraInstallationWebhookIfUnchangedParams, options?: {
|
|
83
|
+
tx?: unknown;
|
|
84
|
+
}): Promise<JiraInstallation | undefined>;
|
|
72
85
|
export declare function deleteJiraInstallationByConnectionId(connectionId: string, options?: {
|
|
73
86
|
tx?: unknown;
|
|
74
87
|
}): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../src/db/installations.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"installations.d.ts","sourceRoot":"","sources":["../../src/db/installations.ts"],"names":[],"mappings":"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"}
|
package/dist/db/installations.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isUniqueViolation } from '@shipfox/node-drizzle';
|
|
2
2
|
import { pgClient, withPostgresSession } from '@shipfox/node-postgres';
|
|
3
|
-
import { and, asc, eq, lte, sql } from 'drizzle-orm';
|
|
3
|
+
import { and, asc, eq, isNotNull, isNull, lte, sql } from 'drizzle-orm';
|
|
4
4
|
import { JiraConnectionAlreadyLinkedError, JiraInstallationAlreadyLinkedError, JiraInstallationSiteMismatchError } from '#core/errors.js';
|
|
5
5
|
import { db } from './db.js';
|
|
6
6
|
import { jiraInstallations, toJiraInstallation } from './schema/installations.js';
|
|
@@ -112,6 +112,11 @@ export async function listJiraInstallationsDueForTokenRefresh(params, options =
|
|
|
112
112
|
const rows = await executor.select().from(jiraInstallations).where(and(eq(jiraInstallations.status, 'installed'), lte(jiraInstallations.refreshTokenLastUsedAt, params.before))).orderBy(asc(jiraInstallations.refreshTokenLastAttemptedAt)).limit(params.limit ?? 100);
|
|
113
113
|
return rows.map(toJiraInstallation);
|
|
114
114
|
}
|
|
115
|
+
export async function listJiraInstallationsDueForWebhookRenewal(params, options = {}) {
|
|
116
|
+
const executor = options.tx ?? db();
|
|
117
|
+
const rows = await executor.select().from(jiraInstallations).where(and(eq(jiraInstallations.status, 'installed'), isNotNull(jiraInstallations.webhookExpiresAt), lte(jiraInstallations.webhookExpiresAt, params.before))).orderBy(asc(jiraInstallations.webhookExpiresAt)).limit(params.limit ?? 100);
|
|
118
|
+
return rows.map(toJiraInstallation);
|
|
119
|
+
}
|
|
115
120
|
export async function updateJiraInstallationWebhook(params, options = {}) {
|
|
116
121
|
const executor = options.tx ?? db();
|
|
117
122
|
const [row] = await executor.update(jiraInstallations).set({
|
|
@@ -121,6 +126,18 @@ export async function updateJiraInstallationWebhook(params, options = {}) {
|
|
|
121
126
|
}).where(eq(jiraInstallations.connectionId, params.connectionId)).returning();
|
|
122
127
|
return row ? toJiraInstallation(row) : undefined;
|
|
123
128
|
}
|
|
129
|
+
export async function updateJiraInstallationWebhookIfUnchanged(params, options = {}) {
|
|
130
|
+
const executor = options.tx ?? db();
|
|
131
|
+
const expectedWebhookIds = JSON.stringify([
|
|
132
|
+
...new Set(params.expectedWebhookIds)
|
|
133
|
+
]);
|
|
134
|
+
const [row] = await executor.update(jiraInstallations).set({
|
|
135
|
+
webhookIds: params.webhookIds,
|
|
136
|
+
webhookExpiresAt: params.webhookExpiresAt,
|
|
137
|
+
updatedAt: new Date()
|
|
138
|
+
}).where(and(eq(jiraInstallations.connectionId, params.connectionId), sql`${jiraInstallations.webhookIds} @> ${expectedWebhookIds}::jsonb`, sql`${jiraInstallations.webhookIds} <@ ${expectedWebhookIds}::jsonb`, params.expectedWebhookExpiresAt === null ? isNull(jiraInstallations.webhookExpiresAt) : eq(jiraInstallations.webhookExpiresAt, params.expectedWebhookExpiresAt))).returning();
|
|
139
|
+
return row ? toJiraInstallation(row) : undefined;
|
|
140
|
+
}
|
|
124
141
|
export async function deleteJiraInstallationByConnectionId(connectionId, options = {}) {
|
|
125
142
|
const executor = options.tx ?? db();
|
|
126
143
|
const result = await executor.delete(jiraInstallations).where(eq(jiraInstallations.connectionId, connectionId));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/db/installations.ts"],"sourcesContent":["import {isUniqueViolation} from '@shipfox/node-drizzle';\nimport {pgClient, withPostgresSession} from '@shipfox/node-postgres';\nimport {and, asc, eq, lte, sql} from 'drizzle-orm';\nimport {\n JiraConnectionAlreadyLinkedError,\n JiraInstallationAlreadyLinkedError,\n JiraInstallationSiteMismatchError,\n} from '#core/errors.js';\nimport {db} from './db.js';\nimport {jiraInstallations, toJiraInstallation} from './schema/installations.js';\n\nexport type JiraInstallationLock = <T>(lockKey: string, fn: () => Promise<T>) => Promise<T>;\n\nconst JIRA_ADVISORY_LOCK_RETRY_DELAY_MS = 100;\nconst JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS = 1_000;\nconst JIRA_ADVISORY_LOCK_TIMEOUT_MS = 30_000;\n\ntype JiraAdvisoryLockAttempt<T> = {acquired: true; value: T} | {acquired: false};\n\nexport function withJiraInstallationLock<T>(lockKey: string, fn: () => Promise<T>) {\n const advisoryKey = `jira-installation:${lockKey}`;\n return withJiraAdvisoryLockWait(`Jira installation lock: ${lockKey}`, () =>\n withPostgresSession(async (client) => {\n const lock = await client.query<{acquired: boolean}>(\n 'SELECT pg_try_advisory_lock(hashtext($1)) AS acquired',\n [advisoryKey],\n );\n if (lock.rows[0]?.acquired !== true) return {acquired: false as const};\n\n try {\n return {acquired: true as const, value: await fn()};\n } finally {\n await client.query('SELECT pg_advisory_unlock(hashtext($1))', [advisoryKey]);\n }\n }),\n );\n}\n\nexport type JiraInstallationStatus = 'installed' | 'revoked';\n\nexport interface JiraInstallation {\n id: string;\n connectionId: string;\n cloudId: string;\n siteUrl: string;\n siteName: string;\n authorizingAccountId: string;\n scopes: string[];\n webhookIds: number[];\n webhookExpiresAt: Date | null;\n status: JiraInstallationStatus;\n tokenExpiresAt: Date | null;\n refreshTokenLastUsedAt: Date;\n refreshTokenLastAttemptedAt: Date;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface UpsertJiraInstallationParams {\n connectionId: string;\n cloudId: string;\n siteUrl: string;\n siteName: string;\n authorizingAccountId: string;\n scopes: string[];\n webhookIds?: number[] | undefined;\n webhookExpiresAt?: Date | null | undefined;\n status: JiraInstallationStatus;\n tokenExpiresAt?: Date | null | undefined;\n refreshTokenLastUsedAt?: Date | undefined;\n refreshTokenLastAttemptedAt?: Date | undefined;\n}\n\nexport interface UpdateJiraInstallationTokenExpiryParams {\n connectionId: string;\n tokenExpiresAt: Date | null;\n scopes?: string[] | undefined;\n}\n\nexport interface UpdateJiraInstallationWebhookParams {\n connectionId: string;\n webhookIds: number[];\n webhookExpiresAt: Date | null;\n}\n\ntype JiraDb = ReturnType<typeof db>;\ntype JiraTx = Parameters<Parameters<JiraDb['transaction']>[0]>[0];\n\nexport async function upsertJiraInstallation(\n params: UpsertJiraInstallationParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const now = new Date();\n let row: typeof jiraInstallations.$inferSelect | undefined;\n try {\n [row] = await executor\n .insert(jiraInstallations)\n .values({\n connectionId: params.connectionId,\n cloudId: params.cloudId,\n siteUrl: params.siteUrl,\n siteName: params.siteName,\n authorizingAccountId: params.authorizingAccountId,\n scopes: params.scopes,\n webhookIds: params.webhookIds ?? [],\n webhookExpiresAt: params.webhookExpiresAt ?? null,\n status: params.status,\n tokenExpiresAt: params.tokenExpiresAt ?? null,\n refreshTokenLastUsedAt: params.refreshTokenLastUsedAt ?? now,\n refreshTokenLastAttemptedAt: params.refreshTokenLastAttemptedAt ?? now,\n })\n .onConflictDoUpdate({\n target: jiraInstallations.connectionId,\n setWhere: eq(jiraInstallations.cloudId, params.cloudId),\n set: {\n cloudId: params.cloudId,\n siteUrl: params.siteUrl,\n siteName: params.siteName,\n authorizingAccountId: params.authorizingAccountId,\n scopes: params.scopes,\n ...(params.webhookIds === undefined ? {} : {webhookIds: params.webhookIds}),\n ...(params.webhookExpiresAt === undefined\n ? {}\n : {webhookExpiresAt: params.webhookExpiresAt}),\n status: params.status,\n tokenExpiresAt: params.tokenExpiresAt ?? null,\n refreshTokenLastUsedAt: params.refreshTokenLastUsedAt ?? now,\n refreshTokenLastAttemptedAt: params.refreshTokenLastAttemptedAt ?? now,\n updatedAt: now,\n },\n })\n .returning();\n } catch (error) {\n if (isUniqueViolation(error, 'integrations_jira_installations_connection_unique')) {\n throw new JiraConnectionAlreadyLinkedError(params.connectionId);\n }\n if (isUniqueViolation(error, 'integrations_jira_installations_cloud_id_unique')) {\n throw new JiraInstallationAlreadyLinkedError(params.cloudId);\n }\n throw error;\n }\n\n if (!row) throw new JiraInstallationSiteMismatchError(params.connectionId, params.cloudId);\n return toJiraInstallation(row);\n}\n\nexport async function getJiraInstallationByCloudId(\n cloudId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(eq(jiraInstallations.cloudId, cloudId))\n .limit(1);\n const row = rows[0];\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function updateJiraInstallationTokenExpiry(\n params: UpdateJiraInstallationTokenExpiryParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const now = new Date();\n const [row] = await executor\n .update(jiraInstallations)\n .set({\n tokenExpiresAt: params.tokenExpiresAt,\n refreshTokenLastUsedAt: now,\n refreshTokenLastAttemptedAt: now,\n ...(params.scopes === undefined ? {} : {scopes: params.scopes}),\n updatedAt: now,\n })\n .where(eq(jiraInstallations.connectionId, params.connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function markJiraInstallationTokenRefreshAttempt(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({refreshTokenLastAttemptedAt: new Date()})\n .where(eq(jiraInstallations.connectionId, connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function listJiraInstallationsDueForTokenRefresh(\n params: {before: Date; limit?: number | undefined},\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation[]> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(\n and(\n eq(jiraInstallations.status, 'installed'),\n lte(jiraInstallations.refreshTokenLastUsedAt, params.before),\n ),\n )\n .orderBy(asc(jiraInstallations.refreshTokenLastAttemptedAt))\n .limit(params.limit ?? 100);\n return rows.map(toJiraInstallation);\n}\n\nexport async function updateJiraInstallationWebhook(\n params: UpdateJiraInstallationWebhookParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({\n webhookIds: params.webhookIds,\n webhookExpiresAt: params.webhookExpiresAt,\n updatedAt: new Date(),\n })\n .where(eq(jiraInstallations.connectionId, params.connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function deleteJiraInstallationByConnectionId(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<boolean> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const result = await executor\n .delete(jiraInstallations)\n .where(eq(jiraInstallations.connectionId, connectionId));\n return (result.rowCount ?? 0) > 0;\n}\n\nexport type JiraRefreshLockResult<T> = JiraAdvisoryLockAttempt<T>;\n\nexport function withJiraRefreshLock<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<JiraRefreshLockResult<T>> {\n return withJiraRefreshLockClient(connectionId, fn);\n}\n\nexport function withJiraRefreshLockAndWait<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withJiraAdvisoryLockWait(`Jira refresh lock: ${connectionId}`, () =>\n withJiraRefreshLock(connectionId, fn),\n );\n}\n\nexport function withJiraWebhookRegistrationLock<T>(\n lockKey: string,\n fn: (tx?: unknown) => Promise<T>,\n): Promise<T> {\n return withJiraInstallationLock(lockKey, () => fn());\n}\n\nasync function withJiraRefreshLockClient<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<JiraRefreshLockResult<T>> {\n const client = await pgClient().connect();\n let acquired = false;\n try {\n const lock = await client.query<{acquired: boolean}>(\n 'SELECT pg_try_advisory_lock(hashtext($1)) AS acquired',\n [connectionId],\n );\n acquired = lock.rows[0]?.acquired === true;\n if (!acquired) return {acquired: false};\n return {acquired: true, value: await fn()};\n } finally {\n try {\n if (acquired) await client.query('SELECT pg_advisory_unlock(hashtext($1))', [connectionId]);\n } finally {\n client.release();\n }\n }\n}\n\nasync function withJiraAdvisoryLockWait<T>(\n lockDescription: string,\n attemptLock: () => Promise<JiraAdvisoryLockAttempt<T>>,\n): Promise<T> {\n const deadline = Date.now() + JIRA_ADVISORY_LOCK_TIMEOUT_MS;\n let retryDelayMs = JIRA_ADVISORY_LOCK_RETRY_DELAY_MS;\n\n while (true) {\n const attempt = await attemptLock();\n if (attempt.acquired) return attempt.value;\n if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${lockDescription}`);\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs));\n retryDelayMs = Math.min(retryDelayMs * 2, JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS);\n }\n}\n\nexport async function getJiraInstallationByConnectionId(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(eq(jiraInstallations.connectionId, connectionId))\n .limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n\nexport async function getJiraInstallationByWebhookId(\n webhookId: number,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(sql`${jiraInstallations.webhookIds} @> ${JSON.stringify([webhookId])}::jsonb`)\n .limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n\nexport async function markJiraInstallationRevoked(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({status: 'revoked', updatedAt: new Date()})\n .where(eq(jiraInstallations.connectionId, connectionId))\n .returning();\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n"],"names":["isUniqueViolation","pgClient","withPostgresSession","and","asc","eq","lte","sql","JiraConnectionAlreadyLinkedError","JiraInstallationAlreadyLinkedError","JiraInstallationSiteMismatchError","db","jiraInstallations","toJiraInstallation","JIRA_ADVISORY_LOCK_RETRY_DELAY_MS","JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS","JIRA_ADVISORY_LOCK_TIMEOUT_MS","withJiraInstallationLock","lockKey","fn","advisoryKey","withJiraAdvisoryLockWait","client","lock","query","rows","acquired","value","upsertJiraInstallation","params","options","executor","tx","now","Date","row","insert","values","connectionId","cloudId","siteUrl","siteName","authorizingAccountId","scopes","webhookIds","webhookExpiresAt","status","tokenExpiresAt","refreshTokenLastUsedAt","refreshTokenLastAttemptedAt","onConflictDoUpdate","target","setWhere","set","undefined","updatedAt","returning","error","getJiraInstallationByCloudId","select","from","where","limit","updateJiraInstallationTokenExpiry","update","markJiraInstallationTokenRefreshAttempt","listJiraInstallationsDueForTokenRefresh","before","orderBy","map","updateJiraInstallationWebhook","deleteJiraInstallationByConnectionId","result","delete","rowCount","withJiraRefreshLock","withJiraRefreshLockClient","withJiraRefreshLockAndWait","withJiraWebhookRegistrationLock","connect","release","lockDescription","attemptLock","deadline","retryDelayMs","attempt","Error","Promise","resolve","setTimeout","Math","min","getJiraInstallationByConnectionId","getJiraInstallationByWebhookId","webhookId","JSON","stringify","markJiraInstallationRevoked"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/db/installations.ts"],"sourcesContent":["import {isUniqueViolation} from '@shipfox/node-drizzle';\nimport {pgClient, withPostgresSession} from '@shipfox/node-postgres';\nimport {and, asc, eq, isNotNull, isNull, lte, sql} from 'drizzle-orm';\nimport {\n JiraConnectionAlreadyLinkedError,\n JiraInstallationAlreadyLinkedError,\n JiraInstallationSiteMismatchError,\n} from '#core/errors.js';\nimport {db} from './db.js';\nimport {jiraInstallations, toJiraInstallation} from './schema/installations.js';\n\nexport type JiraInstallationLock = <T>(lockKey: string, fn: () => Promise<T>) => Promise<T>;\n\nconst JIRA_ADVISORY_LOCK_RETRY_DELAY_MS = 100;\nconst JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS = 1_000;\nconst JIRA_ADVISORY_LOCK_TIMEOUT_MS = 30_000;\n\ntype JiraAdvisoryLockAttempt<T> = {acquired: true; value: T} | {acquired: false};\n\nexport function withJiraInstallationLock<T>(lockKey: string, fn: () => Promise<T>) {\n const advisoryKey = `jira-installation:${lockKey}`;\n return withJiraAdvisoryLockWait(`Jira installation lock: ${lockKey}`, () =>\n withPostgresSession(async (client) => {\n const lock = await client.query<{acquired: boolean}>(\n 'SELECT pg_try_advisory_lock(hashtext($1)) AS acquired',\n [advisoryKey],\n );\n if (lock.rows[0]?.acquired !== true) return {acquired: false as const};\n\n try {\n return {acquired: true as const, value: await fn()};\n } finally {\n await client.query('SELECT pg_advisory_unlock(hashtext($1))', [advisoryKey]);\n }\n }),\n );\n}\n\nexport type JiraInstallationStatus = 'installed' | 'revoked';\n\nexport interface JiraInstallation {\n id: string;\n connectionId: string;\n cloudId: string;\n siteUrl: string;\n siteName: string;\n authorizingAccountId: string;\n scopes: string[];\n webhookIds: number[];\n webhookExpiresAt: Date | null;\n status: JiraInstallationStatus;\n tokenExpiresAt: Date | null;\n refreshTokenLastUsedAt: Date;\n refreshTokenLastAttemptedAt: Date;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface UpsertJiraInstallationParams {\n connectionId: string;\n cloudId: string;\n siteUrl: string;\n siteName: string;\n authorizingAccountId: string;\n scopes: string[];\n webhookIds?: number[] | undefined;\n webhookExpiresAt?: Date | null | undefined;\n status: JiraInstallationStatus;\n tokenExpiresAt?: Date | null | undefined;\n refreshTokenLastUsedAt?: Date | undefined;\n refreshTokenLastAttemptedAt?: Date | undefined;\n}\n\nexport interface UpdateJiraInstallationTokenExpiryParams {\n connectionId: string;\n tokenExpiresAt: Date | null;\n scopes?: string[] | undefined;\n}\n\nexport interface UpdateJiraInstallationWebhookParams {\n connectionId: string;\n webhookIds: number[];\n webhookExpiresAt: Date | null;\n}\n\nexport interface UpdateJiraInstallationWebhookIfUnchangedParams\n extends UpdateJiraInstallationWebhookParams {\n expectedWebhookIds: number[];\n expectedWebhookExpiresAt: Date | null;\n}\n\ntype JiraDb = ReturnType<typeof db>;\ntype JiraTx = Parameters<Parameters<JiraDb['transaction']>[0]>[0];\n\nexport async function upsertJiraInstallation(\n params: UpsertJiraInstallationParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const now = new Date();\n let row: typeof jiraInstallations.$inferSelect | undefined;\n try {\n [row] = await executor\n .insert(jiraInstallations)\n .values({\n connectionId: params.connectionId,\n cloudId: params.cloudId,\n siteUrl: params.siteUrl,\n siteName: params.siteName,\n authorizingAccountId: params.authorizingAccountId,\n scopes: params.scopes,\n webhookIds: params.webhookIds ?? [],\n webhookExpiresAt: params.webhookExpiresAt ?? null,\n status: params.status,\n tokenExpiresAt: params.tokenExpiresAt ?? null,\n refreshTokenLastUsedAt: params.refreshTokenLastUsedAt ?? now,\n refreshTokenLastAttemptedAt: params.refreshTokenLastAttemptedAt ?? now,\n })\n .onConflictDoUpdate({\n target: jiraInstallations.connectionId,\n setWhere: eq(jiraInstallations.cloudId, params.cloudId),\n set: {\n cloudId: params.cloudId,\n siteUrl: params.siteUrl,\n siteName: params.siteName,\n authorizingAccountId: params.authorizingAccountId,\n scopes: params.scopes,\n ...(params.webhookIds === undefined ? {} : {webhookIds: params.webhookIds}),\n ...(params.webhookExpiresAt === undefined\n ? {}\n : {webhookExpiresAt: params.webhookExpiresAt}),\n status: params.status,\n tokenExpiresAt: params.tokenExpiresAt ?? null,\n refreshTokenLastUsedAt: params.refreshTokenLastUsedAt ?? now,\n refreshTokenLastAttemptedAt: params.refreshTokenLastAttemptedAt ?? now,\n updatedAt: now,\n },\n })\n .returning();\n } catch (error) {\n if (isUniqueViolation(error, 'integrations_jira_installations_connection_unique')) {\n throw new JiraConnectionAlreadyLinkedError(params.connectionId);\n }\n if (isUniqueViolation(error, 'integrations_jira_installations_cloud_id_unique')) {\n throw new JiraInstallationAlreadyLinkedError(params.cloudId);\n }\n throw error;\n }\n\n if (!row) throw new JiraInstallationSiteMismatchError(params.connectionId, params.cloudId);\n return toJiraInstallation(row);\n}\n\nexport async function getJiraInstallationByCloudId(\n cloudId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(eq(jiraInstallations.cloudId, cloudId))\n .limit(1);\n const row = rows[0];\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function updateJiraInstallationTokenExpiry(\n params: UpdateJiraInstallationTokenExpiryParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const now = new Date();\n const [row] = await executor\n .update(jiraInstallations)\n .set({\n tokenExpiresAt: params.tokenExpiresAt,\n refreshTokenLastUsedAt: now,\n refreshTokenLastAttemptedAt: now,\n ...(params.scopes === undefined ? {} : {scopes: params.scopes}),\n updatedAt: now,\n })\n .where(eq(jiraInstallations.connectionId, params.connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function markJiraInstallationTokenRefreshAttempt(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({refreshTokenLastAttemptedAt: new Date()})\n .where(eq(jiraInstallations.connectionId, connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function listJiraInstallationsDueForTokenRefresh(\n params: {before: Date; limit?: number | undefined},\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation[]> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(\n and(\n eq(jiraInstallations.status, 'installed'),\n lte(jiraInstallations.refreshTokenLastUsedAt, params.before),\n ),\n )\n .orderBy(asc(jiraInstallations.refreshTokenLastAttemptedAt))\n .limit(params.limit ?? 100);\n return rows.map(toJiraInstallation);\n}\n\nexport async function listJiraInstallationsDueForWebhookRenewal(\n params: {before: Date; limit?: number | undefined},\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation[]> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(\n and(\n eq(jiraInstallations.status, 'installed'),\n isNotNull(jiraInstallations.webhookExpiresAt),\n lte(jiraInstallations.webhookExpiresAt, params.before),\n ),\n )\n .orderBy(asc(jiraInstallations.webhookExpiresAt))\n .limit(params.limit ?? 100);\n return rows.map(toJiraInstallation);\n}\n\nexport async function updateJiraInstallationWebhook(\n params: UpdateJiraInstallationWebhookParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({\n webhookIds: params.webhookIds,\n webhookExpiresAt: params.webhookExpiresAt,\n updatedAt: new Date(),\n })\n .where(eq(jiraInstallations.connectionId, params.connectionId))\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function updateJiraInstallationWebhookIfUnchanged(\n params: UpdateJiraInstallationWebhookIfUnchangedParams,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const expectedWebhookIds = JSON.stringify([...new Set(params.expectedWebhookIds)]);\n const [row] = await executor\n .update(jiraInstallations)\n .set({\n webhookIds: params.webhookIds,\n webhookExpiresAt: params.webhookExpiresAt,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(jiraInstallations.connectionId, params.connectionId),\n sql`${jiraInstallations.webhookIds} @> ${expectedWebhookIds}::jsonb`,\n sql`${jiraInstallations.webhookIds} <@ ${expectedWebhookIds}::jsonb`,\n params.expectedWebhookExpiresAt === null\n ? isNull(jiraInstallations.webhookExpiresAt)\n : eq(jiraInstallations.webhookExpiresAt, params.expectedWebhookExpiresAt),\n ),\n )\n .returning();\n return row ? toJiraInstallation(row) : undefined;\n}\n\nexport async function deleteJiraInstallationByConnectionId(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<boolean> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const result = await executor\n .delete(jiraInstallations)\n .where(eq(jiraInstallations.connectionId, connectionId));\n return (result.rowCount ?? 0) > 0;\n}\n\nexport type JiraRefreshLockResult<T> = JiraAdvisoryLockAttempt<T>;\n\nexport function withJiraRefreshLock<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<JiraRefreshLockResult<T>> {\n return withJiraRefreshLockClient(connectionId, fn);\n}\n\nexport function withJiraRefreshLockAndWait<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withJiraAdvisoryLockWait(`Jira refresh lock: ${connectionId}`, () =>\n withJiraRefreshLock(connectionId, fn),\n );\n}\n\nexport function withJiraWebhookRegistrationLock<T>(\n lockKey: string,\n fn: (tx?: unknown) => Promise<T>,\n): Promise<T> {\n return withJiraInstallationLock(lockKey, () => fn());\n}\n\nasync function withJiraRefreshLockClient<T>(\n connectionId: string,\n fn: () => Promise<T>,\n): Promise<JiraRefreshLockResult<T>> {\n const client = await pgClient().connect();\n let acquired = false;\n try {\n const lock = await client.query<{acquired: boolean}>(\n 'SELECT pg_try_advisory_lock(hashtext($1)) AS acquired',\n [connectionId],\n );\n acquired = lock.rows[0]?.acquired === true;\n if (!acquired) return {acquired: false};\n return {acquired: true, value: await fn()};\n } finally {\n try {\n if (acquired) await client.query('SELECT pg_advisory_unlock(hashtext($1))', [connectionId]);\n } finally {\n client.release();\n }\n }\n}\n\nasync function withJiraAdvisoryLockWait<T>(\n lockDescription: string,\n attemptLock: () => Promise<JiraAdvisoryLockAttempt<T>>,\n): Promise<T> {\n const deadline = Date.now() + JIRA_ADVISORY_LOCK_TIMEOUT_MS;\n let retryDelayMs = JIRA_ADVISORY_LOCK_RETRY_DELAY_MS;\n\n while (true) {\n const attempt = await attemptLock();\n if (attempt.acquired) return attempt.value;\n if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${lockDescription}`);\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs));\n retryDelayMs = Math.min(retryDelayMs * 2, JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS);\n }\n}\n\nexport async function getJiraInstallationByConnectionId(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(eq(jiraInstallations.connectionId, connectionId))\n .limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n\nexport async function getJiraInstallationByWebhookId(\n webhookId: number,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const rows = await executor\n .select()\n .from(jiraInstallations)\n .where(sql`${jiraInstallations.webhookIds} @> ${JSON.stringify([webhookId])}::jsonb`)\n .limit(1);\n const row = rows[0];\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n\nexport async function markJiraInstallationRevoked(\n connectionId: string,\n options: {tx?: unknown} = {},\n): Promise<JiraInstallation | undefined> {\n const executor = (options.tx ?? db()) as JiraDb | JiraTx;\n const [row] = await executor\n .update(jiraInstallations)\n .set({status: 'revoked', updatedAt: new Date()})\n .where(eq(jiraInstallations.connectionId, connectionId))\n .returning();\n if (!row) return undefined;\n return toJiraInstallation(row);\n}\n"],"names":["isUniqueViolation","pgClient","withPostgresSession","and","asc","eq","isNotNull","isNull","lte","sql","JiraConnectionAlreadyLinkedError","JiraInstallationAlreadyLinkedError","JiraInstallationSiteMismatchError","db","jiraInstallations","toJiraInstallation","JIRA_ADVISORY_LOCK_RETRY_DELAY_MS","JIRA_ADVISORY_LOCK_MAX_RETRY_DELAY_MS","JIRA_ADVISORY_LOCK_TIMEOUT_MS","withJiraInstallationLock","lockKey","fn","advisoryKey","withJiraAdvisoryLockWait","client","lock","query","rows","acquired","value","upsertJiraInstallation","params","options","executor","tx","now","Date","row","insert","values","connectionId","cloudId","siteUrl","siteName","authorizingAccountId","scopes","webhookIds","webhookExpiresAt","status","tokenExpiresAt","refreshTokenLastUsedAt","refreshTokenLastAttemptedAt","onConflictDoUpdate","target","setWhere","set","undefined","updatedAt","returning","error","getJiraInstallationByCloudId","select","from","where","limit","updateJiraInstallationTokenExpiry","update","markJiraInstallationTokenRefreshAttempt","listJiraInstallationsDueForTokenRefresh","before","orderBy","map","listJiraInstallationsDueForWebhookRenewal","updateJiraInstallationWebhook","updateJiraInstallationWebhookIfUnchanged","expectedWebhookIds","JSON","stringify","Set","expectedWebhookExpiresAt","deleteJiraInstallationByConnectionId","result","delete","rowCount","withJiraRefreshLock","withJiraRefreshLockClient","withJiraRefreshLockAndWait","withJiraWebhookRegistrationLock","connect","release","lockDescription","attemptLock","deadline","retryDelayMs","attempt","Error","Promise","resolve","setTimeout","Math","min","getJiraInstallationByConnectionId","getJiraInstallationByWebhookId","webhookId","markJiraInstallationRevoked"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ export type { JiraAgentToolCatalogEntry, JiraAgentToolId, JiraAgentToolRequiredS
|
|
|
14
14
|
export { jiraAgentToolCatalog, jiraAgentToolSelectionCatalog, jiraPlainTextToAdf, } from '#core/agent-tools.js';
|
|
15
15
|
export type { JiraAgentToolsProviderOptions, JiraToolCallResult, } from '#core/agent-tools-provider.js';
|
|
16
16
|
export { JiraAgentToolsProvider } from '#core/agent-tools-provider.js';
|
|
17
|
-
export type { DisconnectJiraInstallationParams } from '#core/disconnect.js';
|
|
18
|
-
export { disconnectJiraInstallation } from '#core/disconnect.js';
|
|
17
|
+
export type { DeregisterJiraWebhooksParams, DisconnectJiraInstallationParams, } from '#core/disconnect.js';
|
|
18
|
+
export { deregisterJiraWebhooks, disconnectJiraInstallation, prepareJiraWebhookDeregistration, } from '#core/disconnect.js';
|
|
19
19
|
export { JiraAccessTokenMissingError, JiraAuthorizationScopeMismatchError, JiraConnectionAlreadyLinkedError, JiraConnectionNotFoundError, JiraInstallationAlreadyLinkedError, JiraInstallationSiteMismatchError, JiraInstallStateActorMismatchError, JiraInstallStateError, JiraIntegrationProviderError, JiraOAuthCallbackError, JiraOfflineAccessNotGrantedError, JiraPendingSelectionNotFoundError, JiraSiteSelectionMismatchError, JiraTokenUnrefreshableError, } from '#core/errors.js';
|
|
20
20
|
export type { ConnectJiraInstallationInput, HandleJiraCallbackParams } from '#core/install.js';
|
|
21
21
|
export { handleJiraCallback, handleJiraOAuthCallbackError, handleJiraSiteSelection, } from '#core/install.js';
|
|
@@ -29,8 +29,9 @@ export type { CreateJiraWebhookProcessorOptions, JiraWebhookProcessor, } from '#
|
|
|
29
29
|
export { createJiraWebhookProcessor } from '#core/webhook-processor.js';
|
|
30
30
|
export type { RegisterJiraWebhookParams } from '#core/webhook-registration.js';
|
|
31
31
|
export { JIRA_WEBHOOK_TTL_MS, registerJiraWebhook } from '#core/webhook-registration.js';
|
|
32
|
-
export
|
|
33
|
-
export {
|
|
32
|
+
export { JIRA_WEBHOOK_ROUTE_PREFIX, jiraWebhookUrl } from '#core/webhook-url.js';
|
|
33
|
+
export type { JiraInstallation, JiraInstallationLock, JiraInstallationStatus, UpdateJiraInstallationWebhookIfUnchangedParams, UpdateJiraInstallationWebhookParams, UpsertJiraInstallationParams, } from '#db/installations.js';
|
|
34
|
+
export { deleteJiraInstallationByConnectionId, getJiraInstallationByCloudId, getJiraInstallationByConnectionId, getJiraInstallationByWebhookId, listJiraInstallationsDueForTokenRefresh, listJiraInstallationsDueForWebhookRenewal, markJiraInstallationRevoked, updateJiraInstallationTokenExpiry, updateJiraInstallationWebhook, updateJiraInstallationWebhookIfUnchanged, upsertJiraInstallation, withJiraRefreshLock, withJiraRefreshLockAndWait, withJiraWebhookRegistrationLock, } from '#db/installations.js';
|
|
34
35
|
export type { CreateJiraWebhookRoutesOptions } from '#presentation/routes/webhooks.js';
|
|
35
36
|
export { createJiraWebhookRoutes } from '#presentation/routes/webhooks.js';
|
|
36
37
|
export { createJiraMaintenanceWorker } from '#temporal/worker.js';
|
|
@@ -43,6 +44,12 @@ export interface CreateJiraIntegrationProviderOptions {
|
|
|
43
44
|
} | undefined;
|
|
44
45
|
getJiraInstallationByConnectionId?: typeof getJiraInstallationByConnectionId | undefined;
|
|
45
46
|
cleanup?: {
|
|
47
|
+
deleteConnectionRemoteResources?: (connection: {
|
|
48
|
+
id: string;
|
|
49
|
+
}) => Promise<(() => Promise<void>) | undefined>;
|
|
50
|
+
withConnectionDeletionLock?: (connection: {
|
|
51
|
+
id: string;
|
|
52
|
+
}, fn: () => Promise<void>) => Promise<void>;
|
|
46
53
|
deleteConnectionRecords?: (connection: {
|
|
47
54
|
id: string;
|
|
48
55
|
}, options: {
|
|
@@ -57,6 +64,12 @@ export interface CreateJiraIntegrationProviderOptions {
|
|
|
57
64
|
}
|
|
58
65
|
type JiraIntegrationProviderRoutesOptions = Omit<CreateJiraIntegrationRoutesOptions, 'jira' | 'connectionCapabilities' | 'registerJiraWebhook'> & Omit<CreateJiraWebhookRoutesOptions, 'processor'>;
|
|
59
66
|
export declare function createJiraIntegrationProvider(options?: CreateJiraIntegrationProviderOptions): {
|
|
67
|
+
deleteConnectionRemoteResources?: (connection: {
|
|
68
|
+
id: string;
|
|
69
|
+
}) => Promise<(() => Promise<void>) | undefined>;
|
|
70
|
+
withConnectionDeletionLock?: (connection: {
|
|
71
|
+
id: string;
|
|
72
|
+
}, fn: () => Promise<void>) => Promise<void>;
|
|
60
73
|
deleteConnectionRecords?: (connection: {
|
|
61
74
|
id: string;
|
|
62
75
|
}, options: {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAC,MAAM,EAAC,MAAM,YAAY,CAAC;AAClC,OAAO,EAAC,sBAAsB,EAAC,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -4,16 +4,16 @@ import { config } from '#config.js';
|
|
|
4
4
|
import { JiraAgentToolsProvider } from '#core/agent-tools-provider.js';
|
|
5
5
|
import { createJiraWebhookProcessor } from '#core/webhook-processor.js';
|
|
6
6
|
import { registerJiraWebhook } from '#core/webhook-registration.js';
|
|
7
|
+
import { jiraWebhookUrl } from '#core/webhook-url.js';
|
|
7
8
|
import { closeDb, db } from '#db/db.js';
|
|
8
9
|
import { getJiraInstallationByConnectionId } from '#db/installations.js';
|
|
9
10
|
import { migrationsPath } from '#db/migrations.js';
|
|
10
11
|
import { createJiraIntegrationRoutes } from '#presentation/routes/install.js';
|
|
11
|
-
import { createJiraWebhookRoutes
|
|
12
|
-
const TRAILING_SLASHES_RE = /\/+$/;
|
|
12
|
+
import { createJiraWebhookRoutes } from '#presentation/routes/webhooks.js';
|
|
13
13
|
export { createJiraAgentToolsClient, createJiraApiClient, JIRA_DYNAMIC_WEBHOOK_EVENTS, JIRA_DYNAMIC_WEBHOOK_JQL, mapJiraError } from '#api/client.js';
|
|
14
14
|
export { jiraAgentToolCatalog, jiraAgentToolSelectionCatalog, jiraPlainTextToAdf } from '#core/agent-tools.js';
|
|
15
15
|
export { JiraAgentToolsProvider } from '#core/agent-tools-provider.js';
|
|
16
|
-
export { disconnectJiraInstallation } from '#core/disconnect.js';
|
|
16
|
+
export { deregisterJiraWebhooks, disconnectJiraInstallation, prepareJiraWebhookDeregistration } from '#core/disconnect.js';
|
|
17
17
|
export { JiraAccessTokenMissingError, JiraAuthorizationScopeMismatchError, JiraConnectionAlreadyLinkedError, JiraConnectionNotFoundError, JiraInstallationAlreadyLinkedError, JiraInstallationSiteMismatchError, JiraInstallStateActorMismatchError, JiraInstallStateError, JiraIntegrationProviderError, JiraOAuthCallbackError, JiraOfflineAccessNotGrantedError, JiraPendingSelectionNotFoundError, JiraSiteSelectionMismatchError, JiraTokenUnrefreshableError } from '#core/errors.js';
|
|
18
18
|
export { handleJiraCallback, handleJiraOAuthCallbackError, handleJiraSiteSelection } from '#core/install.js';
|
|
19
19
|
export { createJiraPendingSelectionStore, jiraPendingSecretsNamespace } from '#core/pending.js';
|
|
@@ -22,7 +22,8 @@ export { signJiraInstallState, verifyJiraInstallState } from '#core/state.js';
|
|
|
22
22
|
export { createJiraTokenStore, jiraSecretsNamespace } from '#core/tokens.js';
|
|
23
23
|
export { createJiraWebhookProcessor } from '#core/webhook-processor.js';
|
|
24
24
|
export { JIRA_WEBHOOK_TTL_MS, registerJiraWebhook } from '#core/webhook-registration.js';
|
|
25
|
-
export {
|
|
25
|
+
export { JIRA_WEBHOOK_ROUTE_PREFIX, jiraWebhookUrl } from '#core/webhook-url.js';
|
|
26
|
+
export { deleteJiraInstallationByConnectionId, getJiraInstallationByCloudId, getJiraInstallationByConnectionId, getJiraInstallationByWebhookId, listJiraInstallationsDueForTokenRefresh, listJiraInstallationsDueForWebhookRenewal, markJiraInstallationRevoked, updateJiraInstallationTokenExpiry, updateJiraInstallationWebhook, updateJiraInstallationWebhookIfUnchanged, upsertJiraInstallation, withJiraRefreshLock, withJiraRefreshLockAndWait, withJiraWebhookRegistrationLock } from '#db/installations.js';
|
|
26
27
|
export { createJiraWebhookRoutes } from '#presentation/routes/webhooks.js';
|
|
27
28
|
export { createJiraMaintenanceWorker } from '#temporal/worker.js';
|
|
28
29
|
export { closeDb, config, db, migrationsPath };
|
|
@@ -91,9 +92,6 @@ export function createJiraIntegrationProvider(options = {}) {
|
|
|
91
92
|
] : undefined
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
|
-
function jiraWebhookUrl(connectionId) {
|
|
95
|
-
return `${config.JIRA_WEBHOOK_BASE_URL.replace(TRAILING_SLASHES_RE, '')}${JIRA_WEBHOOK_ROUTE_PREFIX}/${connectionId}`;
|
|
96
|
-
}
|
|
97
95
|
function toJiraWebhookOptions(routes) {
|
|
98
96
|
if (!routes) return undefined;
|
|
99
97
|
if (routes.coreDb === undefined || routes.publishIntegrationEventReceived === undefined || routes.recordDeliveryOnly === undefined || routes.getIntegrationConnectionById === undefined) throw new Error('Jira integration provider requires all webhook receiver dependencies: coreDb, publishIntegrationEventReceived, recordDeliveryOnly, and getIntegrationConnectionById');
|