github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/services/adhoc.ts (about) 1 import { Result } from '@webapp/util/fp'; 2 import { CustomError } from 'ts-custom-error'; 3 import { FlamebearerProfileSchema, Profile } from '@pyroscope/models/src'; 4 import { AllProfilesSchema, AllProfiles } from '@webapp/models/adhoc'; 5 import type { ZodError } from 'zod'; 6 import { z } from 'zod'; 7 import { request, parseResponse } from './base'; 8 import type { RequestError } from './base'; 9 10 const uploadResponseSchema = z.object({ 11 flamebearer: FlamebearerProfileSchema, 12 id: z.string().min(1), 13 }); 14 type UploadResponse = z.infer<typeof uploadResponseSchema>; 15 16 export async function upload( 17 file: File, 18 fileTypeData?: { spyName: string; units: string } 19 ): Promise< 20 Result<UploadResponse, FileToBase64Error | RequestError | ZodError> 21 > { 22 // prepare body 23 const b64 = await fileToBase64(file); 24 if (b64.isErr) { 25 return Result.err<UploadResponse, FileToBase64Error>(b64.error); 26 } 27 28 const response = await request('/api/adhoc/v1/upload', { 29 method: 'POST', 30 body: JSON.stringify({ 31 filename: file.name, 32 profile: b64.value, 33 fileTypeData: fileTypeData || undefined, 34 }), 35 }); 36 return parseResponse(response, uploadResponseSchema); 37 } 38 39 export async function retrieve( 40 id: string 41 ): Promise<Result<Profile, RequestError | ZodError>> { 42 const response = await request(`/api/adhoc/v1/profile/${id}`); 43 return parseResponse<Profile>(response, FlamebearerProfileSchema); 44 } 45 46 export async function retrieveDiff( 47 leftId: string, 48 rightId: string 49 ): Promise<Result<Profile, RequestError | ZodError>> { 50 const response = await request(`/api/adhoc/v1/diff/${leftId}/${rightId}`); 51 return parseResponse<Profile>(response, FlamebearerProfileSchema); 52 } 53 54 export async function retrieveAll(): Promise< 55 Result<AllProfiles, RequestError | ZodError> 56 > { 57 const response = await request(`/api/adhoc/v1/profiles`); 58 return parseResponse(response, AllProfilesSchema); 59 } 60 61 /** 62 * represents an error when trying to convert a File to base64 63 */ 64 export class FileToBase64Error extends CustomError { 65 public constructor( 66 public filename: string, 67 public message: string, 68 public cause?: Error | DOMException 69 ) { 70 super(message); 71 } 72 } 73 74 export default function fileToBase64( 75 file: File 76 ): Promise<Result<string, FileToBase64Error>> { 77 return new Promise((resolve) => { 78 const reader = new FileReader(); 79 80 reader.onloadend = () => { 81 // this is always called, even on failures 82 if (!reader.error) { 83 if (!reader.result) { 84 return resolve( 85 Result.err(new FileToBase64Error(file.name, 'No result')) 86 ); 87 } 88 89 // reader can be used with 'readAsArrayBuffer' which returns an ArrayBuffer 90 // therefore for the sake of the compiler we must check its value 91 if (typeof reader.result === 'string') { 92 // remove the prefix 93 const base64result = reader.result.split(';base64,')[1]; 94 if (!base64result) { 95 return resolve( 96 Result.err( 97 new FileToBase64Error(file.name, 'Failed to strip prefix') 98 ) 99 ); 100 } 101 102 // split didn't work 103 if (base64result === reader.result) { 104 return resolve( 105 Result.err( 106 new FileToBase64Error(file.name, 'Failed to strip prefix') 107 ) 108 ); 109 } 110 111 // the string is prefixed with 112 return resolve(Result.ok(base64result)); 113 } 114 } 115 116 // should not happen 117 return resolve(Result.err(new FileToBase64Error(file.name, 'No result'))); 118 }; 119 120 reader.onerror = () => { 121 resolve( 122 Result.err( 123 new FileToBase64Error( 124 file.name, 125 'File reading has failed', 126 reader.error || undefined 127 ) 128 ) 129 ); 130 }; 131 132 reader.readAsDataURL(file); 133 }); 134 }