@shipfox/api-integration-jira 11.0.0 → 12.1.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 +17 -2
- package/CHANGELOG.md +36 -0
- package/README.md +58 -2
- package/dist/api/client.d.ts +34 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +131 -0
- package/dist/api/client.js.map +1 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -4
- package/dist/config.js.map +1 -1
- package/dist/core/agent-tools-provider.d.ts +26 -0
- package/dist/core/agent-tools-provider.d.ts.map +1 -0
- package/dist/core/agent-tools-provider.js +130 -0
- package/dist/core/agent-tools-provider.js.map +1 -0
- package/dist/core/agent-tools.d.ts +16 -0
- package/dist/core/agent-tools.d.ts.map +1 -0
- package/dist/core/agent-tools.js +441 -0
- package/dist/core/agent-tools.js.map +1 -0
- package/dist/core/install.d.ts +22 -0
- package/dist/core/install.d.ts.map +1 -1
- package/dist/core/install.js +68 -20
- 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 +129 -37
- package/dist/core/tokens.js.map +1 -1
- package/dist/core/webhook-processor.d.ts +15 -0
- package/dist/core/webhook-processor.d.ts.map +1 -0
- package/dist/core/webhook-processor.js +137 -0
- package/dist/core/webhook-processor.js.map +1 -0
- package/dist/core/webhook-registration.d.ts +26 -0
- package/dist/core/webhook-registration.d.ts.map +1 -0
- package/dist/core/webhook-registration.js +165 -0
- package/dist/core/webhook-registration.js.map +1 -0
- package/dist/core/webhook.d.ts +18 -0
- package/dist/core/webhook.d.ts.map +1 -0
- package/dist/core/webhook.js +38 -0
- package/dist/core/webhook.js.map +1 -0
- package/dist/db/db.d.ts +68 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/installations.d.ts +33 -6
- package/dist/db/installations.d.ts.map +1 -1
- package/dist/db/installations.js +79 -7
- package/dist/db/installations.js.map +1 -1
- package/dist/db/schema/installations.d.ts +34 -0
- package/dist/db/schema/installations.d.ts.map +1 -1
- package/dist/db/schema/installations.js +12 -2
- package/dist/db/schema/installations.js.map +1 -1
- package/dist/index.d.ts +34 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -8
- package/dist/index.js.map +1 -1
- package/dist/presentation/routes/install.d.ts +22 -0
- package/dist/presentation/routes/install.d.ts.map +1 -1
- package/dist/presentation/routes/install.js.map +1 -1
- package/dist/presentation/routes/webhooks.d.ts +8 -0
- package/dist/presentation/routes/webhooks.d.ts.map +1 -0
- package/dist/presentation/routes/webhooks.js +108 -0
- package/dist/presentation/routes/webhooks.js.map +1 -0
- package/dist/temporal/activities/index.d.ts +2 -0
- package/dist/temporal/activities/index.d.ts.map +1 -0
- package/dist/temporal/activities/index.js +3 -0
- package/dist/temporal/activities/index.js.map +1 -0
- package/dist/temporal/activities/refresh-jira-tokens.d.ts +20 -0
- package/dist/temporal/activities/refresh-jira-tokens.d.ts.map +1 -0
- package/dist/temporal/activities/refresh-jira-tokens.js +58 -0
- package/dist/temporal/activities/refresh-jira-tokens.js.map +1 -0
- package/dist/temporal/constants.d.ts +6 -0
- package/dist/temporal/constants.d.ts.map +1 -0
- package/dist/temporal/constants.js +8 -0
- package/dist/temporal/constants.js.map +1 -0
- package/dist/temporal/worker.d.ts +9 -0
- package/dist/temporal/worker.d.ts.map +1 -0
- package/dist/temporal/worker.js +26 -0
- package/dist/temporal/worker.js.map +1 -0
- package/dist/temporal/workflows/index.bundle.js +25225 -0
- package/dist/temporal/workflows/index.bundle.meta.json +1 -0
- package/dist/temporal/workflows/index.d.ts +2 -0
- package/dist/temporal/workflows/index.d.ts.map +1 -0
- package/dist/temporal/workflows/index.js +3 -0
- package/dist/temporal/workflows/index.js.map +1 -0
- package/dist/temporal/workflows/refresh-jira-tokens-cron.d.ts +2 -0
- package/dist/temporal/workflows/refresh-jira-tokens-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/refresh-jira-tokens-cron.js +20 -0
- package/dist/temporal/workflows/refresh-jira-tokens-cron.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0001_jira-token-refresh-state.sql +12 -0
- package/drizzle/meta/0001_snapshot.json +245 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +17 -8
- package/src/api/client.test.ts +236 -9
- package/src/api/client.ts +192 -0
- package/src/config.test.ts +0 -1
- package/src/config.ts +1 -4
- package/src/core/agent-tools-provider.test.ts +299 -0
- package/src/core/agent-tools-provider.ts +175 -0
- package/src/core/agent-tools.ts +450 -0
- package/src/core/install.test.ts +179 -0
- package/src/core/install.ts +85 -19
- package/src/core/tokens-refresh.test.ts +223 -4
- package/src/core/tokens.test.ts +1 -1
- package/src/core/tokens.ts +153 -33
- package/src/core/webhook-processor.test.ts +418 -0
- package/src/core/webhook-processor.ts +166 -0
- package/src/core/webhook-registration.test.ts +375 -0
- package/src/core/webhook-registration.ts +216 -0
- package/src/core/webhook.ts +69 -0
- package/src/db/installations.test.ts +148 -2
- package/src/db/installations.ts +133 -7
- package/src/db/schema/installations.ts +21 -1
- package/src/index.test.ts +42 -0
- package/src/index.ts +148 -5
- package/src/presentation/routes/install.ts +12 -0
- package/src/presentation/routes/webhooks.test.ts +132 -0
- package/src/presentation/routes/webhooks.ts +115 -0
- package/src/temporal/activities/index.ts +5 -0
- package/src/temporal/activities/refresh-jira-tokens.test.ts +156 -0
- package/src/temporal/activities/refresh-jira-tokens.ts +88 -0
- package/src/temporal/constants.ts +8 -0
- package/src/temporal/worker.ts +39 -0
- package/src/temporal/workflows/index.ts +1 -0
- package/src/temporal/workflows/refresh-jira-tokens-cron.ts +19 -0
- package/test/env.ts +0 -1
- package/test/factories/jira-installation.ts +4 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
|
-
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
1
|
+
$ shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js
|
|
2
|
+
Successfully compiled: 32 files with swc (419.4ms)
|
|
3
|
+
2026-08-04T14:51:23.448Z [INFO] asset workflow-bundle-21cc907e680c539b74ef.js 3.17 MiB [emitted] [immutable] (name: main)
|
|
4
|
+
orphan modules 33.5 KiB [orphan] 21 modules
|
|
5
|
+
runtime modules 1.13 KiB 5 modules
|
|
6
|
+
modules by path ../../../../node_modules/.pnpm/ 970 KiB 196 modules
|
|
7
|
+
modules by path ./dist/temporal/workflows/ 1.64 KiB
|
|
8
|
+
./dist/temporal/workflows/index-autogenerated-entrypoint.cjs 941 bytes [built] [code generated]
|
|
9
|
+
./dist/temporal/workflows/index.js 72 bytes [built] [code generated]
|
|
10
|
+
./dist/temporal/workflows/refresh-jira-tokens-cron.js 666 bytes [built] [code generated]
|
|
11
|
+
optional modules 30 bytes [optional]
|
|
12
|
+
__temporal_custom_payload_converter (ignored) 15 bytes [optional] [built] [code generated]
|
|
13
|
+
__temporal_custom_failure_converter (ignored) 15 bytes [optional] [built] [code generated]
|
|
14
|
+
../../../shared/node/temporal/dist/workflow-error-interceptor.js 1.21 KiB [built] [code generated]
|
|
15
|
+
webpack 5.107.2 compiled successfully in 7200 ms
|
|
16
|
+
2026-08-04T14:51:23.467Z [INFO] Workflow bundle created { size: '3.17MB' }
|
|
17
|
+
/home/runner/work/shipfox/shipfox/libs/api/integration/jira/dist/temporal/workflows/index.js: 3323887 bytes
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @shipfox/api-integration-jira
|
|
2
2
|
|
|
3
|
+
## 12.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 70e33c0: Adds the in-process Jira REST agent-tool catalog and write-selection support.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- af7adfc: Adds a six-hour Jira refresh-token maintenance worker that refreshes idle tokens and migrates existing installations with backfilled refresh state. Token access now fails closed for non-active connections, and rejected refresh tokens or ambiguous refresh timeouts require reconnecting Jira.
|
|
12
|
+
|
|
13
|
+
## 12.0.0
|
|
14
|
+
|
|
15
|
+
### Major Changes
|
|
16
|
+
|
|
17
|
+
- f13e8bb: Add Jira dynamic webhook registration and authenticated event ingestion through
|
|
18
|
+
the shared stored-webhook workflow. Update the SPI webhook request exports and
|
|
19
|
+
serialize Jira installation replacement across API replicas. Preserve Jira
|
|
20
|
+
delivery identifiers and require lifecycle callbacks for registration. Remove
|
|
21
|
+
the unused Jira webhook signing-secret configuration and allow HS256 verification
|
|
22
|
+
at a supplied receipt time.
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- Updated dependencies [f78740d]
|
|
27
|
+
- Updated dependencies [24ef475]
|
|
28
|
+
- Updated dependencies [f13e8bb]
|
|
29
|
+
- Updated dependencies [869a792]
|
|
30
|
+
- Updated dependencies [54c820e]
|
|
31
|
+
- @shipfox/node-fastify@0.4.1
|
|
32
|
+
- @shipfox/api-integration-spi@1.0.0
|
|
33
|
+
- @shipfox/node-jwt@0.4.0
|
|
34
|
+
- @shipfox/node-postgres@0.5.0
|
|
35
|
+
- @shipfox/api-auth-context@12.0.0
|
|
36
|
+
- @shipfox/api-integration-jira-dto@12.0.0
|
|
37
|
+
- @shipfox/node-drizzle@0.3.5
|
|
38
|
+
|
|
3
39
|
## 11.0.0
|
|
4
40
|
|
|
5
41
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# Jira integration
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Jira OAuth, site links, token storage, webhooks, and token upkeep for Shipfox.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **`createJiraIntegrationProvider`** builds Jira OAuth and webhook routes.
|
|
8
|
+
- **`createJiraTokenStore`** stores Jira access and refresh tokens.
|
|
9
|
+
- **`createJiraMaintenanceWorker`** runs the token refresh workflow.
|
|
10
|
+
- **Installation functions** store Jira sites, webhooks, and token dates.
|
|
11
|
+
|
|
12
|
+
## Token upkeep
|
|
13
|
+
|
|
14
|
+
The worker runs every six hours.
|
|
15
|
+
|
|
16
|
+
It checks active sites in small batches. It refreshes a token after 76 idle days. Jira refresh tokens expire after about 90 idle days. The 14-day lead gives time for recovery.
|
|
17
|
+
|
|
18
|
+
A rejected refresh token or timeout marks the link as `error`. The user must connect Jira again. A new OAuth flow clears this state.
|
|
19
|
+
|
|
20
|
+
## Installation and setup
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pnpm add @shipfox/api-integration-jira
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The host supplies OAuth config, secret storage, connection lookup, and database access.
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import {createJiraIntegrationProvider} from '@shipfox/api-integration-jira';
|
|
32
|
+
|
|
33
|
+
const provider = createJiraIntegrationProvider();
|
|
34
|
+
|
|
35
|
+
console.log(provider.provider); // "jira"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The composed integrations module supplies route and worker dependencies.
|
|
39
|
+
|
|
40
|
+
## Behavior notes
|
|
41
|
+
|
|
42
|
+
Connect Jira with a dedicated Shipfox service account. Jira 3LO actions use the authorizing account. Events from that account are dropped to prevent agent self-triggering.
|
|
43
|
+
|
|
44
|
+
## Data model
|
|
45
|
+
|
|
46
|
+
The package owns the `integrations_jira_installations` table. The `refresh_token_last_used_at` column records the last successful refresh-token use. The `refresh_token_last_attempted_at` column rotates maintenance attempts so failed or inactive connections cannot starve the capped sweep.
|
|
47
|
+
|
|
48
|
+
## Development
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
mise exec -- turbo check --filter=@shipfox/api-integration-jira
|
|
52
|
+
mise exec -- turbo type --filter=@shipfox/api-integration-jira
|
|
53
|
+
mise exec -- turbo test --filter=@shipfox/api-integration-jira
|
|
54
|
+
mise exec -- turbo build --filter=@shipfox/api-integration-jira
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Database tests need the local PostgreSQL test service.
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
package/dist/api/client.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare const JIRA_DYNAMIC_WEBHOOK_EVENTS: readonly ["jira:issue_created", "jira:issue_updated", "jira:issue_deleted", "comment_created", "comment_updated", "comment_deleted"];
|
|
2
|
+
export declare const JIRA_DYNAMIC_WEBHOOK_JQL = "";
|
|
1
3
|
export interface JiraAuthorization {
|
|
2
4
|
accessToken: string;
|
|
3
5
|
refreshToken?: string | undefined;
|
|
@@ -13,6 +15,27 @@ export interface JiraAccessibleResource {
|
|
|
13
15
|
export interface JiraIdentity {
|
|
14
16
|
accountId: string;
|
|
15
17
|
}
|
|
18
|
+
export interface JiraDynamicWebhookRegistration {
|
|
19
|
+
webhookId: number;
|
|
20
|
+
}
|
|
21
|
+
export type JiraAgentToolHttpMethod = 'GET' | 'POST' | 'PUT';
|
|
22
|
+
export type JiraAgentToolQueryValue = string | number | boolean | readonly (string | number)[] | undefined;
|
|
23
|
+
export interface JiraAgentToolRequest {
|
|
24
|
+
accessToken: string;
|
|
25
|
+
cloudId: string;
|
|
26
|
+
method: JiraAgentToolHttpMethod;
|
|
27
|
+
path: string;
|
|
28
|
+
query?: Record<string, JiraAgentToolQueryValue> | undefined;
|
|
29
|
+
body?: unknown;
|
|
30
|
+
operation?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
export interface JiraAgentToolResponse {
|
|
33
|
+
status: number;
|
|
34
|
+
body: unknown;
|
|
35
|
+
}
|
|
36
|
+
export interface JiraAgentToolsClient {
|
|
37
|
+
request(input: JiraAgentToolRequest): Promise<JiraAgentToolResponse>;
|
|
38
|
+
}
|
|
16
39
|
export interface JiraApiClient {
|
|
17
40
|
exchangeAuthorizationCode(input: {
|
|
18
41
|
code: string;
|
|
@@ -27,7 +50,18 @@ export interface JiraApiClient {
|
|
|
27
50
|
accessToken: string;
|
|
28
51
|
cloudId: string;
|
|
29
52
|
}): Promise<JiraIdentity>;
|
|
53
|
+
registerDynamicWebhook(input: {
|
|
54
|
+
accessToken: string;
|
|
55
|
+
cloudId: string;
|
|
56
|
+
url: string;
|
|
57
|
+
}): Promise<JiraDynamicWebhookRegistration>;
|
|
58
|
+
deleteDynamicWebhook(input: {
|
|
59
|
+
accessToken: string;
|
|
60
|
+
cloudId: string;
|
|
61
|
+
webhookId: number;
|
|
62
|
+
}): Promise<void>;
|
|
30
63
|
}
|
|
31
64
|
export declare function createJiraApiClient(): JiraApiClient;
|
|
65
|
+
export declare function createJiraAgentToolsClient(): JiraAgentToolsClient;
|
|
32
66
|
export declare function mapJiraError<T>(operation: string, request: () => Promise<T>): Promise<T>;
|
|
33
67
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/api/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,2BAA2B,sIAAwB,CAAC;AACjE,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAE3C,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,8BAA8B;IAC7C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,uBAAuB,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7D,MAAM,MAAM,uBAAuB,GAC/B,MAAM,GACN,MAAM,GACN,OAAO,GACP,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAC5B,SAAS,CAAC;AAEd,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,uBAAuB,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,GAAG,SAAS,CAAC;IAC5D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,aAAa;IAC5B,yBAAyB,CAAC,KAAK,EAAE;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC7E,kBAAkB,CAAC,KAAK,EAAE;QAAC,YAAY,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9E,sBAAsB,CAAC,KAAK,EAAE;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IACxF,SAAS,CAAC,KAAK,EAAE;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAChF,sBAAsB,CAAC,KAAK,EAAE;QAC5B,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;KACb,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC5C,oBAAoB,CAAC,KAAK,EAAE;QAC1B,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AA0BD,wBAAgB,mBAAmB,IAAI,aAAa,CAkGnD;AAED,wBAAgB,0BAA0B,IAAI,oBAAoB,CAEjE;AA6ID,wBAAsB,YAAY,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAyC9F"}
|
package/dist/api/client.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import { jiraWebhookEventNames } from '@shipfox/api-integration-jira-dto';
|
|
1
2
|
import { logger } from '@shipfox/node-opentelemetry';
|
|
2
3
|
import ky, { HTTPError, TimeoutError } from 'ky';
|
|
3
4
|
import { config } from '#config.js';
|
|
4
5
|
import { JiraIntegrationProviderError } from '#core/errors.js';
|
|
5
6
|
const JIRA_API_TIMEOUT_MS = 10_000;
|
|
6
7
|
const SCOPE_SEPARATOR_RE = /[,\s]+/;
|
|
8
|
+
const TRAILING_SLASHES_RE = /\/+$/;
|
|
9
|
+
export const JIRA_DYNAMIC_WEBHOOK_EVENTS = jiraWebhookEventNames;
|
|
10
|
+
export const JIRA_DYNAMIC_WEBHOOK_JQL = '';
|
|
11
|
+
const JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES = new Set([
|
|
12
|
+
'invalid_grant',
|
|
13
|
+
'unauthorized_client'
|
|
14
|
+
]);
|
|
7
15
|
export function createJiraApiClient() {
|
|
8
16
|
return {
|
|
9
17
|
async exchangeAuthorizationCode (input) {
|
|
@@ -56,9 +64,121 @@ export function createJiraApiClient() {
|
|
|
56
64
|
return {
|
|
57
65
|
accountId: body.accountId
|
|
58
66
|
};
|
|
67
|
+
},
|
|
68
|
+
async registerDynamicWebhook (input) {
|
|
69
|
+
const body = await mapJiraError('register-dynamic-webhook', ()=>ky.post(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/webhook`, {
|
|
70
|
+
headers: {
|
|
71
|
+
authorization: `Bearer ${input.accessToken}`
|
|
72
|
+
},
|
|
73
|
+
json: {
|
|
74
|
+
url: input.url,
|
|
75
|
+
webhooks: [
|
|
76
|
+
{
|
|
77
|
+
events: JIRA_DYNAMIC_WEBHOOK_EVENTS,
|
|
78
|
+
jqlFilter: JIRA_DYNAMIC_WEBHOOK_JQL
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
timeout: JIRA_API_TIMEOUT_MS
|
|
83
|
+
}).json());
|
|
84
|
+
return parseDynamicWebhookRegistration(body);
|
|
85
|
+
},
|
|
86
|
+
async deleteDynamicWebhook (input) {
|
|
87
|
+
await mapJiraError('delete-dynamic-webhook', ()=>ky.delete(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/webhook`, {
|
|
88
|
+
headers: {
|
|
89
|
+
authorization: `Bearer ${input.accessToken}`
|
|
90
|
+
},
|
|
91
|
+
json: {
|
|
92
|
+
webhookIds: [
|
|
93
|
+
input.webhookId
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
timeout: JIRA_API_TIMEOUT_MS
|
|
97
|
+
}));
|
|
59
98
|
}
|
|
60
99
|
};
|
|
61
100
|
}
|
|
101
|
+
export function createJiraAgentToolsClient() {
|
|
102
|
+
return {
|
|
103
|
+
request: requestJiraRest
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function requestJiraRest(input) {
|
|
107
|
+
return await mapJiraError(input.operation ?? 'agent-tool', async ()=>{
|
|
108
|
+
try {
|
|
109
|
+
const response = await ky(jiraRestUrl(input.cloudId, input.path), {
|
|
110
|
+
method: input.method,
|
|
111
|
+
headers: {
|
|
112
|
+
authorization: `Bearer ${input.accessToken}`
|
|
113
|
+
},
|
|
114
|
+
...input.query === undefined ? {} : {
|
|
115
|
+
searchParams: jiraQueryParams(input.query)
|
|
116
|
+
},
|
|
117
|
+
...input.body === undefined ? {} : {
|
|
118
|
+
json: input.body
|
|
119
|
+
},
|
|
120
|
+
timeout: JIRA_API_TIMEOUT_MS
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
status: response.status,
|
|
124
|
+
body: await readJiraResponseBody(response)
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error instanceof HTTPError && (error.response.status === 400 || error.response.status === 404)) {
|
|
128
|
+
return {
|
|
129
|
+
status: error.response.status,
|
|
130
|
+
body: await readJiraResponseBody(error.response)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function jiraRestUrl(cloudId, path) {
|
|
138
|
+
const baseUrl = config.JIRA_API_BASE_URL.replace(TRAILING_SLASHES_RE, '');
|
|
139
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
140
|
+
return `${baseUrl}/ex/jira/${encodeURIComponent(cloudId)}/rest/api/3${normalizedPath}`;
|
|
141
|
+
}
|
|
142
|
+
function jiraQueryParams(query) {
|
|
143
|
+
const params = new URLSearchParams();
|
|
144
|
+
for (const [key, value] of Object.entries(query)){
|
|
145
|
+
if (value === undefined) continue;
|
|
146
|
+
if (Array.isArray(value)) {
|
|
147
|
+
for (const item of value)params.append(key, String(item));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
params.set(key, String(value));
|
|
151
|
+
}
|
|
152
|
+
return params.size > 0 ? params : undefined;
|
|
153
|
+
}
|
|
154
|
+
async function readJiraResponseBody(response) {
|
|
155
|
+
const text = await response.text();
|
|
156
|
+
if (text.length === 0) return undefined;
|
|
157
|
+
try {
|
|
158
|
+
return JSON.parse(text);
|
|
159
|
+
} catch {
|
|
160
|
+
return text;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function parseDynamicWebhookRegistration(body) {
|
|
164
|
+
if (!body || typeof body !== 'object' || !Array.isArray(body.webhookRegistrationResult) || body.webhookRegistrationResult.length !== 1) {
|
|
165
|
+
throw malformed('Jira webhook registration response did not contain exactly one result');
|
|
166
|
+
}
|
|
167
|
+
const result = body.webhookRegistrationResult[0];
|
|
168
|
+
if (!result || typeof result !== 'object') {
|
|
169
|
+
throw malformed('Jira webhook registration result was malformed');
|
|
170
|
+
}
|
|
171
|
+
const { createdWebhookId, errors } = result;
|
|
172
|
+
if (errors !== undefined && (!Array.isArray(errors) || errors.length > 0)) {
|
|
173
|
+
throw malformed('Jira webhook registration returned errors');
|
|
174
|
+
}
|
|
175
|
+
if (typeof createdWebhookId !== 'number' || !Number.isSafeInteger(createdWebhookId) || createdWebhookId <= 0) {
|
|
176
|
+
throw malformed('Jira webhook registration did not return a created webhook id');
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
webhookId: createdWebhookId
|
|
180
|
+
};
|
|
181
|
+
}
|
|
62
182
|
function parseAuthorization(body) {
|
|
63
183
|
if (typeof body.access_token !== 'string' || body.access_token.length === 0) {
|
|
64
184
|
throw malformed('Jira authorization response did not include an access token');
|
|
@@ -115,6 +235,12 @@ export async function mapJiraError(operation, request) {
|
|
|
115
235
|
if (status === 401 || status === 403) {
|
|
116
236
|
throw new JiraIntegrationProviderError('access-denied', 'Jira request was rejected');
|
|
117
237
|
}
|
|
238
|
+
if (status === 400 && operation === 'refresh-access-token') {
|
|
239
|
+
const errorCode = readJiraOAuthErrorCode(error.data);
|
|
240
|
+
if (errorCode && JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES.has(errorCode)) {
|
|
241
|
+
throw new JiraIntegrationProviderError('access-denied', 'Jira refresh token was rejected; reconnect is required');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
118
244
|
throw malformed('Jira request was rejected');
|
|
119
245
|
}
|
|
120
246
|
if (error instanceof TimeoutError) {
|
|
@@ -133,6 +259,11 @@ export async function mapJiraError(operation, request) {
|
|
|
133
259
|
function malformed(message) {
|
|
134
260
|
return new JiraIntegrationProviderError('malformed-provider-response', message);
|
|
135
261
|
}
|
|
262
|
+
function readJiraOAuthErrorCode(data) {
|
|
263
|
+
if (!data || typeof data !== 'object') return undefined;
|
|
264
|
+
const errorCode = data.error;
|
|
265
|
+
return typeof errorCode === 'string' ? errorCode : undefined;
|
|
266
|
+
}
|
|
136
267
|
function retryAfterSeconds(headers) {
|
|
137
268
|
const value = headers.get('retry-after');
|
|
138
269
|
if (!value) return undefined;
|
package/dist/api/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport ky, {HTTPError, TimeoutError} from 'ky';\nimport {config} from '#config.js';\nimport {JiraIntegrationProviderError} from '#core/errors.js';\n\nconst JIRA_API_TIMEOUT_MS = 10_000;\nconst SCOPE_SEPARATOR_RE = /[,\\s]+/;\n\nexport interface JiraAuthorization {\n accessToken: string;\n refreshToken?: string | undefined;\n expiresAt?: Date | undefined;\n scopes: string[];\n}\n\nexport interface JiraAccessibleResource {\n cloudId: string;\n name: string;\n url: string;\n scopes: string[];\n}\n\nexport interface JiraIdentity {\n accountId: string;\n}\n\nexport interface JiraApiClient {\n exchangeAuthorizationCode(input: {code: string}): Promise<JiraAuthorization>;\n refreshAccessToken(input: {refreshToken: string}): Promise<JiraAuthorization>;\n getAccessibleResources(input: {accessToken: string}): Promise<JiraAccessibleResource[]>;\n getMyself(input: {accessToken: string; cloudId: string}): Promise<JiraIdentity>;\n}\n\ninterface JiraTokenResponse {\n access_token?: unknown;\n refresh_token?: unknown;\n expires_in?: unknown;\n scope?: unknown;\n}\n\ninterface JiraResourceResponse {\n id?: unknown;\n name?: unknown;\n url?: unknown;\n scopes?: unknown;\n}\n\ninterface JiraMyselfResponse {\n accountId?: unknown;\n}\n\nexport function createJiraApiClient(): JiraApiClient {\n return {\n async exchangeAuthorizationCode(input) {\n const body = await mapJiraError('exchange-authorization-code', () =>\n ky\n .post(`${config.JIRA_AUTH_BASE_URL}/oauth/token`, {\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: config.JIRA_OAUTH_CLIENT_ID,\n client_secret: config.JIRA_OAUTH_CLIENT_SECRET,\n code: input.code,\n redirect_uri: config.JIRA_OAUTH_REDIRECT_URL,\n }),\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraTokenResponse>(),\n );\n return parseAuthorization(body);\n },\n\n async refreshAccessToken(input) {\n const body = await mapJiraError('refresh-access-token', () =>\n ky\n .post(`${config.JIRA_AUTH_BASE_URL}/oauth/token`, {\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: config.JIRA_OAUTH_CLIENT_ID,\n client_secret: config.JIRA_OAUTH_CLIENT_SECRET,\n refresh_token: input.refreshToken,\n }),\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraTokenResponse>(),\n );\n return parseAuthorization(body);\n },\n\n async getAccessibleResources(input) {\n const body = await mapJiraError('get-accessible-resources', () =>\n ky\n .get(`${config.JIRA_API_BASE_URL}/oauth/token/accessible-resources`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<unknown>(),\n );\n if (!Array.isArray(body)) {\n throw malformed('Jira accessible-resources response was not an array');\n }\n return body.map(parseAccessibleResource);\n },\n\n async getMyself(input) {\n const body = await mapJiraError('get-myself', () =>\n ky\n .get(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/myself`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraMyselfResponse>(),\n );\n if (typeof body.accountId !== 'string' || body.accountId.length === 0) {\n throw malformed('Jira identity response did not include an accountId');\n }\n return {accountId: body.accountId};\n },\n };\n}\n\nfunction parseAuthorization(body: JiraTokenResponse): JiraAuthorization {\n if (typeof body.access_token !== 'string' || body.access_token.length === 0) {\n throw malformed('Jira authorization response did not include an access token');\n }\n return {\n accessToken: body.access_token,\n refreshToken: typeof body.refresh_token === 'string' ? body.refresh_token : undefined,\n expiresAt: parseExpiresAt(body.expires_in),\n scopes: parseScopes(body.scope),\n };\n}\n\nfunction parseAccessibleResource(value: unknown): JiraAccessibleResource {\n if (!value || typeof value !== 'object') throw malformed('Jira site response was malformed');\n const {id, name, url, scopes} = value as JiraResourceResponse;\n if (\n typeof id !== 'string' ||\n id.length === 0 ||\n typeof name !== 'string' ||\n name.length === 0 ||\n typeof url !== 'string' ||\n url.length === 0 ||\n !Array.isArray(scopes) ||\n !scopes.every((scope) => typeof scope === 'string')\n ) {\n throw malformed('Jira site response did not include a valid cloud id, name, URL, and scopes');\n }\n return {cloudId: id, name, url, scopes};\n}\n\nfunction parseScopes(scope: unknown): string[] {\n if (typeof scope === 'string')\n return scope\n .split(SCOPE_SEPARATOR_RE)\n .map((value) => value.trim())\n .filter(Boolean);\n if (Array.isArray(scope) && scope.every((value) => typeof value === 'string')) return scope;\n if (scope === undefined) return [];\n throw malformed('Jira authorization response included malformed scopes');\n}\n\nfunction parseExpiresAt(expiresIn: unknown): Date | undefined {\n if (expiresIn === undefined) return undefined;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw malformed('Jira authorization response included a malformed expiry');\n }\n return new Date(Date.now() + expiresIn * 1000);\n}\n\nexport async function mapJiraError<T>(operation: string, request: () => Promise<T>): Promise<T> {\n try {\n return await request();\n } catch (error) {\n if (error instanceof JiraIntegrationProviderError) throw error;\n if (error instanceof HTTPError) {\n const {status, statusText, headers} = error.response;\n logger().warn({operation, status, statusText}, 'Jira API request rejected');\n if (status === 429) {\n throw new JiraIntegrationProviderError(\n 'rate-limited',\n 'Jira request was rate limited',\n retryAfterSeconds(headers),\n );\n }\n if (status >= 500)\n throw new JiraIntegrationProviderError('provider-unavailable', 'Jira request failed');\n if (status === 401 || status === 403) {\n throw new JiraIntegrationProviderError('access-denied', 'Jira request was rejected');\n }\n throw malformed('Jira request was rejected');\n }\n if (error instanceof TimeoutError) {\n logger().warn({operation}, 'Jira API request timed out');\n throw new JiraIntegrationProviderError('timeout', 'Jira request timed out');\n }\n logger().warn(\n {operation, errName: error instanceof Error ? error.name : typeof error},\n 'Jira API request failed',\n );\n throw new JiraIntegrationProviderError('provider-unavailable', 'Jira request failed');\n }\n}\n\nfunction malformed(message: string): JiraIntegrationProviderError {\n return new JiraIntegrationProviderError('malformed-provider-response', message);\n}\n\nfunction retryAfterSeconds(headers: Headers): number | undefined {\n const value = headers.get('retry-after');\n if (!value) return undefined;\n const parsed = Number.parseInt(value, 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n"],"names":["logger","ky","HTTPError","TimeoutError","config","JiraIntegrationProviderError","JIRA_API_TIMEOUT_MS","SCOPE_SEPARATOR_RE","createJiraApiClient","exchangeAuthorizationCode","input","body","mapJiraError","post","JIRA_AUTH_BASE_URL","URLSearchParams","grant_type","client_id","JIRA_OAUTH_CLIENT_ID","client_secret","JIRA_OAUTH_CLIENT_SECRET","code","redirect_uri","JIRA_OAUTH_REDIRECT_URL","timeout","json","parseAuthorization","refreshAccessToken","refresh_token","refreshToken","getAccessibleResources","get","JIRA_API_BASE_URL","headers","authorization","accessToken","Array","isArray","malformed","map","parseAccessibleResource","getMyself","cloudId","accountId","length","access_token","undefined","expiresAt","parseExpiresAt","expires_in","scopes","parseScopes","scope","value","id","name","url","every","split","trim","filter","Boolean","expiresIn","Number","isFinite","Date","now","operation","request","error","status","statusText","response","warn","retryAfterSeconds","errName","Error","message","parsed","parseInt","isNaN"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/api/client.ts"],"sourcesContent":["import {jiraWebhookEventNames} from '@shipfox/api-integration-jira-dto';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport ky, {HTTPError, TimeoutError} from 'ky';\nimport {config} from '#config.js';\nimport {JiraIntegrationProviderError} from '#core/errors.js';\n\nconst JIRA_API_TIMEOUT_MS = 10_000;\nconst SCOPE_SEPARATOR_RE = /[,\\s]+/;\nconst TRAILING_SLASHES_RE = /\\/+$/;\nexport const JIRA_DYNAMIC_WEBHOOK_EVENTS = jiraWebhookEventNames;\nexport const JIRA_DYNAMIC_WEBHOOK_JQL = '';\n\nexport interface JiraAuthorization {\n accessToken: string;\n refreshToken?: string | undefined;\n expiresAt?: Date | undefined;\n scopes: string[];\n}\n\nexport interface JiraAccessibleResource {\n cloudId: string;\n name: string;\n url: string;\n scopes: string[];\n}\n\nexport interface JiraIdentity {\n accountId: string;\n}\n\nexport interface JiraDynamicWebhookRegistration {\n webhookId: number;\n}\n\nexport type JiraAgentToolHttpMethod = 'GET' | 'POST' | 'PUT';\n\nexport type JiraAgentToolQueryValue =\n | string\n | number\n | boolean\n | readonly (string | number)[]\n | undefined;\n\nexport interface JiraAgentToolRequest {\n accessToken: string;\n cloudId: string;\n method: JiraAgentToolHttpMethod;\n path: string;\n query?: Record<string, JiraAgentToolQueryValue> | undefined;\n body?: unknown;\n operation?: string | undefined;\n}\n\nexport interface JiraAgentToolResponse {\n status: number;\n body: unknown;\n}\n\nexport interface JiraAgentToolsClient {\n request(input: JiraAgentToolRequest): Promise<JiraAgentToolResponse>;\n}\n\nexport interface JiraApiClient {\n exchangeAuthorizationCode(input: {code: string}): Promise<JiraAuthorization>;\n refreshAccessToken(input: {refreshToken: string}): Promise<JiraAuthorization>;\n getAccessibleResources(input: {accessToken: string}): Promise<JiraAccessibleResource[]>;\n getMyself(input: {accessToken: string; cloudId: string}): Promise<JiraIdentity>;\n registerDynamicWebhook(input: {\n accessToken: string;\n cloudId: string;\n url: string;\n }): Promise<JiraDynamicWebhookRegistration>;\n deleteDynamicWebhook(input: {\n accessToken: string;\n cloudId: string;\n webhookId: number;\n }): Promise<void>;\n}\n\ninterface JiraTokenResponse {\n access_token?: unknown;\n refresh_token?: unknown;\n expires_in?: unknown;\n scope?: unknown;\n}\n\ninterface JiraResourceResponse {\n id?: unknown;\n name?: unknown;\n url?: unknown;\n scopes?: unknown;\n}\n\ninterface JiraMyselfResponse {\n accountId?: unknown;\n}\n\ninterface JiraDynamicWebhookRegistrationResponse {\n webhookRegistrationResult?: unknown;\n}\n\nconst JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES = new Set(['invalid_grant', 'unauthorized_client']);\n\nexport function createJiraApiClient(): JiraApiClient {\n return {\n async exchangeAuthorizationCode(input) {\n const body = await mapJiraError('exchange-authorization-code', () =>\n ky\n .post(`${config.JIRA_AUTH_BASE_URL}/oauth/token`, {\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: config.JIRA_OAUTH_CLIENT_ID,\n client_secret: config.JIRA_OAUTH_CLIENT_SECRET,\n code: input.code,\n redirect_uri: config.JIRA_OAUTH_REDIRECT_URL,\n }),\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraTokenResponse>(),\n );\n return parseAuthorization(body);\n },\n\n async refreshAccessToken(input) {\n const body = await mapJiraError('refresh-access-token', () =>\n ky\n .post(`${config.JIRA_AUTH_BASE_URL}/oauth/token`, {\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: config.JIRA_OAUTH_CLIENT_ID,\n client_secret: config.JIRA_OAUTH_CLIENT_SECRET,\n refresh_token: input.refreshToken,\n }),\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraTokenResponse>(),\n );\n return parseAuthorization(body);\n },\n\n async getAccessibleResources(input) {\n const body = await mapJiraError('get-accessible-resources', () =>\n ky\n .get(`${config.JIRA_API_BASE_URL}/oauth/token/accessible-resources`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<unknown>(),\n );\n if (!Array.isArray(body)) {\n throw malformed('Jira accessible-resources response was not an array');\n }\n return body.map(parseAccessibleResource);\n },\n\n async getMyself(input) {\n const body = await mapJiraError('get-myself', () =>\n ky\n .get(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/myself`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraMyselfResponse>(),\n );\n if (typeof body.accountId !== 'string' || body.accountId.length === 0) {\n throw malformed('Jira identity response did not include an accountId');\n }\n return {accountId: body.accountId};\n },\n\n async registerDynamicWebhook(input) {\n const body = await mapJiraError('register-dynamic-webhook', () =>\n ky\n .post(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/webhook`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n json: {\n url: input.url,\n webhooks: [\n {\n events: JIRA_DYNAMIC_WEBHOOK_EVENTS,\n jqlFilter: JIRA_DYNAMIC_WEBHOOK_JQL,\n },\n ],\n },\n timeout: JIRA_API_TIMEOUT_MS,\n })\n .json<JiraDynamicWebhookRegistrationResponse>(),\n );\n return parseDynamicWebhookRegistration(body);\n },\n\n async deleteDynamicWebhook(input) {\n await mapJiraError('delete-dynamic-webhook', () =>\n ky.delete(`${config.JIRA_API_BASE_URL}/ex/jira/${input.cloudId}/rest/api/3/webhook`, {\n headers: {authorization: `Bearer ${input.accessToken}`},\n json: {webhookIds: [input.webhookId]},\n timeout: JIRA_API_TIMEOUT_MS,\n }),\n );\n },\n };\n}\n\nexport function createJiraAgentToolsClient(): JiraAgentToolsClient {\n return {request: requestJiraRest};\n}\n\nasync function requestJiraRest(input: JiraAgentToolRequest): Promise<JiraAgentToolResponse> {\n return await mapJiraError(input.operation ?? 'agent-tool', async () => {\n try {\n const response = await ky(jiraRestUrl(input.cloudId, input.path), {\n method: input.method,\n headers: {authorization: `Bearer ${input.accessToken}`},\n ...(input.query === undefined ? {} : {searchParams: jiraQueryParams(input.query)}),\n ...(input.body === undefined ? {} : {json: input.body}),\n timeout: JIRA_API_TIMEOUT_MS,\n });\n return {status: response.status, body: await readJiraResponseBody(response)};\n } catch (error) {\n if (\n error instanceof HTTPError &&\n (error.response.status === 400 || error.response.status === 404)\n ) {\n return {\n status: error.response.status,\n body: await readJiraResponseBody(error.response),\n };\n }\n throw error;\n }\n });\n}\n\nfunction jiraRestUrl(cloudId: string, path: string): string {\n const baseUrl = config.JIRA_API_BASE_URL.replace(TRAILING_SLASHES_RE, '');\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n return `${baseUrl}/ex/jira/${encodeURIComponent(cloudId)}/rest/api/3${normalizedPath}`;\n}\n\nfunction jiraQueryParams(\n query: Record<string, JiraAgentToolQueryValue>,\n): URLSearchParams | undefined {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) params.append(key, String(item));\n continue;\n }\n params.set(key, String(value));\n }\n return params.size > 0 ? params : undefined;\n}\n\nasync function readJiraResponseBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (text.length === 0) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction parseDynamicWebhookRegistration(\n body: JiraDynamicWebhookRegistrationResponse,\n): JiraDynamicWebhookRegistration {\n if (\n !body ||\n typeof body !== 'object' ||\n !Array.isArray(body.webhookRegistrationResult) ||\n body.webhookRegistrationResult.length !== 1\n ) {\n throw malformed('Jira webhook registration response did not contain exactly one result');\n }\n\n const result = body.webhookRegistrationResult[0];\n if (!result || typeof result !== 'object') {\n throw malformed('Jira webhook registration result was malformed');\n }\n const {createdWebhookId, errors} = result as {\n createdWebhookId?: unknown;\n errors?: unknown;\n };\n if (errors !== undefined && (!Array.isArray(errors) || errors.length > 0)) {\n throw malformed('Jira webhook registration returned errors');\n }\n if (\n typeof createdWebhookId !== 'number' ||\n !Number.isSafeInteger(createdWebhookId) ||\n createdWebhookId <= 0\n ) {\n throw malformed('Jira webhook registration did not return a created webhook id');\n }\n return {webhookId: createdWebhookId};\n}\n\nfunction parseAuthorization(body: JiraTokenResponse): JiraAuthorization {\n if (typeof body.access_token !== 'string' || body.access_token.length === 0) {\n throw malformed('Jira authorization response did not include an access token');\n }\n return {\n accessToken: body.access_token,\n refreshToken: typeof body.refresh_token === 'string' ? body.refresh_token : undefined,\n expiresAt: parseExpiresAt(body.expires_in),\n scopes: parseScopes(body.scope),\n };\n}\n\nfunction parseAccessibleResource(value: unknown): JiraAccessibleResource {\n if (!value || typeof value !== 'object') throw malformed('Jira site response was malformed');\n const {id, name, url, scopes} = value as JiraResourceResponse;\n if (\n typeof id !== 'string' ||\n id.length === 0 ||\n typeof name !== 'string' ||\n name.length === 0 ||\n typeof url !== 'string' ||\n url.length === 0 ||\n !Array.isArray(scopes) ||\n !scopes.every((scope) => typeof scope === 'string')\n ) {\n throw malformed('Jira site response did not include a valid cloud id, name, URL, and scopes');\n }\n return {cloudId: id, name, url, scopes};\n}\n\nfunction parseScopes(scope: unknown): string[] {\n if (typeof scope === 'string')\n return scope\n .split(SCOPE_SEPARATOR_RE)\n .map((value) => value.trim())\n .filter(Boolean);\n if (Array.isArray(scope) && scope.every((value) => typeof value === 'string')) return scope;\n if (scope === undefined) return [];\n throw malformed('Jira authorization response included malformed scopes');\n}\n\nfunction parseExpiresAt(expiresIn: unknown): Date | undefined {\n if (expiresIn === undefined) return undefined;\n if (typeof expiresIn !== 'number' || !Number.isFinite(expiresIn) || expiresIn <= 0) {\n throw malformed('Jira authorization response included a malformed expiry');\n }\n return new Date(Date.now() + expiresIn * 1000);\n}\n\nexport async function mapJiraError<T>(operation: string, request: () => Promise<T>): Promise<T> {\n try {\n return await request();\n } catch (error) {\n if (error instanceof JiraIntegrationProviderError) throw error;\n if (error instanceof HTTPError) {\n const {status, statusText, headers} = error.response;\n logger().warn({operation, status, statusText}, 'Jira API request rejected');\n if (status === 429) {\n throw new JiraIntegrationProviderError(\n 'rate-limited',\n 'Jira request was rate limited',\n retryAfterSeconds(headers),\n );\n }\n if (status >= 500)\n throw new JiraIntegrationProviderError('provider-unavailable', 'Jira request failed');\n if (status === 401 || status === 403) {\n throw new JiraIntegrationProviderError('access-denied', 'Jira request was rejected');\n }\n if (status === 400 && operation === 'refresh-access-token') {\n const errorCode = readJiraOAuthErrorCode(error.data);\n if (errorCode && JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES.has(errorCode)) {\n throw new JiraIntegrationProviderError(\n 'access-denied',\n 'Jira refresh token was rejected; reconnect is required',\n );\n }\n }\n throw malformed('Jira request was rejected');\n }\n if (error instanceof TimeoutError) {\n logger().warn({operation}, 'Jira API request timed out');\n throw new JiraIntegrationProviderError('timeout', 'Jira request timed out');\n }\n logger().warn(\n {operation, errName: error instanceof Error ? error.name : typeof error},\n 'Jira API request failed',\n );\n throw new JiraIntegrationProviderError('provider-unavailable', 'Jira request failed');\n }\n}\n\nfunction malformed(message: string): JiraIntegrationProviderError {\n return new JiraIntegrationProviderError('malformed-provider-response', message);\n}\n\nfunction readJiraOAuthErrorCode(data: unknown): string | undefined {\n if (!data || typeof data !== 'object') return undefined;\n const errorCode = (data as {error?: unknown}).error;\n return typeof errorCode === 'string' ? errorCode : undefined;\n}\n\nfunction retryAfterSeconds(headers: Headers): number | undefined {\n const value = headers.get('retry-after');\n if (!value) return undefined;\n const parsed = Number.parseInt(value, 10);\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n"],"names":["jiraWebhookEventNames","logger","ky","HTTPError","TimeoutError","config","JiraIntegrationProviderError","JIRA_API_TIMEOUT_MS","SCOPE_SEPARATOR_RE","TRAILING_SLASHES_RE","JIRA_DYNAMIC_WEBHOOK_EVENTS","JIRA_DYNAMIC_WEBHOOK_JQL","JIRA_REFRESH_TOKEN_TERMINAL_ERROR_CODES","Set","createJiraApiClient","exchangeAuthorizationCode","input","body","mapJiraError","post","JIRA_AUTH_BASE_URL","URLSearchParams","grant_type","client_id","JIRA_OAUTH_CLIENT_ID","client_secret","JIRA_OAUTH_CLIENT_SECRET","code","redirect_uri","JIRA_OAUTH_REDIRECT_URL","timeout","json","parseAuthorization","refreshAccessToken","refresh_token","refreshToken","getAccessibleResources","get","JIRA_API_BASE_URL","headers","authorization","accessToken","Array","isArray","malformed","map","parseAccessibleResource","getMyself","cloudId","accountId","length","registerDynamicWebhook","url","webhooks","events","jqlFilter","parseDynamicWebhookRegistration","deleteDynamicWebhook","delete","webhookIds","webhookId","createJiraAgentToolsClient","request","requestJiraRest","operation","response","jiraRestUrl","path","method","query","undefined","searchParams","jiraQueryParams","status","readJiraResponseBody","error","baseUrl","replace","normalizedPath","startsWith","encodeURIComponent","params","key","value","Object","entries","item","append","String","set","size","text","JSON","parse","webhookRegistrationResult","result","createdWebhookId","errors","Number","isSafeInteger","access_token","expiresAt","parseExpiresAt","expires_in","scopes","parseScopes","scope","id","name","every","split","trim","filter","Boolean","expiresIn","isFinite","Date","now","statusText","warn","retryAfterSeconds","errorCode","readJiraOAuthErrorCode","data","has","errName","Error","message","parsed","parseInt","isNaN"],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ export declare const config: Readonly<{
|
|
|
2
2
|
JIRA_OAUTH_CLIENT_ID: string;
|
|
3
3
|
JIRA_OAUTH_CLIENT_SECRET: string;
|
|
4
4
|
JIRA_OAUTH_REDIRECT_URL: string;
|
|
5
|
-
JIRA_WEBHOOK_SIGNING_SECRET: string;
|
|
6
5
|
JIRA_WEBHOOK_BASE_URL: string;
|
|
7
6
|
JIRA_API_BASE_URL: string;
|
|
8
7
|
JIRA_AUTH_BASE_URL: string;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;;;;;;;kDAqBjB,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -4,14 +4,11 @@ export const config = createConfig({
|
|
|
4
4
|
desc: 'OAuth client ID of the Jira app, used to start and complete the Jira connect flow. Required.'
|
|
5
5
|
}),
|
|
6
6
|
JIRA_OAUTH_CLIENT_SECRET: str({
|
|
7
|
-
desc: 'OAuth client secret of the Jira app, used
|
|
7
|
+
desc: 'OAuth client secret of the Jira app, used for authorization-code exchange and inbound webhook JWT verification. Required.'
|
|
8
8
|
}),
|
|
9
9
|
JIRA_OAUTH_REDIRECT_URL: url({
|
|
10
10
|
desc: 'Public client callback URL Jira redirects to after OAuth authorization, such as https://shipfox.example.com/integrations/jira/callback. Required.'
|
|
11
11
|
}),
|
|
12
|
-
JIRA_WEBHOOK_SIGNING_SECRET: str({
|
|
13
|
-
desc: 'Secret used to verify incoming Jira webhooks. It must match the webhook signing secret configured for the Jira app. Required.'
|
|
14
|
-
}),
|
|
15
12
|
JIRA_WEBHOOK_BASE_URL: url({
|
|
16
13
|
desc: 'Public base URL Jira sends webhooks to. Its origin must be added to the Jira app webhook allowlist. Required.'
|
|
17
14
|
}),
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {createConfig, str, url} from '@shipfox/config';\n\nexport const config = createConfig({\n JIRA_OAUTH_CLIENT_ID: str({\n desc: 'OAuth client ID of the Jira app, used to start and complete the Jira connect flow. Required.',\n }),\n JIRA_OAUTH_CLIENT_SECRET: str({\n desc: 'OAuth client secret of the Jira app, used
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {createConfig, str, url} from '@shipfox/config';\n\nexport const config = createConfig({\n JIRA_OAUTH_CLIENT_ID: str({\n desc: 'OAuth client ID of the Jira app, used to start and complete the Jira connect flow. Required.',\n }),\n JIRA_OAUTH_CLIENT_SECRET: str({\n desc: 'OAuth client secret of the Jira app, used for authorization-code exchange and inbound webhook JWT verification. Required.',\n }),\n JIRA_OAUTH_REDIRECT_URL: url({\n desc: 'Public client callback URL Jira redirects to after OAuth authorization, such as https://shipfox.example.com/integrations/jira/callback. Required.',\n }),\n JIRA_WEBHOOK_BASE_URL: url({\n desc: 'Public base URL Jira sends webhooks to. Its origin must be added to the Jira app webhook allowlist. Required.',\n }),\n JIRA_API_BASE_URL: url({\n desc: 'Jira API base URL. Override this only when routing Jira API requests through a proxy or test server.',\n default: 'https://api.atlassian.com',\n }),\n JIRA_AUTH_BASE_URL: url({\n desc: 'Jira OAuth base URL. Override this only when routing OAuth requests through a proxy or test server.',\n default: 'https://auth.atlassian.com',\n }),\n});\n"],"names":["createConfig","str","url","config","JIRA_OAUTH_CLIENT_ID","desc","JIRA_OAUTH_CLIENT_SECRET","JIRA_OAUTH_REDIRECT_URL","JIRA_WEBHOOK_BASE_URL","JIRA_API_BASE_URL","default","JIRA_AUTH_BASE_URL"],"mappings":"AAAA,SAAQA,YAAY,EAAEC,GAAG,EAAEC,GAAG,QAAO,kBAAkB;AAEvD,OAAO,MAAMC,SAASH,aAAa;IACjCI,sBAAsBH,IAAI;QACxBI,MAAM;IACR;IACAC,0BAA0BL,IAAI;QAC5BI,MAAM;IACR;IACAE,yBAAyBL,IAAI;QAC3BG,MAAM;IACR;IACAG,uBAAuBN,IAAI;QACzBG,MAAM;IACR;IACAI,mBAAmBP,IAAI;QACrBG,MAAM;QACNK,SAAS;IACX;IACAC,oBAAoBT,IAAI;QACtBG,MAAM;QACNK,SAAS;IACX;AACF,GAAG"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AgentToolSession, AgentToolsProvider, IntegrationConnection, OpenAgentToolsSessionInput } from '@shipfox/api-integration-spi';
|
|
2
|
+
import type { JiraAgentToolsClient } from '#api/client.js';
|
|
3
|
+
import type { JiraTokenStore } from '#core/tokens.js';
|
|
4
|
+
import { type JiraAgentToolRequiredScope } from './agent-tools.js';
|
|
5
|
+
type JiraIntegrationConnection = IntegrationConnection<'jira'>;
|
|
6
|
+
export type JiraToolCallResult = {
|
|
7
|
+
isError?: boolean | undefined;
|
|
8
|
+
content: readonly {
|
|
9
|
+
type: 'text';
|
|
10
|
+
text: string;
|
|
11
|
+
}[];
|
|
12
|
+
structuredContent?: Record<string, unknown> | undefined;
|
|
13
|
+
};
|
|
14
|
+
export interface JiraAgentToolsProviderOptions {
|
|
15
|
+
jira: Pick<JiraAgentToolsClient, 'request'>;
|
|
16
|
+
tokenStore: Pick<JiraTokenStore, 'getAccessToken'>;
|
|
17
|
+
}
|
|
18
|
+
export declare class JiraAgentToolsProvider implements AgentToolsProvider<JiraIntegrationConnection, JiraAgentToolRequiredScope, unknown, JiraToolCallResult> {
|
|
19
|
+
private readonly options;
|
|
20
|
+
constructor(options: JiraAgentToolsProviderOptions);
|
|
21
|
+
catalog(): readonly [import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry, import("@shipfox/api-integration-jira-dto").JiraAgentToolCatalogEntry];
|
|
22
|
+
selectionCatalog(): import("@shipfox/api-integration-spi").AgentToolSelectionCatalog;
|
|
23
|
+
openSession(input: OpenAgentToolsSessionInput<JiraIntegrationConnection, JiraAgentToolRequiredScope, unknown>): Promise<AgentToolSession<JiraToolCallResult>>;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=agent-tools-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-tools-provider.d.ts","sourceRoot":"","sources":["../../src/core/agent-tools-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,EAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAwB,oBAAoB,EAAC,MAAM,gBAAgB,CAAC;AAChF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAEL,KAAK,0BAA0B,EAGhC,MAAM,kBAAkB,CAAC;AAG1B,KAAK,yBAAyB,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAE/D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,SAAS;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC,EAAE,CAAC;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CACzD,CAAC;AAEF,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAC;IAC5C,UAAU,EAAE,IAAI,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;CACpD;AAED,qBAAa,sBACX,YACE,kBAAkB,CAChB,yBAAyB,EACzB,0BAA0B,EAC1B,OAAO,EACP,kBAAkB,CACnB;IAES,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,6BAA6B;IAEnE,OAAO;IAIP,gBAAgB;IAIV,WAAW,CACf,KAAK,EAAE,0BAA0B,CAC/B,yBAAyB,EACzB,0BAA0B,EAC1B,OAAO,CACR,GACA,OAAO,CAAC,gBAAgB,CAAC,kBAAkB,CAAC,CAAC;CAsDjD"}
|