Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle uploadFile s3 errors more gracefully #1942

Merged
merged 2 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions app/src/os/realm.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,24 +206,24 @@ export class RealmService extends AbstractService<RealmUpdateTypes> {
// Used in onboarding before a session exists.
async uploadFile(
args: FileUploadParams
): Promise<{ Location: string; key: string } | undefined> {
if (!this.services) return;
): Promise<{ Location: string; key: string } | null> {
if (!this.services?.ship) return null;

const credentials = this.services.ship?.credentials;
const credentials = this.services.ship.credentials;

if (!credentials) {
log.error('realm.service.ts:', 'No credentials found');
return;
return null;
}

const patp = this.services.ship?.patp;
const patp = this.services.ship.patp;

if (!patp) {
log.error('realm.service.ts:', 'No patp found');
return;
return null;
}

return this.services.ship?.uploadFile(args);
return this.services.ship.uploadFile(args);
}

async updatePassword(patp: string, password: string) {
Expand Down
97 changes: 50 additions & 47 deletions app/src/os/services/ship/ship.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,61 +297,64 @@ export class ShipService extends AbstractService<any> {
});

return { credentials, configuration };
} catch (e) {
log.error('ship.service.ts, getS3Bucket(): error getting credentials', e);
} catch {
log.error('ship.service.ts: Failed to get S3 bucket.');

return null;
}
}

public async uploadFile(
args: FileUploadParams
): Promise<{ Location: string; key: string }> {
return await new Promise((resolve, reject) => {
this.getS3Bucket()
.then(async (response) => {
console.log('getS3Bucket response: ', response);
if (!response) return;
// a little shim to handle people who accidentally included their bucket at the front of the credentials.endpoint
let endp = response.credentials.endpoint;
if (endp.split('.')[0] === response.configuration.currentBucket) {
endp = endp.split('.').slice(1).join('.');
}
const client = new S3Client({
credentials: response.credentials,
endpoint: endp,
signatureVersion: 'v4',
});
let fileContent, fileName, fileExtension;
if (args.source === 'file' && typeof args.content === 'string') {
fileContent = fs.readFileSync(args.content);
const fileParts = args.content.split('.');
fileName = fileParts.slice(0, -1);
// only take the filename, not the path
fileName = fileName[0].split('/').pop();
fileExtension = fileParts.pop();
} else if (args.source === 'buffer') {
fileContent = Buffer.from(args.content, 'base64');
fileName = 'clipboard';
fileExtension = args.contentType.split('/')[1];
}
if (!fileContent) log.warn('No file content found');
const key = `${
this.patp
}/${moment().unix()}-${fileName}.${fileExtension}`;
const params = {
Bucket: response.configuration.currentBucket,
Key: key,
Body: fileContent as Buffer,
ACL: StorageAcl.PublicRead,
ContentType: args.contentType,
};
const { Location } = await client.upload(params).promise();
resolve({ Location, key });
})
.catch(reject);
});
): Promise<{ Location: string; key: string } | null> {
try {
const response = await this.getS3Bucket();
if (!response) return null;

// a little shim to handle people who accidentally included their bucket at the front of the credentials.endpoint
let endp = response.credentials.endpoint;
if (endp.split('.')[0] === response.configuration.currentBucket) {
endp = endp.split('.').slice(1).join('.');
}
const client = new S3Client({
credentials: response.credentials,
endpoint: endp,
signatureVersion: 'v4',
});
let fileContent, fileName, fileExtension;
if (args.source === 'file' && typeof args.content === 'string') {
fileContent = fs.readFileSync(args.content);
const fileParts = args.content.split('.');
fileName = fileParts.slice(0, -1);
// only take the filename, not the path
fileName = fileName[0].split('/').pop();
fileExtension = fileParts.pop();
} else if (args.source === 'buffer') {
fileContent = Buffer.from(args.content, 'base64');
fileName = 'clipboard';
fileExtension = args.contentType.split('/')[1];
}
if (!fileContent) log.warn('No file content found');
const key = `${
this.patp
}/${moment().unix()}-${fileName}.${fileExtension}`;
const params = {
Bucket: response.configuration.currentBucket,
Key: key,
Body: fileContent as Buffer,
ACL: StorageAcl.PublicRead,
ContentType: args.contentType,
};
const { Location } = await client.upload(params).promise();

return { Location, key };
} catch {
log.error('ship.service.ts: Failed to upload file.');

return null;
}
}

