diff --git a/.env b/.env index a57ebe2..177e996 100644 --- a/.env +++ b/.env @@ -1,2 +1 @@ REACT_APP_NEOFS="" -REACT_APP_NETMAP_CONTRACT="0xc4576ea5c3081dd765a17aaaa73d9352e74bdc28" diff --git a/README.md b/README.md index 214254b..7e72cd6 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,6 @@ Send.NeoFS is a simple example of integration with NeoFS network via HTTP protoc Set variables in the `.env` file before executing the commands: - `REACT_APP_NEOFS` - Path to SendFS -- `REACT_APP_CONTAINER_ID` - NeoFS container ID where the objects would be stored -- `REACT_APP_NETMAP_CONTRACT` - NeoFS netmap contract # Send.NeoFS local up @@ -69,9 +67,9 @@ password: proxy_cache_path /srv/neofs_cache/ levels=1:2 keys_zone=neofs_cache:50m max_size=16g inactive=60m use_temp_path=off; server { - set $cid HPUKdZBBtD75jDN8iVb3zoaNACWinuf1vF5kkYpMMbap; - set $data_cid 41tVWBvQVTLGQPHBmXySHsJkcc6X17i39bMuJe9wYhAJ; - set $neofs_http_gateway http.fs.neo.org; + set $cid 7CpJVtBdNvPjjdYwV7q9CghGVPagVLTs71BPQuGQLKSQ; + set $data_cid 754iyTDY8xUtZJZfheSYLUn7jvCkxr79RcbjMt81QykC; + set $neofs_rest_gateway https://rest.fs.neo.org; set $rpc rpc.morph.fs.neo.org:40341; client_max_body_size 100m; proxy_connect_timeout 5m; @@ -79,68 +77,83 @@ server { proxy_read_timeout 5m; send_timeout 5m; default_type application/octet-stream; - + location ~ "^\/chain" { rewrite ^/chain/(.*) /$1 break; proxy_pass https://rpc; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_429 non_idempotent; + } + + location /gate/objects/ { + rewrite ^/gate/(.*) /v1/objects/$data_cid break; + proxy_pass $neofs_rest_gateway; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_429; + proxy_pass_request_headers on; } - + location /gate/upload/ { - rewrite ^/gate/(.*) /upload/$data_cid break; - proxy_pass https://$neofs_http_gateway; + rewrite ^/gate/(.*) /v1/upload/$data_cid break; + proxy_pass $neofs_rest_gateway; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_429; + proxy_pass_request_headers on; proxy_set_header X-Attribute-email $http_x_attribute_email; proxy_set_header X-Attribute-NEOFS-Expiration-Epoch $http_x_attribute_neofs_expiration_epoch; } - + location ~ "^\/gate\/get(/.*)?\/?$" { - rewrite ^/gate/get/(.*) /$data_cid/$1 break; - proxy_pass https://$neofs_http_gateway; + rewrite ^/gate/get/(.*) /v1/get/$data_cid/$1 break; + proxy_pass $neofs_rest_gateway; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_429; + proxy_intercept_errors on; proxy_cache_valid 404 0; proxy_cache_valid 200 15m; + proxy_buffering on; proxy_cache neofs_cache; proxy_cache_methods GET; } - + location /signup_google/ { proxy_pass http://localhost:8084/login?service=google; proxy_intercept_errors on; + proxy_buffering on; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } - + location /signup_github/ { proxy_pass http://localhost:8084/login?service=github; proxy_intercept_errors on; + proxy_buffering on; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } - + location ~ "^\/callback" { rewrite ^/callback\?(.*) /$1 break; proxy_pass http://localhost:8084; } - + location /load { - rewrite '^(/.*)$' /get_by_attribute/$cid/FileName/index.html break; - proxy_pass https://$neofs_http_gateway; + rewrite '^(/.*)$' /v1/objects/$cid/by_attribute/FileName/index.html break; + proxy_pass $neofs_rest_gateway; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_429; + include /etc/nginx/mime.types; } - + location /toc { - rewrite '^(/.*)$' /get_by_attribute/$cid/FileName/index.html break; - proxy_pass https://$neofs_http_gateway; + rewrite '^(/.*)$' /v1/objects/$cid/by_attribute/FileName/index.html break; + proxy_pass $neofs_rest_gateway; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504 http_403 http_429; + include /etc/nginx/mime.types; } - + location / { - rewrite '^(/[0-9a-zA-Z\-]{43,44})$' /get/$cid/$1 break; - rewrite '^/$' /get_by_attribute/$cid/FileName/index.html break; - rewrite '^/([^/]*)$' /get_by_attribute/$cid/FileName/$1 break; - rewrite '^(/.*)$' /get_by_attribute/$cid/FilePath/$1 break; - proxy_pass https://$neofs_http_gateway; + proxy_pass https://$neofs_rest_gateway; } } ``` diff --git a/src/App.tsx b/src/App.tsx index 21d537a..244ddc8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -43,8 +43,6 @@ library.add( interface Environment { version: string | undefined server: string | undefined - netmapContract: string | undefined - epochLine: string | undefined } interface User { @@ -76,8 +74,6 @@ export const App = () => { const [environment] = useState({ version: process.env.REACT_APP_VERSION, server: process.env.REACT_APP_NEOFS, - netmapContract: process.env.REACT_APP_NETMAP_CONTRACT, - epochLine: "c25hcHNob3RFcG9jaA==", }); const [user] = useState(getCookie('X-Bearer') && getCookie('X-Attribute-Email') ? { XBearer: getCookie('X-Bearer'), diff --git a/src/Home.tsx b/src/Home.tsx index fead792..2354c59 100644 --- a/src/Home.tsx +++ b/src/Home.tsx @@ -15,16 +15,6 @@ import { Button, } from 'react-bulma-components'; -function base64ToArrayBuffer(base64: string) { - const bytes: any = new Uint8Array([0, 0, 0, 0]); - const binary_string: string = base64; - const len: number = binary_string.length; - for (let i: number = 0; i < len; i += 1) { - bytes[i] = binary_string.charCodeAt(i); - } - return bytes; -} - const Home = ({ onModal, onScroll, @@ -36,7 +26,7 @@ const Home = ({ }) => { const [files, setFiles] = useState([]); const [dragActive, setDragActive] = useState(false); - const [lifetimeData, setLifetimeData] = useState(12); + const [lifetimeData, setLifetimeData] = useState('12h'); const [isLoading, setLoading] = useState(false); const [isCopied, setCopy] = useState(false); const fileUploadMbLimit: number = 200 * 1024 * 1024; @@ -106,48 +96,26 @@ const Home = ({ } setLoading(true); - api('POST', `/chain`, { - "id":"1", - "jsonrpc":"2.0", - "method":"getstorage", - "params":[ - environment.netmapContract, - environment.epochLine, - ], - }).then((res: any) => { - if (res['result']) { - const epoch_b64: any = atob(res['result']); - const epoch_bytes: any = base64ToArrayBuffer(epoch_b64); - const dv2: any = new DataView(epoch_bytes.buffer); - const epoch: string | number = dv2.getUint32(0, true); - - files.forEach((file: File) => { - const formData: any = new FormData(); - formData.append('file', file); - onUploadFile(formData, file.name, epoch, lifetimeData); - }); - } else { - setLoading(false); - onModal('failed', 'Something went wrong, try again'); - } - }).catch(() => { - setLoading(false); - onModal('failed', 'Something went wrong, try again'); + files.forEach((file: File) => { + onUploadFile(file); }); e.preventDefault(); }; - const onUploadFile = (formData: any, filename: string | null, epoch: string | number, lifetime: string | number) => { - document.cookie = `Bearer=${user.XBearer}; path=/gate/upload; expires=${new Date(Date.now() + 10 * 1000).toUTCString()}`; - api('POST', '/gate/upload/', formData, { - 'X-Attribute-NEOFS-Expiration-Epoch': String(Number(epoch) + Number(lifetime)), - 'X-Attribute-email': user.XAttributeEmail, - 'Content-Type': 'multipart/form-data', + const onUploadFile = (file: any | null) => { + document.cookie = `Bearer=${user.XBearer}; path=/gate/objects; expires=${new Date(Date.now() + 10 * 1000).toUTCString()}`; + api('POST', "/gate/objects/", file, { + 'X-Attributes': JSON.stringify({ + 'FileName': file.name, + 'Email': user.XAttributeEmail, + }), + 'X-Neofs-Expiration-Duration': lifetimeData, + 'Content-Type': file.type, }).then((res: any) => { - res['filename'] = filename; + res['filename'] = file.name; setUploadedObjects((uploadedObjectsTemp: UploadedObject[]) => [...uploadedObjectsTemp, res]); setFiles((files: File[]) => { - const filesTemp = files.filter((item: File) => item.name !== filename); + const filesTemp = files.filter((item: File) => item.name !== file.name); if (filesTemp.length === 0) { setLoading(false); } @@ -211,10 +179,10 @@ const Home = ({ value={lifetimeData} disabled={isLoading} > - - - - + + + + diff --git a/src/Load.tsx b/src/Load.tsx index 2cbe1c7..a551203 100644 --- a/src/Load.tsx +++ b/src/Load.tsx @@ -98,6 +98,7 @@ const Load = ({ > File data {`Filename: ${objectData.filename ? objectData.filename : '-'}`} + {`Content-Type: ${objectData.contentType ? objectData.contentType : '-'}`} {`Size: ${objectData.size ? objectData.size : '-'}`} {`Expiration epoch: ${objectData.expirationEpoch ? objectData.expirationEpoch : '-'}`} {`Owner ID: ${objectData.ownerId ? objectData.ownerId : '-'}`} diff --git a/src/api.ts b/src/api.ts index db9be35..b1f71d0 100644 --- a/src/api.ts +++ b/src/api.ts @@ -7,6 +7,7 @@ export interface ObjectData { containerId?: string | null ownerId?: string | null filename?: string | null + contentType?: string | null size?: string | null expirationEpoch?: string | null } @@ -17,9 +18,8 @@ async function serverRequest(method: Methods, url: string, params: object, heade headers, } - if (json['headers']['Content-Type'] === 'multipart/form-data') { + if (json['headers']['Content-Type']) { json['body'] = params; - delete json['headers']['Content-Type']; } else if (Object.keys(params).length > 0) { json['body'] = JSON.stringify(params); json['headers']['Content-Type'] = 'application/json'; @@ -45,12 +45,14 @@ export default function api(method: Methods, url: string, params: object = {}, h if (method === 'HEAD' && response.status !== 200) { reject(res); } else if (method === 'HEAD' && response.headers) { + const attributes: any = response.headers.get('X-Attributes') ? JSON.parse(response.headers.get('X-Attributes')) : {}; const res: ObjectData = { - 'filename': response.headers.get('X-Attribute-Filename'), + 'filename': attributes['FileName'], + 'contentType': response.headers.get('Content-Type'), 'containerId': response.headers.get('X-Container-Id'), 'ownerId': response.headers.get('X-Owner-Id'), 'size': response.headers.get('Content-Length') ? response.headers.get('Content-Length') : response.headers.get('x-neofs-payload-length'), - 'expirationEpoch': response.headers.get('X-Attribute-Neofs-Expiration-Epoch'), + 'expirationEpoch': attributes['__NEOFS__EXPIRATION_EPOCH'], } resolve(res); } else if (method === 'GET' && url.indexOf(`/gate/get/`) !== -1) {