@forge/api 2.17.0-next.3 → 2.18.0-next.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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @forge/api
2
2
 
3
+ ## 2.18.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7cab7f9c: Handle redirect in nodejs runtime
8
+
9
+ ## 2.17.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 3edb8a19: Add tracing to experimental integration
14
+
15
+ ### Patch Changes
16
+
17
+ - 5ba685b6: Add new success dimension to proxy fetch metrics on node runtime
18
+ - cf453494: Bumping dependencies via Renovate:
19
+
20
+ - node-fetch
21
+ - @types/node-fetch
22
+
23
+ - bb7b824d: Bumping dependencies via Renovate:
24
+
25
+ - @types/node
26
+
27
+ - Updated dependencies [cf453494]
28
+ - Updated dependencies [bb7b824d]
29
+ - @forge/storage@1.5.3
30
+
3
31
  ## 2.17.0-next.3
4
32
 
5
33
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import { RequestInfo, RequestInit, Response } from 'node-fetch';
1
+ import { RequestInfo, RequestInit, Response, Headers } from 'node-fetch';
2
2
  import { FetchAPI } from '..';
3
3
  declare type FetchFunction = (url: RequestInfo, options?: RequestInit) => Promise<Response>;
4
4
  declare type ProxyUrlProvider = 'app' | 'user' | 'none';
@@ -10,6 +10,7 @@ declare type ProxyFetchArgs = {
10
10
  } | {
11
11
  type: 'egress';
12
12
  };
13
+ export declare const getRedirectUrl: (responseHeaders: Headers, original: string, isFromEgress: boolean) => string;
13
14
  export declare const createProxyFetch: (args: ProxyFetchArgs) => FetchFunction;
14
15
  export declare function getNodeRuntimeAPI(): FetchAPI;
15
16
  export declare function getSandboxRuntimeAPI(api: any): FetchAPI;
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/api/fetch.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAW,MAAM,YAAY,CAAC;AAEhF,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAQ9B,aAAK,aAAa,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AACpF,aAAK,gBAAgB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAChD,aAAK,cAAc,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,GAAG,WAAW,CAAC;AA4BvE,aAAK,cAAc,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AA8B/G,eAAO,MAAM,gBAAgB,SAAU,cAAc,KAAG,aAoCrD,CAAC;AAOJ,wBAAgB,iBAAiB,IAAI,QAAQ,CAoB5C;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,QAAQ,CAEvD"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/api/fetch.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAW,OAAO,EAAE,MAAM,YAAY,CAAC;AAEzF,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAS9B,aAAK,aAAa,GAAG,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AACpF,aAAK,gBAAgB,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAChD,aAAK,cAAc,GAAG,MAAM,GAAG,YAAY,GAAG,UAAU,GAAG,WAAW,CAAC;AA4BvE,aAAK,cAAc,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAsE/G,eAAO,MAAM,cAAc,oBAAqB,OAAO,YAAY,MAAM,gBAAgB,OAAO,KAAG,MAQlG,CAAC;AAyEF,eAAO,MAAM,gBAAgB,SAAU,cAAc,KAAG,aAerD,CAAC;AAOJ,wBAAgB,iBAAiB,IAAI,QAAQ,CAoB5C;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,QAAQ,CAEvD"}
package/out/api/fetch.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getSandboxRuntimeAPI = exports.getNodeRuntimeAPI = exports.createProxyFetch = void 0;
3
+ exports.getSandboxRuntimeAPI = exports.getNodeRuntimeAPI = exports.createProxyFetch = exports.getRedirectUrl = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const node_fetch_1 = tslib_1.__importStar(require("node-fetch"));
6
6
  const _1 = require(".");
@@ -32,46 +32,116 @@ const metricName = (args) => {
32
32
  }
33
33
  };