public async deleteFile(args: { key: string }): Promise<any> {
return await new Promise((resolve, reject) => {
this.getS3Bucket()
Expand Down
23 changes: 12 additions & 11 deletions app/src/renderer/apps/Courier/components/ChatInputBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,20 @@ export const ChatInputBox = ({
}, [attachments]);

const uploadFile = useCallback(
(params: FileUploadParams) => {
async (params: FileUploadParams) => {
setIsUploading(true);
setUploadError('');
(ShipIPC.uploadFile(params) as Promise<any>)
.then((data: { Location: string; key: string }) => {
console.log(data);
const url = data.Location;
setAttachment([...attachments, url]);
})
.catch(() => {
setUploadError('Failed upload, please try again.');
})
.finally(() => setIsUploading(false));

try {
const data = await ShipIPC.uploadFile(params);
if (!data) throw new Error('Failed upload, please try again.');

setAttachment([...attachments, data.Location]);
} catch (e: string | any) {
setUploadError(e);
} finally {
setIsUploading(false);
}
},
[attachments]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const AccountPassportSection = ({ account }: Props) => {
content: file.path,
contentType: file.type,
});
if (!result) return null;

return result.Location;
};
Expand Down
15 changes: 9 additions & 6 deletions app/src/renderer/onboarding/steps/PassportStep.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { track } from '@amplitude/analytics-browser';

import { useToggle } from '@holium/design-system/util';
import { OnboardingStorage, PassportDialog } from '@holium/shared';

import { FileUploadParams } from '../../../os/services/ship/ship.service';
Expand All @@ -15,7 +16,7 @@ export const PassportStep = ({ setStep }: StepProps) => {
const [nicknameSrc, setNickname] = useState<string | null>(nickname);
const [sigilColor, setSigilColor] = useState<string | undefined>('#000000');

const [isReady, setIsReady] = useState(false);
const loading = useToggle(true);

const isHoliumHosted = serverType === 'hosted';

Expand Down Expand Up @@ -48,18 +49,18 @@ export const PassportStep = ({ setStep }: StepProps) => {
OnboardingIPC.getPassport()
.then((ourPassport) => {
if (!ourPassport) {
setIsReady(true);
loading.toggleOff();
return;
}
setNickname(ourPassport?.nickname);
setAvatarSrc(ourPassport?.avatar);
setDescription(ourPassport?.bio);
setSigilColor(ourPassport?.color);
setIsReady(true);
loading.toggleOff();
})
.catch((e) => {
console.error(e);
setIsReady(true);
loading.toggleOff();
});
}, []);

Expand All @@ -74,7 +75,9 @@ export const PassportStep = ({ setStep }: StepProps) => {
contentType: file.type,
};
const result = await RealmIPC.uploadFile(params);
return result?.Location;
if (!result) return null;

return result.Location;
};

const onBack = () => {
Expand Down Expand Up @@ -117,7 +120,7 @@ export const PassportStep = ({ setStep }: StepProps) => {
return (
<PassportDialog
patp={serverId ?? ''}
loading={!isReady}
loading={loading.isOn}
prefilledColor={sigilColor}
prefilledNickname={nicknameSrc ?? ''}
prefilledDescription={descriptionSrc ?? ''}
Expand Down
2 changes: 1 addition & 1 deletion shared/src/onboarding/components/PassportCardAvatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ type Props = {
color?: string;
initialAvatarSrc: string | undefined;
setAvatarSrc: (src?: string) => void;
onUploadFile: (file: File) => Promise<string | undefined>;
onUploadFile: (file: File) => Promise<string | null>;
};

export const PassportCardAvatar = ({
Expand Down
2 changes: 1 addition & 1 deletion shared/src/onboarding/components/PassportForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type Props = {
setNickname: (nickname: string) => void;
setDescription: (description: string) => void;
setAvatarSrc: (src?: string) => void;
onUploadFile: (file: File) => Promise<string | undefined>;
onUploadFile: (file: File) => Promise<string | null>;
};

export const PassportForm = ({
Expand Down
4 changes: 2 additions & 2 deletions shared/src/onboarding/dialogs/Passport/PassportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type Props = {
prefilledNickname: string;
prefilledDescription: string;
prefilledAvatarSrc: string;
onUploadFile: (file: File) => Promise<string | undefined>;
onUploadFile: (file: File) => Promise<string | null>;
onBack?: () => void;
onNext: (
nickname: string,
Expand Down Expand Up @@ -129,7 +129,7 @@ export const PassportDialog = ({
</Flex>
}
onBack={onBack}
onNext={handleOnNext}
onNext={loading ? undefined : handleOnNext}
/>
);
};