I have an S3-like react-application with redux. I'm using AWS SDK v3 for JS, and initializing my client like this:
auth.js
export const s3Client = new S3Client({
region: 'default',
credentials: {
accessKeyId: 'testTestAccess',
secretAccessKey: 'testTestSecret'
},
endpoint: `${document.URL}s3/`
});
My requests go through our proxy, that's the reason why i can leave credentials exactly like in code above, so it can be any string. But i'm using s3 signed url, and it uses credentials in query strings.
That's how i send my request with redux-thunk
authReducer.js
const initialState = {
secretKey: 'initSecretKey',
accessKey: 'initAccessKey',
keysCreated: false
};
export const fetchKeys = createAsyncThunk(
'auth/fetchKeys',
async (_, { rejectWithValue }) => {
try {
const response = await axiosInstance.get();
if (response.statusText !== 'OK') {
throw new Error('Error!');
}
return response.data.secrets;
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const authSlice = createSlice({
name: 'auth',
initialState,
extraReducers: {
[fetchKeys.pending]: (state, action) => {
console.log('PENDING...');
},
[fetchKeys.fulfilled]: (state, action) => {
const { AccessKey, SecretKey } = action.payload;
state.secretKey = SecretKey;
state.accessKey = AccessKey;
state.keysCreated = true;
},
[fetchKeys.rejected]: (state, action) => {
console.log('ERROR');
}
},
reducers: {
setSecretKey: (state, action) => ({
...state,
secretKey: action.payload
}),
setAccessKey: (state, action) => ({
...state,
accessKey: action.payload
}),
setKeysCreated: (state, action) => ({
...state,
keysCreated: action.payload
})
}
});
export const { setSecretKey, setAccessKey, setKeysCreated } =
authSlice.actions;
export default authSlice.reducer;
MainPage.jsx
const MainPage = () => {
const dispatch = useDispatch();
const { keysCreated } = useSelector((state) => state.authReducer);
useEffect(() => {
dispatch(fetchKeys());
}, [dispatch]);
if (keysCreated) {
return <Content />
}
return <Loader />
};
So what i want to do:
- When the page is rendering, i'm sending an async request to keygen with axios and redux-thunk
- Showing the page only if i got the keys
- (!) Replace mock keys in s3 client instance with new keys from keygen, when request is succeeded, so that i can make signed urls.
How can i do that? I get keys from store only once, so looks like i need subscribe on changes
const { secretKey, accessKey } = store.getState().authReducer;
export const s3Client = new S3Client({
credentials: {
accessKeyId: accessKey, // 'initAccessKey' from initial state
secretAccessKey: secretKey // 'initSecretKey' from initial state
}
});
CodePudding user response:
I resolved my problem with hook.
auth.js
export const s3Client = new S3Client({
region: 'default',
credentials: {
accessKeyId: 'initAccessKey',
secretAccessKey: 'initSecretKey'
}
});
export const useS3Client = () => {
const { accessKey, secretKey, keysCreated } = useSelector(
(state) => state.authReducer
);
const client = useMemo(() => {
if (keysCreated) {
return new S3Client({
region: 'default',
credentials: {
accessKeyId: accessKey,
secretAccessKey: secretKey
}
});
}
return s3Client;
}, [accessKey, secretKey, keysCreated]);
return [client, keysCreated];
};
I changed a little my functions for API signed requests:
// i added client as 1st argument
export const getUrl = async (client, object, expirationSec = 300) => {
const { name, absolutePath, parentBucket } = object;
const params = {
Bucket: parentBucket,
Key: absolutePath
};
return await getSignedUrl(client, new GetObjectCommand(params), {
expiresIn: expirationSec
}).catch((error) => setError(error, 'SIGNED URL GETTING FAILED'));
}
And then i can use it in my components:
MainPage.jsx
const MainPageContainer = () => {
const dispatch = useDispatch();
const [, keysCreated] = useS3Client();
useEffect(() => {
dispatch(fetchKeys());
}, [dispatch]);
if (!keysCreated) return <Loader />;
return <MainPage />;
};
SignUrlForm.jsx
const SignUrlForm = () => {
const [client] = useS3Client();
const onGetSignedUrlClick = (object) => {
getUrl(client, object, intevalSec).then((url) => {
// some actions
});
};
return <Button onClick={onGetSignedUrlClick}>Sign URL</Button>;
};