34
34
  const createProxyRequest = ({ url: proxyUrl, token, host }, args, originalRequest) => {
35
- let proxyRequest;
35
+ let proxyEndpoint;
36
36
  switch (args.type) {
37
37
  case 'egress':
38
- proxyRequest = new node_fetch_1.Request(`${proxyUrl}/egress`, originalRequest);
39
- const url = new URL(originalRequest.url);
40
- proxyRequest.headers.set('Forge-Proxy-Target-Relative', `${url.pathname}${url.search}`);
41
- proxyRequest.headers.set('Forge-Proxy-Target-Host', url.hostname);
38
+ proxyEndpoint = `${proxyUrl}/egress`;
42
39
  break;
43
40
  case 'fpp':
44
- proxyRequest = new node_fetch_1.Request(`${proxyUrl}/fpp/provider/${args.provider}/remote/${args.remote}`, originalRequest);
45
- proxyRequest.headers.set('Forge-Proxy-Target-Relative', originalRequest.url);
41
+ proxyEndpoint = `${proxyUrl}/fpp/provider/${args.provider}/remote/${args.remote}`;
46
42
  break;
47
43
  }
44
+ const tempRequest = new node_fetch_1.Request(proxyEndpoint, originalRequest);
45
+ const proxyRequest = new node_fetch_1.Request(tempRequest, { redirect: 'manual' });
46
+ proxyRequest.headers.set('Forge-Proxy-Target', originalRequest.url);
48
47
  proxyRequest.headers.set('Forge-Proxy-Authorization', `Bearer ${token}`);
49
48
  if (host) {
50
49
  proxyRequest.headers.set('Host', host);
51
50
  }
52
51
  return proxyRequest;
53
52
  };
