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

fix(downloads): do not download into browser memory #319

Merged
merged 8 commits into from
Nov 17, 2021
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CRYOSTAT_AUTHORITY=http://localhost:8181
CRYOSTAT_AUTHORITY=http://0.0.0.0:8181
109 changes: 53 additions & 56 deletions src/app/Shared/Services/Api.service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import { Target, TargetService } from './Target.service';
import { Notifications } from '@app/Notifications/Notifications';
import { AuthMethod, LoginService, SessionState } from './Login.service';

type ApiVersion = "v1" | "v2";
type ApiVersion = 'v1' | 'v2' | 'v2.1' | 'beta';

export class HttpError extends Error {
readonly httpResponse: Response;
Expand Down Expand Up @@ -333,65 +333,62 @@ export class ApiService {
}

downloadReport(recording: SavedRecording): void {
this.login.getHeaders().subscribe(headers => {
const req = () =>
fromFetch(recording.reportUrl, {
credentials: 'include',
mode: 'cors',
headers,
})
.pipe(
map(resp => {
if (resp.ok) return resp;
throw new HttpError(resp);
}),
catchError(err => this.handleError<Response>(err, req)),
concatMap(resp => resp.blob()),
);
req().subscribe(resp =>
const body = new window.FormData();
body.append('resource', recording.reportUrl.replace('/api/v1', '/api/beta'));
this.sendRequest('beta', 'auth/token', {
method: 'POST',
body,
})
.pipe(
concatMap(resp => resp.json()),
map(json => json.data.result.resourceUrl)
).subscribe(resourceUrl => {
this.downloadFile(
`${recording.name}.report.html`,
resp,
'text/html')
)
});
resourceUrl,
`${recording.name}.report.html`
);
});
}

downloadRecording(recording: SavedRecording): void {
this.login.getHeaders().subscribe(headers => {
const req = () => fromFetch(recording.downloadUrl, {
credentials: 'include',
mode: 'cors',
headers,
})
.pipe(
map(resp => {
if (resp.ok) return resp;
throw new HttpError(resp);
}),
catchError(err => this.handleError<Response>(err, req)),
concatMap(resp => resp.blob()),
);
req().subscribe(resp =>
const body = new window.FormData();
body.append('resource', recording.downloadUrl.replace('/api/v1', '/api/beta'));
this.sendRequest('beta', 'auth/token', {
method: 'POST',
body,
})
.pipe(
concatMap(resp => resp.json()),
map(json => json.data.result.resourceUrl)
jan-law marked this conversation as resolved.
Show resolved Hide resolved
).subscribe(resourceUrl => {
this.downloadFile(
recording.name + (recording.name.endsWith('.jfr') ? '' : '.jfr'),
resp,
'application/octet-stream')
)
});
resourceUrl,
recording.name + (recording.name.endsWith('.jfr') ? '' : '.jfr')
);
});
}

downloadTemplate(template: EventTemplate): void {
this.target.target().pipe(concatMap(target => {
const url = `targets/${encodeURIComponent(target.connectUrl)}/templates/${encodeURIComponent(template.name)}/type/${encodeURIComponent(template.type)}`;
return this.sendRequest('v1', url)
.pipe(concatMap(resp => resp.text()));
}))
.subscribe(resp => {
this.downloadFile(
`${template.name}.jfc`,
resp,
'application/jfc+xml')
this.target.target()
.pipe(first(), map(target =>
`${this.login.authority}/api/beta/targets/${encodeURIComponent(target.connectUrl)}/templates/${encodeURIComponent(template.name)}/type/${encodeURIComponent(template.type)}`
))
.subscribe(resource => {
const body = new window.FormData();
body.append('resource', resource);
this.sendRequest('beta', 'auth/token', {
method: 'POST',
body,
})
.pipe(
concatMap(resp => resp.json()),
map(json => json.data.result.resourceUrl),
).subscribe(resourceUrl => {
this.downloadFile(
resourceUrl,
`${template.name}.jfc`
);
});
});
}

Expand Down Expand Up @@ -455,14 +452,14 @@ export class ApiService {
return req();
}

private downloadFile(filename: string, data: BlobPart, type: string): void {
const blob = new window.Blob([ data ], { type } );
const url = window.URL.createObjectURL(blob);
private downloadFile(url: string, filename: string): void {
andrewazores marked this conversation as resolved.
Show resolved Hide resolved
const anchor = document.createElement('a');
anchor.setAttribute('style', 'display: none; visibility: hidden;');
anchor.target = '_blank;'
anchor.download = filename;
anchor.href = url;
anchor.click();
window.setTimeout(() => window.URL.revokeObjectURL(url));
anchor.remove();
}

private handleError<T>(error: Error, retry: () => Observable<T>): ObservableInput<T> {
Expand Down
6 changes: 4 additions & 2 deletions src/app/Shared/Services/Login.service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,14 +124,16 @@ export class LoginService {
const headers = new window.Headers();
if (!!token && !!method) {
headers.set('Authorization', `${method} ${token}`)
} else if (method === AuthMethod.NONE) {
headers.set('Authorization', AuthMethod.NONE);
}
return headers;
}

getHeaders(): Observable<Headers> {
const authorization = combineLatest([this.getToken(), this.getAuthMethod()])
.pipe(
map((parts: [string, string]) => this.getAuthHeaders(parts[0], parts[1])),
map((parts: [string, AuthMethod]) => this.getAuthHeaders(parts[0], parts[1])),
first(),
);
return combineLatest([authorization, this.target.target()])
Expand All @@ -155,7 +157,7 @@ export class LoginService {
return this.token.asObservable();
}

getAuthMethod(): Observable<string> {
getAuthMethod(): Observable<AuthMethod> {
return this.authMethod.asObservable();
}

Expand Down