@scality/data-browser-library 1.1.2 → 1.1.3

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.
@@ -6,7 +6,7 @@ import { useLocation, useNavigate } from "react-router";
6
6
  import styled_components from "styled-components";
7
7
  import { useDataBrowserUICustomization } from "../../contexts/DataBrowserUICustomizationContext.js";
8
8
  import { useListObjectVersions, useListObjects, useSearchObjects, useSearchObjectsVersions } from "../../hooks/index.js";
9
- import { useGetPresignedDownload } from "../../hooks/presignedOperations.js";
9
+ import { useDownloadObject } from "../../hooks/useDownloadObject.js";
10
10
  import { useBatchObjectLegalHold } from "../../hooks/useBatchObjectLegalHold.js";
11
11
  import { useFeatures } from "../../hooks/useFeatures.js";
12
12
  import { useQueryParams } from "../../utils/hooks.js";
@@ -62,25 +62,6 @@ const removePrefix = (path, prefix)=>{
62
62
  return path;
63
63
  };
64
64
  const createLegalHoldKey = (key, versionId)=>`${key}:${versionId ?? 'null'}`;
65
- const downloadFile = (url, filename, onCleanup)=>{
66
- try {
67
- new URL(url);
68
- const link = document.createElement('a');
69
- link.href = url;
70
- link.download = filename;
71
- link.style.display = 'none';
72
- link.rel = 'noopener noreferrer';
73
- document.body.appendChild(link);
74
- link.click();
75
- const timeoutId = setTimeout(()=>{
76
- if (document.body.contains(link)) document.body.removeChild(link);
77
- }, 100);
78
- onCleanup?.(timeoutId);
79
- } catch (error) {
80
- console.error('Invalid download URL:', url, error);
81
- throw new Error('Failed to initiate download: Invalid URL');
82
- }
83
- };
84
65
  const createNameColumn = (onPrefixChange, onDownload)=>({
85
66
  Header: 'Name',
86
67
  accessor: 'displayName',
@@ -302,7 +283,7 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange, onSele
302
283
  const navigate = useNavigate();
303
284
  const [searchValue, setSearchValue] = useState(queryParams.get(SEARCH_QUERY_PARAM) || '');
304
285
  const [selectedObjects, setSelectedObjects] = useState([]);
305
- const { mutateAsync: getPresignedDownload } = useGetPresignedDownload();
286
+ const { mutateAsync: downloadObject } = useDownloadObject();
306
287
  const downloadingRef = useRef(new Set());
307
288
  const downloadTimeoutsRef = useRef(new Map());
308
289
  const { showToast } = useToast();
@@ -337,18 +318,13 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange, onSele
337
318
  try {
338
319
  const rawFilename = object.displayName || object.Key.split('/').pop() || 'download';
339
320
  const sanitized = rawFilename.replace(/["\r\n\t]/g, '');
340
- const encoded = encodeURIComponent(sanitized);
341
- const result = await getPresignedDownload({
321
+ await downloadObject({
342
322
  Bucket: bucketName,
343
323
  Key: object.Key,
344
- ResponseContentDisposition: `attachment; filename="${sanitized}"; filename*=UTF-8''${encoded}`,
345
324
  ...versionId ? {
346
325
  VersionId: versionId
347
- } : {}
348
- });
349
- if (!result?.Url) throw new Error('Failed to generate presigned URL: No URL returned');
350
- downloadFile(result.Url, sanitized, (timeoutId)=>{
351
- downloadTimeoutsRef.current.set(`${downloadKey}_link`, timeoutId);
326
+ } : {},
327
+ filename: sanitized
352
328
  });
353
329
  } catch (error) {
354
330
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
@@ -361,13 +337,12 @@ const ObjectList = ({ bucketName, prefix, onObjectSelect, onPrefixChange, onSele
361
337
  const timeoutId = setTimeout(()=>{
362
338
  downloadingRef.current.delete(downloadKey);
363
339
  downloadTimeoutsRef.current.delete(downloadKey);
364
- downloadTimeoutsRef.current.delete(`${downloadKey}_link`);
365
340
  }, 1000);
366
341
  downloadTimeoutsRef.current.set(downloadKey, timeoutId);
367
342
  }
368
343
  }, [
369
344
  bucketName,
370
- getPresignedDownload,
345
+ downloadObject,
371
346
  showToast
372
347
  ]);
373
348
  const isSearchActive = isMetadataSearchEnabled && Boolean(metadataSearchQuery);
@@ -24,7 +24,7 @@ describe('usePresigningS3Client', ()=>{
24
24
  }));
25
25
  });
26
26
  it('should create an S3 client with the direct endpoint when no proxy is configured', ()=>{
27
- renderHook(()=>usePresigningS3Client(), {
27
+ const { result } = renderHook(()=>usePresigningS3Client(), {
28
28
  wrapper: createTestWrapper(testConfig, testCredentials)
29
29
  });
30
30
  expect(MockedS3Client).toHaveBeenCalledWith(expect.objectContaining({
@@ -32,34 +32,38 @@ describe('usePresigningS3Client', ()=>{
32
32
  region: testConfig.region,
33
33
  forcePathStyle: true
34
34
  }));
35
+ expect(result.current.client).toBeDefined();
36
+ });
37
+ it('should return identity rewriteUrl when no proxy is configured', ()=>{
38
+ const { result } = renderHook(()=>usePresigningS3Client(), {
39
+ wrapper: createTestWrapper(testConfig, testCredentials)
40
+ });
41
+ const url = 'http://s3.example.com/bucket/key?X-Amz-Signature=abc';
42
+ expect(result.current.rewriteUrl(url)).toBe(url);
35
43
  });
36
- it('should create an S3 client with the proxy endpoint (not target) when proxy is enabled', ()=>{
44
+ it('should create an S3 client with the target endpoint when proxy is enabled', ()=>{
37
45
  renderHook(()=>usePresigningS3Client(), {
38
46
  wrapper: createTestWrapper(proxyConfig, testCredentials)
39
47
  });
40
48
  expect(MockedS3Client).toHaveBeenCalledWith(expect.objectContaining({
41
- endpoint: 'https://proxy.example.com/s3',
49
+ endpoint: 'http://internal-s3.cluster.local',
42
50
  region: testConfig.region,
43
51
  forcePathStyle: true
44
52
  }));
45
53
  const constructorCall = MockedS3Client.mock.calls[0][0];
46
- expect(constructorCall?.endpoint).not.toContain('internal-s3.cluster.local');
54
+ expect(constructorCall?.endpoint).not.toContain('proxy.example.com');
47
55
  });
48
- it('should not attach proxy middleware even when proxy is enabled', ()=>{
49
- const mockUse = jest.fn();
50
- MockedS3Client.mockImplementation(()=>({
51
- config: {},
52
- middlewareStack: {
53
- use: mockUse,
54
- add: jest.fn()
55
- }
56
- }));
57
- renderHook(()=>usePresigningS3Client(), {
56
+ it('should rewrite presigned URLs from target to proxy endpoint', ()=>{
57
+ const { result } = renderHook(()=>usePresigningS3Client(), {
58
58
  wrapper: createTestWrapper(proxyConfig, testCredentials)
59
59
  });
60
- expect(mockUse).not.toHaveBeenCalled();
60
+ const targetUrl = 'http://internal-s3.cluster.local/my-bucket/my-key?X-Amz-Signature=abc123';
61
+ const rewritten = result.current.rewriteUrl(targetUrl);
62
+ expect(rewritten).toContain('https://proxy.example.com');
63
+ expect(rewritten).toContain('/s3/my-bucket/my-key');
64
+ expect(rewritten).toContain('X-Amz-Signature=abc123');
61
65
  });
62
- it('should use origin-relative proxy endpoint resolved with window.location.origin', ()=>{
66
+ it('should rewrite URLs with origin-relative proxy endpoint', ()=>{
63
67
  const originRelativeConfig = {
64
68
  ...testConfig,
65
69
  proxy: {
@@ -68,13 +72,30 @@ describe('usePresigningS3Client', ()=>{
68
72
  target: 'http://artesca-data-connector-s3api.zenko.svc.cluster.local'
69
73
  }
70
74
  };
71
- const originalOrigin = window.location.origin;
72
- renderHook(()=>usePresigningS3Client(), {
75
+ const { result } = renderHook(()=>usePresigningS3Client(), {
73
76
  wrapper: createTestWrapper(originRelativeConfig, testCredentials)
74
77
  });
75
78
  expect(MockedS3Client).toHaveBeenCalledWith(expect.objectContaining({
76
- endpoint: `${originalOrigin}/zenko/s3`
79
+ endpoint: 'http://artesca-data-connector-s3api.zenko.svc.cluster.local'
77
80
  }));
81
+ const targetUrl = 'http://artesca-data-connector-s3api.zenko.svc.cluster.local/bucket/key?X-Amz-Signature=xyz';
82
+ const rewritten = result.current.rewriteUrl(targetUrl);
83
+ expect(rewritten).toContain('/zenko/s3/bucket/key');
84
+ expect(rewritten).toContain('X-Amz-Signature=xyz');
85
+ });
86
+ it('should not attach proxy middleware even when proxy is enabled', ()=>{
87
+ const mockUse = jest.fn();
88
+ MockedS3Client.mockImplementation(()=>({
89
+ config: {},
90
+ middlewareStack: {
91
+ use: mockUse,
92
+ add: jest.fn()
93
+ }
94
+ }));
95
+ renderHook(()=>usePresigningS3Client(), {
96
+ wrapper: createTestWrapper(proxyConfig, testCredentials)
97
+ });
98
+ expect(mockUse).not.toHaveBeenCalled();
78
99
  });
79
100
  it('should use direct endpoint when proxy is disabled', ()=>{
80
101
  const disabledProxyConfig = {
@@ -91,14 +112,14 @@ describe('usePresigningS3Client', ()=>{
91
112
  endpoint: testConfig.endpoint
92
113
  }));
93
114
  });
94
- it('should return the same client instance on re-render when s3ConfigIdentifier is unchanged', ()=>{
115
+ it('should return the same result on re-render when s3ConfigIdentifier is unchanged', ()=>{
95
116
  const { result, rerender } = renderHook(()=>usePresigningS3Client(), {
96
117
  wrapper: createTestWrapper(testConfig, testCredentials)
97
118
  });
98
- const firstClient = result.current;
119
+ const first = result.current;
99
120
  rerender();
100
- const secondClient = result.current;
101
- expect(firstClient).toBe(secondClient);
121
+ const second = result.current;
122
+ expect(first).toBe(second);
102
123
  expect(MockedS3Client).toHaveBeenCalledTimes(1);
103
124
  });
104
125
  });
@@ -14,5 +14,5 @@
14
14
  */
15
15
  export { useCreateS3InfiniteQueryHook } from './useCreateS3InfiniteQueryHook';
16
16
  export { useCreateS3LoginHook } from './useCreateS3LoginHook';
17
- export { useCreatePresigningMutationHook, useCreateS3FunctionMutationHook, useCreateS3MutationHook, } from './useCreateS3MutationHook';
17
+ export { type UrlRewriter, useCreatePresigningMutationHook, useCreateS3FunctionMutationHook, useCreateS3MutationHook, } from './useCreateS3MutationHook';
18
18
  export { useCreateS3QueryHook } from './useCreateS3QueryHook';
@@ -3,4 +3,5 @@ import { type UseMutationOptions, type UseMutationResult } from '@tanstack/react
3
3
  import { type EnhancedS3Error } from '../../utils/errorHandling';
4
4
  export declare function useCreateS3MutationHook<TInput extends object, TOutput>(Command: new (input: TInput) => any, operationName: string, invalidationKeys?: string[]): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput>, "mutationFn">) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
5
5
  export declare function useCreateS3FunctionMutationHook<TInput, TOutput>(operation: (s3Client: S3Client, input: TInput) => Promise<TOutput>, invalidationKeys?: string[]): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput, unknown>, "mutationFn"> | undefined) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
6
- export declare function useCreatePresigningMutationHook<TInput, TOutput>(operation: (s3Client: S3Client, input: TInput) => Promise<TOutput>, invalidationKeys?: string[]): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput, unknown>, "mutationFn"> | undefined) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
6
+ export type UrlRewriter = (url: string) => string;
7
+ export declare function useCreatePresigningMutationHook<TInput, TOutput>(operation: (s3Client: S3Client, input: TInput, rewriteUrl: UrlRewriter) => Promise<TOutput>, invalidationKeys?: string[]): (options?: Omit<UseMutationOptions<TOutput, EnhancedS3Error, TInput>, "mutationFn">) => UseMutationResult<TOutput, EnhancedS3Error, TInput>;
@@ -57,6 +57,24 @@ function useCreateS3FunctionMutationHook(operation, invalidationKeys) {
57
57
  return createFunctionMutationHook(operation, useS3Client, invalidationKeys);
58
58
  }
59
59
  function useCreatePresigningMutationHook(operation, invalidationKeys) {
60
- return createFunctionMutationHook(operation, usePresigningS3Client, invalidationKeys);
60
+ return (options)=>{
61
+ const { s3ConfigIdentifier } = useDataBrowserContext();
62
+ const { client, rewriteUrl } = usePresigningS3Client();
63
+ const queryClient = useQueryClient();
64
+ return useMutation({
65
+ mutationFn: async (params)=>operation(client, params, rewriteUrl),
66
+ onSuccess: (_data, _variables)=>{
67
+ if (invalidationKeys) invalidationKeys.forEach((key)=>{
68
+ queryClient.invalidateQueries({
69
+ queryKey: [
70
+ s3ConfigIdentifier,
71
+ key
72
+ ]
73
+ });
74
+ });
75
+ },
76
+ ...options
77
+ });
78
+ };
61
79
  }
62
80
  export { useCreatePresigningMutationHook, useCreateS3FunctionMutationHook, useCreateS3MutationHook };
@@ -3,7 +3,7 @@ import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
3
3
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
4
4
  import { createS3OperationError } from "../utils/errorHandling.js";
5
5
  import { useCreatePresigningMutationHook } from "./factories/index.js";
6
- const generatePresignedDownloadUrl = async (client, config)=>{
6
+ const generatePresignedDownloadUrl = async (client, config, rewriteUrl)=>{
7
7
  try {
8
8
  const { Bucket, Key, expiresIn = 3600, ...awsOptions } = config;
9
9
  const command = new GetObjectCommand({
@@ -16,7 +16,7 @@ const generatePresignedDownloadUrl = async (client, config)=>{
16
16
  });
17
17
  const expiresAt = new Date(Date.now() + 1000 * expiresIn);
18
18
  return {
19
- Url: url,
19
+ Url: rewriteUrl(url),
20
20
  ExpiresAt: expiresAt,
21
21
  Bucket: Bucket,
22
22
  Key: Key,
@@ -26,7 +26,7 @@ const generatePresignedDownloadUrl = async (client, config)=>{
26
26
  throw createS3OperationError(error, 'GeneratePresignedDownload', config.Bucket, config.Key);
27
27
  }
28
28
  };
29
- const generatePresignedUploadUrl = async (client, config)=>{
29
+ const generatePresignedUploadUrl = async (client, config, rewriteUrl)=>{
30
30
  try {
31
31
  const { Bucket, Key, expiresIn = 3600, ...awsOptions } = config;
32
32
  const command = new PutObjectCommand({
@@ -39,7 +39,7 @@ const generatePresignedUploadUrl = async (client, config)=>{
39
39
  });
40
40
  const expiresAt = new Date(Date.now() + 1000 * expiresIn);
41
41
  return {
42
- Url: url,
42
+ Url: rewriteUrl(url),
43
43
  ExpiresAt: expiresAt,
44
44
  Bucket: Bucket,
45
45
  Key: Key
@@ -48,7 +48,7 @@ const generatePresignedUploadUrl = async (client, config)=>{
48
48
  throw createS3OperationError(error, 'GeneratePresignedUpload', config.Bucket, config.Key);
49
49
  }
50
50
  };
51
- const generatePresignedPost = async (client, config)=>{
51
+ const generatePresignedPost = async (client, config, rewriteUrl)=>{
52
52
  try {
53
53
  const { Bucket, Key, expiresIn = 3600, ...awsOptions } = config;
54
54
  const presignedPost = await createPresignedPost(client, {
@@ -60,6 +60,7 @@ const generatePresignedPost = async (client, config)=>{
60
60
  const expiresAt = new Date(Date.now() + 1000 * expiresIn);
61
61
  return {
62
62
  ...presignedPost,
63
+ url: rewriteUrl(presignedPost.url),
63
64
  ExpiresAt: expiresAt
64
65
  };
65
66
  } catch (error) {
@@ -0,0 +1,18 @@
1
+ import { type GetObjectCommandInput } from '@aws-sdk/client-s3';
2
+ import { type EnhancedS3Error } from '../utils/errorHandling';
3
+ type DownloadObjectInput = GetObjectCommandInput & {
4
+ filename: string;
5
+ };
6
+ /**
7
+ * Hook for downloading S3 objects via the S3 SDK client.
8
+ *
9
+ * Uses the normal S3 client (with proxy middleware) to fetch the object,
10
+ * avoiding presigned URL signature issues through reverse proxies.
11
+ *
12
+ * Download strategy:
13
+ * 1. If File System Access API is available (Chrome/Edge): stream
14
+ * directly to disk via showSaveFilePicker, no memory buffering
15
+ * 2. Otherwise: buffer via transformToByteArray and download via Blob URL
16
+ */
17
+ export declare const useDownloadObject: () => import("@tanstack/react-query").UseMutationResult<void, EnhancedS3Error, DownloadObjectInput, unknown>;
18
+ export {};
@@ -0,0 +1,59 @@
1
+ import { GetObjectCommand } from "@aws-sdk/client-s3";
2
+ import { useMutation } from "@tanstack/react-query";
3
+ import { createS3OperationError } from "../utils/errorHandling.js";
4
+ import { useS3Client } from "./useS3Client.js";
5
+ const supportsStreamDownload = ()=>'undefined' != typeof window && 'showSaveFilePicker' in window;
6
+ async function streamDownload(stream, filename) {
7
+ const handle = await window.showSaveFilePicker({
8
+ suggestedName: filename
9
+ });
10
+ const writable = await handle.createWritable();
11
+ await stream.pipeTo(writable);
12
+ }
13
+ function downloadViaBlob(bytes, filename, contentType) {
14
+ const blob = new Blob([
15
+ bytes
16
+ ], {
17
+ type: contentType
18
+ });
19
+ const url = URL.createObjectURL(blob);
20
+ const link = document.createElement('a');
21
+ link.href = url;
22
+ link.download = filename;
23
+ link.style.display = 'none';
24
+ document.body.appendChild(link);
25
+ link.click();
26
+ setTimeout(()=>{
27
+ URL.revokeObjectURL(url);
28
+ if (document.body.contains(link)) document.body.removeChild(link);
29
+ }, 100);
30
+ }
31
+ const useDownloadObject = ()=>{
32
+ const s3Client = useS3Client();
33
+ return useMutation({
34
+ mutationFn: async ({ filename, ...params })=>{
35
+ try {
36
+ if (supportsStreamDownload()) {
37
+ const response = await s3Client.send(new GetObjectCommand(params));
38
+ if (!response.Body) throw new Error('Empty response body');
39
+ try {
40
+ const stream = response.Body.transformToWebStream();
41
+ await streamDownload(stream, filename);
42
+ return;
43
+ } catch (error) {
44
+ if (error?.name === 'AbortError') return;
45
+ console.warn('Stream download failed, falling back to blob:', error);
46
+ }
47
+ }
48
+ const response = await s3Client.send(new GetObjectCommand(params));
49
+ if (!response.Body) throw new Error('Empty response body');
50
+ const bytes = await response.Body.transformToByteArray();
51
+ const contentType = response.ContentType || 'application/octet-stream';
52
+ downloadViaBlob(bytes, filename, contentType);
53
+ } catch (error) {
54
+ throw createS3OperationError(error, 'DownloadObject', params.Bucket, params.Key);
55
+ }
56
+ }
57
+ });
58
+ };
59
+ export { useDownloadObject };
@@ -1,13 +1,20 @@
1
1
  import { S3Client } from '@aws-sdk/client-s3';
2
2
  /**
3
- * Hook to get an S3 client specifically for presigned URL generation.
3
+ * Hook for presigned URL generation that works through reverse proxies.
4
4
  *
5
- * When proxy is configured, presigned URLs must use the proxy endpoint
6
- * (browser-accessible) rather than the internal target. This client is
7
- * created WITHOUT proxy middleware so that `getSignedUrl` produces URLs
8
- * pointing to the proxy endpoint instead of the internal S3 service.
5
+ * Returns an S3 client pointed at the real S3 target (for correct SigV4
6
+ * signing) plus a `rewriteUrl` function that rewrites the generated URL
7
+ * to go through the proxy so browsers can reach it.
9
8
  *
10
- * For non-proxy configurations, this returns a standard S3 client
11
- * identical to `useS3Client`.
9
+ * Flow:
10
+ * 1. getSignedUrl() signs against the S3 target → signature covers
11
+ * target host + clean path (no proxy prefix)
12
+ * 2. rewriteUrl() replaces host/protocol/port and prepends the proxy
13
+ * path prefix → URL is browser-accessible
14
+ * 3. Browser hits the proxy (NGINX), which strips the prefix and
15
+ * forwards to S3 with the target Host → matches the signature
12
16
  */
13
- export declare const usePresigningS3Client: () => S3Client;
17
+ export declare const usePresigningS3Client: () => {
18
+ client: S3Client;
19
+ rewriteUrl: (url: string) => string;
20
+ };
@@ -1,19 +1,47 @@
1
1
  import { S3Client } from "@aws-sdk/client-s3";
2
2
  import { useMemo } from "react";
3
3
  import { useDataBrowserContext } from "../components/providers/DataBrowserProvider.js";
4
- import { resolveProxyEndpoint } from "../utils/proxyMiddleware.js";
4
+ import { parseEndpoint, resolveProxyEndpoint } from "../utils/proxyMiddleware.js";
5
5
  const usePresigningS3Client = ()=>{
6
6
  const { s3ConfigIdentifier, getS3Config } = useDataBrowserContext();
7
7
  if (!getS3Config) throw new Error('usePresigningS3Client: S3 config not available. Ensure DataBrowserProvider has getS3Config prop set.');
8
8
  return useMemo(()=>{
9
9
  const config = getS3Config();
10
- const endpoint = config.proxy?.enabled ? resolveProxyEndpoint(config.proxy.endpoint) : config.endpoint;
11
- return new S3Client({
12
- endpoint,
10
+ if (!config.proxy?.enabled) {
11
+ const client = new S3Client({
12
+ endpoint: config.endpoint,
13
+ credentials: config.credentials,
14
+ forcePathStyle: config.forcePathStyle ?? true,
15
+ region: config.region
16
+ });
17
+ return {
18
+ client,
19
+ rewriteUrl: (url)=>url
20
+ };
21
+ }
22
+ const target = parseEndpoint(config.proxy.target);
23
+ const targetEndpoint = `${target.protocol}//${target.hostname}${80 !== target.port && 443 !== target.port ? `:${target.port}` : ''}`;
24
+ const client = new S3Client({
25
+ endpoint: targetEndpoint,
13
26
  credentials: config.credentials,
14
27
  forcePathStyle: config.forcePathStyle ?? true,
15
28
  region: config.region
16
29
  });
30
+ const proxyEndpoint = resolveProxyEndpoint(config.proxy.endpoint);
31
+ const proxy = parseEndpoint(proxyEndpoint);
32
+ const proxyOrigin = `${proxy.protocol}//${proxy.hostname}${80 !== proxy.port && 443 !== proxy.port ? `:${proxy.port}` : ''}`;
33
+ const proxyPathPrefix = proxy.pathname || '';
34
+ const rewriteUrl = (url)=>{
35
+ const parsed = new URL(url);
36
+ const rewritten = new URL(proxyOrigin);
37
+ rewritten.pathname = proxyPathPrefix + parsed.pathname;
38
+ rewritten.search = parsed.search;
39
+ return rewritten.toString();
40
+ };
41
+ return {
42
+ client,
43
+ rewriteUrl
44
+ };
17
45
  }, [
18
46
  s3ConfigIdentifier
19
47
  ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scality/data-browser-library",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "A modular React component library for browsing S3 buckets and objects",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",