54
- const createProxyFetch = (args) => (0, runtime_1.wrapInMetrics)(metricName(args), async (url, options) => {
55
- const { proxy, metrics } = (0, runtime_1.getRuntime)();
56
- const request = new node_fetch_1.Request(url, options);
57
- const proxyRequest = createProxyRequest(proxy, args, request);
53
+ const wrapWithOverheadMetric = async (fetch, metrics) => {
58
54
  const requestStart = perf_hooks_1.performance.now();
59
- const response = await (0, node_fetch_1.default)(proxyRequest);
55
+ const response = await fetch;
60
56
  const requestEnd = perf_hooks_1.performance.now();
61
57
  const proxyUpstreamLatency = parseInt(response.headers.get(FORGE_PROXY_UPSTREAM_LATENCY_HEADER) || '');
62
58
  if (proxyUpstreamLatency) {
63
59
  metrics.timing('proxy-success-overhead').set(requestEnd - requestStart - proxyUpstreamLatency);
64
60
  }
61
+ return response;
62
+ };
63
+ const getRedirectArgs = (args, response) => {
64
+ const proxyRelativeLocation = response.headers.get('forge-proxy-relative-location');
65
+ if (!proxyRelativeLocation) {
66
+ return { type: 'egress' };
67
+ }
68
+ return args;
69
+ };
70
+ const buildGetRequest = (url, originalRequest) => {
71
+ const request = new node_fetch_1.Request(url, { method: 'GET' });
72
+ for (const [name, values] of Array.from(originalRequest.headers.entries())) {
73
+ if (!name.toLowerCase().startsWith('content-')) {
74
+ request.headers.set(name, values);
75
+ }
76
+ }
77
+ return request;
78
+ };
79
+ const getLocation = (responseHeaders) => {
80
+ const location = responseHeaders.get('location');
81
+ if (!location) {
82
+ throw new errors_1.FetchError('Redirect location is empty');
83
+ }
84
+ return location;
85
+ };
86
+ const getRedirectUrl = (responseHeaders, original, isFromEgress) => {
87
+ if (!isFromEgress) {
88
+ return responseHeaders.get('forge-proxy-relative-location') || getLocation(responseHeaders);
89
+ }
90
+ return new URL(getLocation(responseHeaders), original).toString();
91
+ };
92
+ exports.getRedirectUrl = getRedirectUrl;
93
+ const buildRedirectedRequest = (response, originalRequest, isFromEgress) => {
94
+ const url = (0, exports.getRedirectUrl)(response.headers, originalRequest.url, isFromEgress);
95
+ if (response.status === 303) {
96
+ return buildGetRequest(url, originalRequest);
97
+ }
98
+ return new node_fetch_1.Request(url, originalRequest);
99
+ };
100
+ const fetchViaProxy = async ({ proxy, proxyFetchArgs, request, metrics, count = 0 }) => {
101
+ const REDIRECT_STATUS = [300, 301, 302, 303, 307, 308];
102
+ const MAX_REDIRECTS = 20;
103
+ if (count >= MAX_REDIRECTS) {
104
+ throw new errors_1.FetchError('Max redirects exceeded');
105
+ }
106
+ const proxyRequest = createProxyRequest(proxy, proxyFetchArgs, request);
107
+ if (request.redirect === 'manual' && count === 0) {
108
+ return wrapWithOverheadMetric((0, node_fetch_1.default)(proxyRequest), metrics);
109
+ }
110
+ const response = await wrapWithOverheadMetric((0, node_fetch_1.default)(proxyRequest), metrics);
111
+ if (REDIRECT_STATUS.includes(response.status)) {
112
+ const redirectedRequest = buildRedirectedRequest(response, request, proxyFetchArgs.type === 'egress');
113
+ const redirectedArgs = getRedirectArgs(proxyFetchArgs, response);
114
+ return fetchViaProxy({
115
+ proxy,
116
+ proxyFetchArgs: redirectedArgs,
117
+ request: redirectedRequest,
118
+ metrics,
119
+ count: count + 1
120
+ });
121
+ }
122
+ return response;
123
+ };
124
+ const handleProxyResponseErrors = (response, requestUrl) => {
65
125
  if (response.headers.has('forge-proxy-error')) {
66
126
  const errorReason = response.headers.get('forge-proxy-error');
67
127
  if (errorReason === 'NEEDS_AUTHENTICATION_ERR') {
68
128
  throw new errors_1.NeedsAuthenticationError('Authentication Required', ATLASSIAN_TOKEN_SERVICE_KEY);
69
129
  }
70
130
  if (errorReason === 'BLOCKED_EGRESS') {
71
- throw new errors_1.ExternalEndpointNotAllowedError(request.url);
131
+ throw new errors_1.ExternalEndpointNotAllowedError(requestUrl);
72
132
  }
73
133
  throw new errors_1.ProxyRequestError(response.status, response.headers.get('forge-proxy-error'));
74
134
  }
135
+ };
136
+ const createProxyFetch = (args) => (0, runtime_1.wrapInMetrics)(metricName(args), async (url, options) => {
137
+ const { proxy, metrics } = (0, runtime_1.getRuntime)();
138
+ const response = await fetchViaProxy({
139
+ proxy,
140
+ proxyFetchArgs: args,
141
+ request: new node_fetch_1.Request(url, options),
142
+ metrics
143
+ });
144
+ handleProxyResponseErrors(response, url.toString());
75
145
  return response;
76
146
  }, { tags: { proxy: 'true' } });
77
147
  exports.createProxyFetch = createProxyFetch;
@@ -1,10 +1,11 @@
1
1
  import type { Metrics, Tags } from '@forge/util/packages/metrics-interface';
2
+ export declare type ProxyInfo = {
3
+ token: string;
4
+ url: string;
5
+ host?: string;
6
+ };
2
7
  export declare type ForgeRuntime = {
3
- proxy: {
4
- token: string;
5
- url: string;
6
- host?: string;
7
- };
8
+ proxy: ProxyInfo;
8
9
  contextAri: string;
9
10
  appContext: {
10
11
  appId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/api/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,wCAAwC,CAAC;AAS5E,oBAAY,YAAY,GAAG;IACzB,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE;QACb,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE;QACJ,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,CAAC;AAEF,wBAAgB,UAAU,IAAI,YAAY,CAOzC;AAED,wBAAgB,aAAa,CAAC,IAAI,SAAS,OAAO,EAAE,EAAE,GAAG,EACvD,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,GAAG,CAAC,EACnC,EAAE,IAAI,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAO,GAC7B,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,GAAG,CAAC,CAiBjC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/api/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,wCAAwC,CAAC;AAU5E,oBAAY,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,oBAAY,YAAY,GAAG;IACzB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE;QACb,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE;QACJ,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,CAAC;AAEF,wBAAgB,UAAU,IAAI,YAAY,CAOzC;AAED,wBAAgB,aAAa,CAAC,IAAI,SAAS,OAAO,EAAE,EAAE,GAAG,EACvD,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,GAAG,CAAC,EACnC,EAAE,IAAI,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAO,GAC7B,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,GAAG,CAAC,CAiBjC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forge/api",
3
- "version": "2.17.0-next.3",
3
+ "version": "2.18.0-next.0",
4
4
  "description": "Forge API methods",
5
5
  "author": "Atlassian",
6
6
  "license": "UNLICENSED",
@@ -12,7 +12,7 @@
12
12
  "compile": "tsc -b -v"
13
13
  },
14
14
  "devDependencies": {
15
- "@forge/runtime": "4.4.1-next.2",
15
+ "@forge/runtime": "4.4.1",
16
16
  "@forge/util": "1.2.3",
17
17
  "@types/node": "14.18.48",
18
18
  "jest-matcher-specific-error": "^1.0.0",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "@forge/auth": "0.0.1",
23
23
  "@forge/egress": "1.2.1",
24
- "@forge/storage": "1.5.3-next.1",
24
+ "@forge/storage": "1.5.3",
25
25
  "@types/node-fetch": "^2.6.4",
26
26
  "node-fetch": "2.6.11"
27
27
  }