@scm-manager/ui-api 2.30.2-20220031-151012 → 2.30.2-20220101-084238

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scm-manager/ui-api",
3
- "version": "2.30.2-20220031-151012",
3
+ "version": "2.30.2-20220101-084238",
4
4
  "description": "React hook api for the SCM-Manager backend",
5
5
  "main": "src/index.ts",
6
6
  "files": [
@@ -25,7 +25,7 @@
25
25
  "react-test-renderer": "^17.0.1"
26
26
  },
27
27
  "dependencies": {
28
- "@scm-manager/ui-types": "^2.30.2-20220031-151012",
28
+ "@scm-manager/ui-types": "^2.30.2-20220101-084238",
29
29
  "fetch-mock-jest": "^1.5.1",
30
30
  "gitdiff-parser": "^0.1.2",
31
31
  "query-string": "6.14.1",
@@ -22,9 +22,9 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- import {apiClient, createUrl, extractXsrfTokenFromCookie} from "./apiclient";
25
+ import { apiClient, createUrl, extractXsrfTokenFromCookie } from "./apiclient";
26
26
  import fetchMock from "fetch-mock";
27
- import {BackendError, BadGatewayError} from "./errors";
27
+ import { BackendError } from "./errors";
28
28
 
29
29
  describe("create url", () => {
30
30
  it("should not change absolute urls", () => {
@@ -45,9 +45,9 @@ describe("error handling tests", () => {
45
45
  context: [
46
46
  {
47
47
  type: "planet",
48
- id: "earth"
49
- }
50
- ]
48
+ id: "earth",
49
+ },
50
+ ],
51
51
  };
52
52
 
53
53
  afterEach(() => {
@@ -55,9 +55,9 @@ describe("error handling tests", () => {
55
55
  fetchMock.restore();
56
56
  });
57
57
 
58
- it("should create a normal error, if the content type is not scmm-error", done => {
58
+ it("should create a normal error, if the content type is not scmm-error", (done) => {
59
59
  fetchMock.getOnce("/api/v2/error", {
60
- status: 404
60
+ status: 404,
61
61
  });
62
62
 
63
63
  apiClient.get("/error").catch((err: Error) => {
@@ -67,24 +67,13 @@ describe("error handling tests", () => {
67
67
  });
68
68
  });
69
69
 
70
- it("should create a bad gateway error", done => {
71
- fetchMock.getOnce("/api/v2/error", {
72
- status: 502
73
- });
74
-
75
- apiClient.get("/error").catch((err: Error) => {
76
- expect(err).toBeInstanceOf(BadGatewayError);
77
- done();
78
- });
79
- });
80
-
81
- it("should create an backend error, if the content type is scmm-error", done => {
70
+ it("should create an backend error, if the content type is scmm-error", (done) => {
82
71
  fetchMock.getOnce("/api/v2/error", {
83
72
  status: 404,
84
73
  headers: {
85
- "Content-Type": "application/vnd.scmm-error+json;v=2"
74
+ "Content-Type": "application/vnd.scmm-error+json;v=2",
86
75
  },
87
- body: earthNotFoundError
76
+ body: earthNotFoundError,
88
77
  });
89
78
 
90
79
  apiClient.get("/error").catch((err: BackendError) => {
@@ -98,8 +87,8 @@ describe("error handling tests", () => {
98
87
  expect(err.context).toEqual([
99
88
  {
100
89
  type: "planet",
101
- id: "earth"
102
- }
90
+ id: "earth",
91
+ },
103
92
  ]);
104
93
  done();
105
94
  });
package/src/apiclient.ts CHANGED
@@ -25,13 +25,12 @@
25
25
  import { contextPath } from "./urls";
26
26
  import {
27
27
  BackendErrorContent,
28
- BadGatewayError,
29
28
  createBackendError,
30
29
  ForbiddenError,
31
30
  isBackendError,
32
31
  TOKEN_EXPIRED_ERROR_CODE,
33
32
  TokenExpiredError,
34
- UnauthorizedError
33
+ UnauthorizedError,
35
34
  } from "./errors";
36
35
 
37
36
  type SubscriptionEvent = {
@@ -63,12 +62,7 @@ type SubscriptionArgument = MessageListeners | SubscriptionContext;
63
62
 
64
63
  type Cancel = () => void;
65
64
 
66
- const sessionId = (
67
- Date.now().toString(36) +
68
- Math.random()
69
- .toString(36)
70
- .substr(2, 5)
71
- ).toUpperCase();
65
+ const sessionId = (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toUpperCase();
72
66
 
73
67
  const extractXsrfTokenFromJwt = (jwt: string) => {
74
68
  const parts = jwt.split(".");
@@ -103,7 +97,7 @@ const createRequestHeaders = () => {
103
97
  // identify the web interface
104
98
  "X-SCM-Client": "WUI",
105
99
  // identify the window session
106
- "X-SCM-Session-ID": sessionId
100
+ "X-SCM-Session-ID": sessionId,
107
101
  };
108
102
 
109
103
  const xsrf = extractXsrfToken();
@@ -113,10 +107,10 @@ const createRequestHeaders = () => {
113
107
  return headers;
114
108
  };
115
109
 
116
- const applyFetchOptions: (p: RequestInit) => RequestInit = o => {
110
+ const applyFetchOptions: (p: RequestInit) => RequestInit = (o) => {
117
111
  if (o.headers) {
118
112
  o.headers = {
119
- ...createRequestHeaders()
113
+ ...createRequestHeaders(),
120
114
  };
121
115
  } else {
122
116
  o.headers = createRequestHeaders();
@@ -140,8 +134,6 @@ function handleFailure(response: Response) {
140
134
  throw new UnauthorizedError("Unauthorized", 401);
141
135
  } else if (response.status === 403) {
142
136
  throw new ForbiddenError("Forbidden", 403);
143
- } else if (response.status === 502) {
144
- throw new BadGatewayError("Bad Gateway", 502);
145
137
  } else if (isBackendError(response)) {
146
138
  return response.json().then((content: BackendErrorContent) => {
147
139
  throw createBackendError(content, response.status);
@@ -177,9 +169,7 @@ class ApiClient {
177
169
  requestListeners: RequestListener[] = [];
178
170
 
179
171
  get = (url: string): Promise<Response> => {
180
- return this.request(url, applyFetchOptions({}))
181
- .then(handleFailure)
182
- .catch(this.notifyAndRethrow);
172
+ return this.request(url, applyFetchOptions({})).then(handleFailure).catch(this.notifyAndRethrow);
183
173
  };
184
174
 
185
175
  post = (
@@ -206,7 +196,7 @@ class ApiClient {
206
196
  const options: RequestInit = {
207
197
  method: "POST",
208
198
  body: formData,
209
- headers: additionalHeaders
199
+ headers: additionalHeaders,
210
200
  };
211
201
  return this.httpRequestWithBinaryBody(options, url);
212
202
  };
@@ -217,22 +207,18 @@ class ApiClient {
217
207
 
218
208
  head = (url: string) => {
219
209
  let options: RequestInit = {
220
- method: "HEAD"
210
+ method: "HEAD",
221
211
  };
222
212
  options = applyFetchOptions(options);
223
- return this.request(url, options)
224
- .then(handleFailure)
225
- .catch(this.notifyAndRethrow);
213
+ return this.request(url, options).then(handleFailure).catch(this.notifyAndRethrow);
226
214
  };
227
215
 
228
216
  delete = (url: string): Promise<Response> => {
229
217
  let options: RequestInit = {
230
- method: "DELETE"
218
+ method: "DELETE",
231
219
  };
232
220
  options = applyFetchOptions(options);
233
- return this.request(url, options)
234
- .then(handleFailure)
235
- .catch(this.notifyAndRethrow);
221
+ return this.request(url, options).then(handleFailure).catch(this.notifyAndRethrow);
236
222
  };
237
223
 
238
224
  httpRequestWithJSONBody = (
@@ -244,7 +230,7 @@ class ApiClient {
244
230
  ): Promise<Response> => {
245
231
  const options: RequestInit = {
246
232
  method: method,
247
- headers: additionalHeaders
233
+ headers: additionalHeaders,
248
234
  };
249
235
  if (payload) {
250
236
  options.body = JSON.stringify(payload);
@@ -260,7 +246,7 @@ class ApiClient {
260
246
  ) => {
261
247
  const options: RequestInit = {
262
248
  method: method,
263
- headers: additionalHeaders
249
+ headers: additionalHeaders,
264
250
  };
265
251
  options.body = payload;
266
252
  return this.httpRequestWithBinaryBody(options, url, "text/plain");
@@ -276,14 +262,12 @@ class ApiClient {
276
262
  options.headers["Content-Type"] = contentType;
277
263
  }
278
264
 
279
- return this.request(url, options)
280
- .then(handleFailure)
281
- .catch(this.notifyAndRethrow);
265
+ return this.request(url, options).then(handleFailure).catch(this.notifyAndRethrow);
282
266
  };
283
267
 
284
268
  subscribe(url: string, argument: SubscriptionArgument): Cancel {
285
269
  const es = new EventSource(createUrlWithIdentifiers(url), {
286
- withCredentials: true
270
+ withCredentials: true,
287
271
  });
288
272
 
289
273
  let listeners: MessageListeners;
@@ -324,11 +308,11 @@ class ApiClient {
324
308
  };
325
309
 
326
310
  private notifyRequestListeners = (url: string, options: RequestInit) => {
327
- this.requestListeners.forEach(requestListener => requestListener(url, options));
311
+ this.requestListeners.forEach((requestListener) => requestListener(url, options));
328
312
  };
329
313
 
330
314
  private notifyAndRethrow = (error: Error): never => {
331
- this.errorListeners.forEach(errorListener => errorListener(error));
315
+ this.errorListeners.forEach((errorListener) => errorListener(error));
332
316
  throw error;
333
317
  };
334
318
  }
package/src/errors.ts CHANGED
@@ -79,15 +79,6 @@ export class UnauthorizedError extends Error {
79
79
  }
80
80
  }
81
81
 
82
- export class BadGatewayError extends Error {
83
- statusCode: number;
84
-
85
- constructor(message: string, statusCode: number) {
86
- super(message);
87
- this.statusCode = statusCode;
88
- }
89
- }
90
-
91
82
  export class TokenExpiredError extends UnauthorizedError {}
92
83
 
93
84
  export class ForbiddenError extends Error {
package/src/plugins.ts CHANGED
@@ -27,7 +27,6 @@ import { isPluginCollection, PendingPlugins, Plugin, PluginCollection } from "@s
27
27
  import { useMutation, useQuery, useQueryClient } from "react-query";
28
28
  import { apiClient } from "./apiclient";
29
29
  import { requiredLink } from "./links";
30
- import { BadGatewayError } from "./errors";
31
30
 
32
31
  type WaitForRestartOptions = {
33
32
  initialDelay?: number;
@@ -38,38 +37,31 @@ const waitForRestartAfter = (
38
37
  promise: Promise<any>,
39
38
  { initialDelay = 1000, timeout = 500 }: WaitForRestartOptions = {}
40
39
  ): Promise<void> => {
41
- const endTime = Number(new Date()) + (4 * 60 * 1000);
40
+ const endTime = Number(new Date()) + 60000;
42
41
  let started = false;
43
42
 
44
- const executor = <T = any>(data: T) => (resolve: (result: T) => void, reject: (error: Error) => void) => {
45
- // we need some initial delay
46
- if (!started) {
47
- started = true;
48
- setTimeout(executor(data), initialDelay, resolve, reject);
49
- } else {
50
- apiClient
51
- .get("")
52
- .then(() => resolve(data))
53
- .catch(() => {
54
- if (Number(new Date()) < endTime) {
55
- setTimeout(executor(data), timeout, resolve, reject);
56
- } else {
57
- reject(new Error("timeout reached"));
58
- }
59
- });
60
- }
61
- };
62
-
63
- return promise
64
- .catch(err => {
65
- if (err instanceof BadGatewayError) {
66
- // in some rare cases the reverse proxy stops forwarding traffic to scm before the response is returned
67
- // in such a case the reverse proxy returns 502 (bad gateway), so we treat 502 not as error
68
- return "ok";
43
+ const executor =
44
+ <T = any>(data: T) =>
45
+ (resolve: (result: T) => void, reject: (error: Error) => void) => {
46
+ // we need some initial delay
47
+ if (!started) {
48
+ started = true;
49
+ setTimeout(executor(data), initialDelay, resolve, reject);
50
+ } else {
51
+ apiClient
52
+ .get("")
53
+ .then(() => resolve(data))
54
+ .catch(() => {
55
+ if (Number(new Date()) < endTime) {
56
+ setTimeout(executor(data), timeout, resolve, reject);
57
+ } else {
58
+ reject(new Error("timeout reached"));
59
+ }
60
+ });
69
61
  }
70
- throw err;
71
- })
72
- .then(data => new Promise<void>(executor(data)));
62
+ };
63
+
64
+ return promise.then((data) => new Promise<void>(executor(data)));
73
65
  };
74
66
 
75
67
  export type UseAvailablePluginsOptions = {
@@ -80,10 +72,10 @@ export const useAvailablePlugins = ({ enabled }: UseAvailablePluginsOptions = {}
80
72
  const indexLink = useRequiredIndexLink("availablePlugins");
81
73
  return useQuery<PluginCollection, Error>(
82
74
  ["plugins", "available"],
83
- () => apiClient.get(indexLink).then(response => response.json()),
75
+ () => apiClient.get(indexLink).then((response) => response.json()),
84
76
  {
85
77
  enabled,
86
- retry: 3
78
+ retry: 3,
87
79
  }
88
80
  );
89
81
  };
@@ -96,10 +88,10 @@ export const useInstalledPlugins = ({ enabled }: UseInstalledPluginsOptions = {}
96
88
  const indexLink = useRequiredIndexLink("installedPlugins");
97
89
  return useQuery<PluginCollection, Error>(
98
90
  ["plugins", "installed"],
99
- () => apiClient.get(indexLink).then(response => response.json()),
91
+ () => apiClient.get(indexLink).then((response) => response.json()),
100
92
  {
101
93
  enabled,
102
- retry: 3
94
+ retry: 3,
103
95
  }
104
96
  );
105
97
  };
@@ -108,10 +100,10 @@ export const usePendingPlugins = (): ApiResult<PendingPlugins> => {
108
100
  const indexLink = useIndexLink("pendingPlugins");
109
101
  return useQuery<PendingPlugins, Error>(
110
102
  ["plugins", "pending"],
111
- () => apiClient.get(indexLink!).then(response => response.json()),
103
+ () => apiClient.get(indexLink!).then((response) => response.json()),
112
104
  {
113
105
  enabled: !!indexLink,
114
- retry: 3
106
+ retry: 3,
115
107
  }
116
108
  );
117
109
  };
@@ -143,19 +135,19 @@ export const useInstallPlugin = () => {
143
135
  return promise;
144
136
  },
145
137
  {
146
- onSuccess: () => queryClient.invalidateQueries("plugins")
138
+ onSuccess: () => queryClient.invalidateQueries("plugins"),
147
139
  }
148
140
  );
149
141
  return {
150
142
  install: (plugin: Plugin, restartOptions: RestartOptions = {}) =>
151
143
  mutate({
152
144
  plugin,
153
- restartOptions
145
+ restartOptions,
154
146
  }),
155
147
  isLoading,
156
148
  error,
157
149
  data,
158
- isInstalled: !!data
150
+ isInstalled: !!data,
159
151
  };
160
152
  };
161
153
 
@@ -170,18 +162,18 @@ export const useUninstallPlugin = () => {
170
162
  return promise;
171
163
  },
172
164
  {
173
- onSuccess: () => queryClient.invalidateQueries("plugins")
165
+ onSuccess: () => queryClient.invalidateQueries("plugins"),
174
166
  }
175
167
  );
176
168
  return {
177
169
  uninstall: (plugin: Plugin, restartOptions: RestartOptions = {}) =>
178
170
  mutate({
179
171
  plugin,
180
- restartOptions
172
+ restartOptions,
181
173
  }),
182
174
  isLoading,
183
175
  error,
184
- isUninstalled: !!data
176
+ isUninstalled: !!data,
185
177
  };
186
178
  };
187
179
 
@@ -204,18 +196,18 @@ export const useUpdatePlugins = () => {
204
196
  return promise;
205
197
  },
206
198
  {
207
- onSuccess: () => queryClient.invalidateQueries("plugins")
199
+ onSuccess: () => queryClient.invalidateQueries("plugins"),
208
200
  }
209
201
  );
210
202
  return {
211
203
  update: (plugin: Plugin | PluginCollection, restartOptions: RestartOptions = {}) =>
212
204
  mutate({
213
205
  plugins: plugin,
214
- restartOptions
206
+ restartOptions,
215
207
  }),
216
208
  isLoading,
217
209
  error,
218
- isUpdated: !!data
210
+ isUpdated: !!data,
219
211
  };
220
212
  };
221
213
 
@@ -230,7 +222,7 @@ export const useExecutePendingPlugins = () => {
230
222
  ({ pending, restartOptions }) =>
231
223
  waitForRestartAfter(apiClient.post(requiredLink(pending, "execute")), restartOptions),
232
224
  {
233
- onSuccess: () => queryClient.invalidateQueries("plugins")
225
+ onSuccess: () => queryClient.invalidateQueries("plugins"),
234
226
  }
235
227
  );
236
228
  return {
@@ -238,22 +230,22 @@ export const useExecutePendingPlugins = () => {
238
230
  mutate({ pending, restartOptions }),
239
231
  isLoading,
240
232
  error,
241
- isExecuted: !!data
233
+ isExecuted: !!data,
242
234
  };
243
235
  };
244
236
 
245
237
  export const useCancelPendingPlugins = () => {
246
238
  const queryClient = useQueryClient();
247
239
  const { mutate, isLoading, error, data } = useMutation<unknown, Error, PendingPlugins>(
248
- pending => apiClient.post(requiredLink(pending, "cancel")),
240
+ (pending) => apiClient.post(requiredLink(pending, "cancel")),
249
241
  {
250
- onSuccess: () => queryClient.invalidateQueries("plugins")
242
+ onSuccess: () => queryClient.invalidateQueries("plugins"),
251
243
  }
252
244
  );
253
245
  return {
254
246
  update: (pending: PendingPlugins) => mutate(pending),
255
247
  isLoading,
256
248
  error,
257
- isCancelled: !!data
249
+ isCancelled: !!data,
258
250
  };
259
251
  };
@@ -28,6 +28,16 @@ import { useMutation, useQuery, useQueryClient } from "react-query";
28
28
  import { apiClient } from "./apiclient";
29
29
  import { useLocation } from "react-router-dom";
30
30
 
31
+ const appendQueryParam = (link: Link, name: string, value: string) => {
32
+ let href = link.href;
33
+ if (href.includes("?")) {
34
+ href += "&";
35
+ } else {
36
+ href += "?";
37
+ }
38
+ link.href = href + name + "=" + value;
39
+ };
40
+
31
41
  export const usePluginCenterAuthInfo = (): ApiResult<PluginCenterAuthenticationInfo> => {
32
42
  const link = useIndexLink("pluginCenterAuth");
33
43
  const location = useLocation();
@@ -42,7 +52,10 @@ export const usePluginCenterAuthInfo = (): ApiResult<PluginCenterAuthenticationI
42
52
  .then(response => response.json())
43
53
  .then((result: PluginCenterAuthenticationInfo) => {
44
54
  if (result._links?.login) {
45
- (result._links.login as Link).href += `?source=${location.pathname}`;
55
+ appendQueryParam(result._links.login as Link, "source", location.pathname);
56
+ }
57
+ if (result._links?.reconnect) {
58
+ appendQueryParam(result._links.reconnect as Link, "source", location.pathname);
46
59
  }
47
60
  return result;
48
61
  });