add file chuck upload, file validation, redirect to home page incase route doesnt exist and refresh token interceptor

This commit is contained in:
Peter Maquiran
2023-11-09 11:45:04 +01:00
parent a05f85a37f
commit a16e97a59a
41 changed files with 48604 additions and 1902 deletions
+10
View File
@@ -0,0 +1,10 @@
export type Either<T, E> = { isOk: true; isError: false, value: T } | { isOk: false; isError: true, error: E };
export function ok<T, E>(value: T): Either<T, E> {
return { isOk: true, isError: false, value };
}
export function error<T, E>(error: E): Either<T, E> {
return { isOk: false, isError: true, error};
}
+1 -1
View File
@@ -64,7 +64,7 @@ export class AuthService {
const data = SessionStore.getDataFromLocalStorage();
if (!data?.user?.Authorization && SessionStore?.user?.Authorization) {
window.location.reload();
// window.location.reload();
}
if (window['all-process-gabinete']) {
@@ -21,22 +21,22 @@ export class AttendeesPageModal implements OnInit {
eventPersons: EventPerson[];
adding: "intervenient" | "CC";
currentPath = window.location.pathname;
taskParticipants:EventPerson[] = [];
taskParticipantsCc:EventPerson[] = [];
loggeduser: LoginUserRespose;
@Input() loggedAttendSon: boolean;
taskType: any;
constructor(
private modalCtrl: ModalController,
private modalCtrl: ModalController,
private contactsService: ContactsService,
private navParams: NavParams,
private modalController: ModalController,
public ThemeService: ThemeService,
private router: Router,) {
this.adding = this.navParams.get('adding');
this.taskParticipants = this.navParams.get('taskParticipants');
this.taskParticipantsCc = this.navParams.get('taskParticipantsCc');
@@ -48,7 +48,7 @@ export class AttendeesPageModal implements OnInit {
ngOnInit() {
console.log('Pesquisa de contactos current path2',this.router.url)
this.fetchContacts("");
if(this.taskParticipants == null || this.taskParticipants == undefined){
this.taskParticipants = [];
}
@@ -56,9 +56,9 @@ export class AttendeesPageModal implements OnInit {
if(this.taskParticipantsCc == null || this.taskParticipantsCc == undefined){
this.taskParticipantsCc = [];
}
}
ngOnChanges(event) {}
save(){
@@ -80,16 +80,16 @@ export class AttendeesPageModal implements OnInit {
filterSearchList(itm: EventPerson): boolean {
const result = this.taskParticipants.concat( this.taskParticipantsCc).find((contact, index)=>{
if(contact.Name.toLocaleLowerCase() == itm.Name.toLocaleLowerCase() && contact.EmailAddress.toLocaleLowerCase() == itm.EmailAddress.toLocaleLowerCase()){
index = index;
return contact;
}
})
return undefined == result;
}
remove(itm: EventPerson){
@@ -102,7 +102,7 @@ export class AttendeesPageModal implements OnInit {
return contact;
}
return false;
});
} else if (this.adding == "CC") {
@@ -113,7 +113,7 @@ export class AttendeesPageModal implements OnInit {
return contact;
}
return false;
});
}
@@ -123,7 +123,7 @@ export class AttendeesPageModal implements OnInit {
if(this.adding == "intervenient"){
itm.IsRequired = true;
this.taskParticipants.push(itm);
} else if (this.adding == "CC") {
itm.IsRequired = false;
this.taskParticipantsCc.push(itm);
@@ -141,7 +141,7 @@ export class AttendeesPageModal implements OnInit {
const index: number = result.findIndex((cont) => {
return cont.EmailAddress.toLocaleLowerCase() == attendee.EmailAddress.toLocaleLowerCase()
});
result.splice(index, 1);
});
}
@@ -151,12 +151,12 @@ export class AttendeesPageModal implements OnInit {
this.showLoader = false;
} else {
this.contacts = result;
console.log('Attendes Email',this.loggeduser.Email)
// console.log('Attendes Email',this.loggeduser.Email)
let filterLoggedUserEmail = this.contacts.filter(item => item.EmailAddress.toLocaleLowerCase() != this.loggeduser.Email.toLocaleLowerCase())
console.log('Attendes Email', filterLoggedUserEmail)
// console.log('Attendes Email', filterLoggedUserEmail)
let filterEmptyEmail = filterLoggedUserEmail.filter(item => item.EmailAddress.toLocaleLowerCase() != "")
this.contacts = filterEmptyEmail;
console.log('Attendes Email', this.contacts)
// console.log('Attendes Email', this.contacts)
this.showLoader = false;
}
@@ -164,4 +164,4 @@ export class AttendeesPageModal implements OnInit {
);
}
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ export class FileLoaderService {
// input.onchange = () => {
// // you can use this method to get file and perform respective operations
// let files = Array.from(input.files);
//
//
// };
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { FileValidatorService } from './file-validator.service';
describe('FileValidatorService', () => {
let service: FileValidatorService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(FileValidatorService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,173 @@
import { Injectable } from '@angular/core';
import { Either, error, ok} from "src/app/services/Either"
import * as Sentry from '@sentry/capacitor';
@Injectable({
providedIn: 'root'
})
export class FileValidatorService {
forbiddenExtensions = [
".exe",
".bat",
".sh",
".jar",
".js",
".vbs",
".ps1",
".ini",
".config",
".zip",
".rar",
".tar.gz",
".7z",
".cab",
".sql",
".bak",
".htaccess",
".htpasswd",
".scr",
".pif",
".com",
".msi",
".dll",
".sys",
".ini",
".docm",
".xlsm",
".pptm",
".rtf",
".so",
".dylib",
".dat",
".log",
".conf",
".php",
".py",
".rb",
];
constructor() { }
private base64ToBlob(base64) {
const binaryData = atob(base64);
const arrayBuffer = new ArrayBuffer(binaryData.length);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < binaryData.length; i++) {
view[i] = binaryData.charCodeAt(i);
}
return new Blob([arrayBuffer], { type: 'video/mp4' }); // Adjust the type as per your video format
}
ValidateImage(base64Image: string): Promise<Either<true, Event>> {
const imageDiv: HTMLImageElement = document.createElement("img")
return new Promise((resolve, reject) => {
imageDiv.onload = () => {
resolve(ok(true))
}
imageDiv.onerror = (e: Event) => {
resolve(error(e))
Sentry.captureMessage('FileValidatorService invalid image content');
}
imageDiv.src = base64Image
})
}
ValidateVideoFromBase64Data(base64Data: string) : Promise<Either<true, false>> {
const blob = this.base64ToBlob(base64Data);
return new Promise((resolve, reject) => {
const videoElement = document.createElement('video');
videoElement.src = URL.createObjectURL(blob);
// Add event listeners to handle video load and error
videoElement.addEventListener('loadeddata', () => {
resolve(ok(true))
// You can also perform additional validation checks here if needed.
});
videoElement.addEventListener('error', () => {
resolve(ok(true))
Sentry.captureMessage('FileValidatorService invalid video content');
});
})
}
ValidateVideoFromBlob(blob: Blob) : Promise<Either<true, false>> {
return new Promise((resolve, reject) => {
const videoElement = document.createElement('video');
videoElement.src = URL.createObjectURL(blob);
// Add event listeners to handle video load and error
videoElement.addEventListener('loadeddata', () => {
resolve(ok(true))
// You can also perform additional validation checks here if needed.
});
videoElement.addEventListener('error', () => {
resolve(ok(true))
Sentry.captureMessage('FileValidatorService invalid video content');
});
})
}
validateAudioFromBlob(blob: Blob): Promise<Either<true, false>> {
return new Promise((resolve, reject) => {
const audioElement = new Audio();
// Add event listeners to handle audio load and error
audioElement.addEventListener('canplaythrough', () => {
console.log('Valid audio');
resolve(ok(true))
// You can also perform additional validation checks here if needed.
});
audioElement.addEventListener('error', () => {
console.log('Invalid audio');
resolve(error(false))
});
audioElement.src = URL.createObjectURL(blob);
})
}
hasDuplicateExtension(filename): Either<true, false> {
// Use a regular expression to match multiple consecutive dots
const duplicateExtensionRegex = /\.\.+/;
const a = duplicateExtensionRegex.test(filename);
if(a) {
return ok(true)
} else {
Sentry.captureMessage('FileValidatorService invalid filename '+ filename);
return error(false)
}
}
fileNameValidation(fileName): Either<true, false> {
const fileExtension = fileName.slice(((fileName.lastIndexOf(".") - 1) >>> 0) + 2);
const found = this.forbiddenExtensions.includes(`.${fileExtension.toLowerCase()}`);
if(found) {
return error(false)
} else {
return ok(true)
}
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { HttpServiceService } from './http-service.service';
describe('HttpServiceService', () => {
let service: HttpServiceService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(HttpServiceService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,43 @@
import { HttpClient, HttpContext, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
// import { Result, err, ok } from 'neverthrow'
import { tap, shareReplay, catchError } from "rxjs/operators";
import { Observable, of } from "rxjs";
@Injectable({
providedIn: 'root'
})
export class HttpServiceService {
constructor(private http: HttpClient) {}
put(url: string, body: any | null, options: Options): Observable<any> {
return this.http.put(url, body, options as any).pipe(
tap((response) => {
// Handle success response if needed
}),
catchError((error) => {
// Handle error response if needed
return of(error);
})
);
}
}
interface Options {
headers?: HttpHeaders | {
[header: string]: string | string[];
};
context?: HttpContext;
observe?: 'body';
params?: HttpParams | {
[param: string]: string | number | boolean | ReadonlyArray<string | number | boolean>;
};
reportProgress?: boolean;
responseType?: 'arraybuffer';
withCredentials?: boolean;
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { TokenInterceptorService } from './token-interceptor.service';
describe('TokenInterceptorService', () => {
let service: TokenInterceptorService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(TokenInterceptorService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,81 @@
import { Injectable } from "@angular/core";
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpErrorResponse,
HTTP_INTERCEPTORS,
} from "@angular/common/http";
import { Observable, throwError, BehaviorSubject } from "rxjs";
import { catchError, filter, take, switchMap } from "rxjs/operators";
import { SessionStore } from "src/app/store/session.service";
import { MiddlewareRepositoryService } from "src/app/repository/middleWare/middleware-repository.service"
@Injectable({
providedIn: 'root'
})
export class TokenInterceptorService {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(
null
);
constructor(private middlewareRepositoryService: MiddlewareRepositoryService) {
this.middlewareRepositoryService = middlewareRepositoryService
}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (SessionStore.user.Authorization) {
request = this.addToken(request, SessionStore.user.Authorization);
}
return next.handle(request).pipe(
catchError((error) => {
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(request, next);
} else {
return throwError(error);
}
})
) as any
}
private addToken(request: HttpRequest<any>, token: string) {
return request.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
return this.middlewareRepositoryService.refreshToken().pipe(
switchMap((token: any) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(token['result'].accessToken);
return next.handle(this.addToken(request, token['result'].accessToken));
})
);
} else {
return this.refreshTokenSubject.pipe(
filter((token) => token != null),
take(1),
switchMap((jwt) => {
return next.handle(this.addToken(request, jwt));
})
);
}
}
}
+15 -10
View File
@@ -239,17 +239,22 @@ export class NotificationsService {
}
);
} else {
navigator.serviceWorker.onmessage = (event) => {
console.log('Mensagem recebida do Service Worker:', event.data.data);
let object = {
notification: event.data
}
try {
if (event.data.notificationClicked) {
console.log('Notificação push do Firebase clicada em segundo plano!');
this.notificatinsRoutes(object)
}
};
navigator.serviceWorker.onmessage = (event) => {
console.log('Mensagem recebida do Service Worker:', event.data.data);
let object = {
notification: event.data
}
if (event.data.notificationClicked) {
console.log('Notificação push do Firebase clicada em segundo plano!');
this.notificatinsRoutes(object)
}
};
} catch(e) {
console.log(e)
}
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ChunkService } from './chunk.service';
describe('ChunkService', () => {
let service: ChunkService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ChunkService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+140
View File
@@ -0,0 +1,140 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class ChunkService {
constructor() {
window["ChunkService"] = this
// const a = this.generateChunkFile({} as any, {} as any)
}
generateChunkFile(base64: string, chunkSizeInBytes: number): File[] {
const byteCharacters = atob(base64); // Decode base64 data to binary
const chunks: File[] = [];
let start = 0;
function createChunk(): File {
const chunkSize = Math.min(chunkSizeInBytes, byteCharacters.length - start);
const chunkData = byteCharacters.slice(start, start + chunkSize);
start += chunkSize;
// console.log({chunkData})
const chunkArray = new Uint8Array(chunkData.length);
for (let i = 0; i < chunkData.length; i++) {
chunkArray[i] = chunkData.charCodeAt(i);
// console.log('chunkData.charCodeAt', chunkData.charCodeAt(i))
}
// console.log({Uint8Array:"Uint8Array", chunkArray})
const blob = new Blob([chunkArray]);
// console.log('blob')
// console.log('File')
return new File([blob], `chunk_${chunks.length + 1}`);
}
while (start < byteCharacters.length) {
chunks.push(createChunk());
}
return chunks;
}
generateChunkOfUint8Array(base64: string, chunkSizeInBytes: number): Uint8Array[] {
const byteCharacters = atob(base64); // Decode base64 data to binary
const chunks: Uint8Array[] = [];
let start = 0;
function createChunk(): Uint8Array {
const chunkSize = Math.min(chunkSizeInBytes, byteCharacters.length - start);
const chunkData = byteCharacters.slice(start, start + chunkSize);
start += chunkSize;
const chunkArray = new Uint8Array(chunkData.length);
for (let i = 0; i < chunkData.length; i++) {
chunkArray[i] = chunkData.charCodeAt(i);
}
return chunkArray;
}
while (start < byteCharacters.length) {
chunks.push(createChunk());
}
return chunks;
}
async uploadChunk(file: File): Promise<any> {
// Read and upload the file in chunks (as you've previously implemented)
const chunkSize = 1024 * 500; // Adjust the chunk size as needed
const chunks = [];
let offset = 0;
let i = 0
while (offset < file.size) {
console.log(offset)
const chunk = file.slice(offset, offset + chunkSize);
const reader = new FileReader();
reader.onload = () => {
chunks[i] = new Uint8Array(reader.result as ArrayBuffer)
};
reader.readAsArrayBuffer(chunk);
offset += chunkSize;
}
return chunks
}
onFileSelect(event: any, chunkSizeInBytes):Promise<Blob[]> {
const file:File = event.target.files[0];
const filename = file.name;
//const chunkSize = 1024 * 1024; // 1 MB chunks (adjust as needed)
const chunkSize = chunkSizeInBytes
return new Promise((resolve, reject) => {
// Read and upload chunks
const fileReader = new FileReader();
fileReader.onload = (e) => {
const arrayBuffer = e.target.result as ArrayBuffer;
const blob = new Blob([new Uint8Array(arrayBuffer)]);
const totalChunks = Math.ceil(file.size / chunkSize);
const chunks: Blob[] = []
for (let i = 1; i <= totalChunks; i++) {
const start = (i - 1) * chunkSize;
const end = i * chunkSize;
const chunk = blob.slice(start, end);
chunks.push(chunk)
}
resolve(chunks)
};
fileReader.readAsArrayBuffer(file);
})
}
generateChunkFromBase64(base64: string, chunkSizeInBytes: number) {
const byteCharacters = atob(base64); // Decode base64 data to binary
const chunkArray = [];
for (let offset = 0; offset < byteCharacters.length; offset += chunkSizeInBytes) {
const chunkData = byteCharacters.slice(offset, offset + chunkSizeInBytes);
chunkArray.push(chunkData);
}
return chunkArray;
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { StreamService } from './stream.service';
describe('StreamService', () => {
let service: StreamService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(StreamService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
+112
View File
@@ -0,0 +1,112 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpEventType } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class StreamService {
constructor(
private http: HttpClient,
) {
window["StreamService"] = this
}
async uploadFile() {
const API_URL = 'http://localhost:3000/upload'; // Replace with your server URL
const filePath = 'path/to/large-file.zip'; // Replace with the path to your file
const fileName = 'my-file'; // Specify your desired filename
const fileExtension = 'zip'; // Specify the file extension
const headers = new HttpHeaders()
.append('X-File-Name', fileName)
.append('X-File-Extension', fileExtension);
const file = await this.readFileInChunks(filePath);
const chunkSize = 1024 * 1024; // 1 MB chunk size (adjust as needed)
for (let offset = 0; offset < file.length; offset += chunkSize) {
const chunk = file.slice(offset, offset + chunkSize);
// await this.uploadChunk(API_URL, chunk, headers);
}
console.log('Upload completed.');
}
async readFileInChunks(filePath: string): Promise<Uint8Array> {
const response = await fetch(filePath);
const reader = response.body.getReader();
const chunks: Uint8Array[] = [];
let done = false;
while (!done) {
const { value, done: isDone } = await reader.read();
if (!isDone) {
chunks.push(value);
}
done = isDone;
}
return new Uint8Array([].concat(...chunks.map((chunk) => Array.from(chunk))));
}
async uploadChunk(url: string, chunks: Uint8Array[], fileName, fileExtension): Promise<void> {
let i = 1
console.log('123', chunks.length)
for(const chunk of chunks) {
try {
console.log("iterate")
const headers = new HttpHeaders()
.append('X-File-Name', fileName)
.append('X-File-Extension', fileExtension)
.append('X-File-Content-Length', chunks.length.toString())
.append('X-File-Index', i.toString())
await this.http.post('http://localhost:3001/upload', chunk.buffer, { headers, responseType: 'blob' }).toPromise();
i++
} catch (error) {
console.error('Upload error:', error);
}
}
}
async uploadChunkNoLoop(url: string, chunk: Uint8Array, fileName, fileExtension, i, length): Promise<void> {
console.log("iterate")
const headers = new HttpHeaders()
.append('X-File-Name', fileName)
.append('X-File-Extension', fileExtension)
.append('X-File-Content-Length', length)
.append('X-File-Index', i.toString())
await this.http.post('http://localhost:3001/upload', chunk.buffer, { headers, responseType: 'blob' }).toPromise();
}
uploadChunk1(chunk: Blob, chunkNumber: number, totalChunks: number, filename: string) {
console.log(chunk)
const headers = new HttpHeaders()
.append('X-File-Name', filename)
.append('X-File-Content-Length', totalChunks.toString())
.append('X-File-Index', chunkNumber.toString())
return this.http.post('http://localhost:3001/upload-chunk', Blob, { headers, responseType: 'blob' });
}
}
// const text = 'Hello, World00120301010asdf1002sdf 0fsdfasf0001230 12300!\n';
// const base64 = btoa(text);
// let chunks = window["ChunkService"].generateChunkOfUint8Array(base64, 8)
// window.StreamService.uploadChunk("", chunks, "peter12", "txt")