diff --git a/.htaccess b/.htaccess index fa24a060ae7e5730d1ebd8aaccad939411216459..f782cf9c008a84c36305a8757fde5bc5c007a4ad 100755 --- a/.htaccess +++ b/.htaccess @@ -8,5 +8,4 @@ RewriteEngine On RewriteRule "\.(css|js|eot|ttf|woff|woff2|svg|ico|png)$" - [L] -RewriteRule "^simplesaml/" - [L] -RewriteRule ^ dist/index.html +RewriteRule "^simplesaml/" - [L] \ No newline at end of file diff --git a/bin/scheduler.php b/bin/scheduler.php index eff7efb5a31446c303b5fd427b58eb259244599f..42f060839872ff6932a615ae55dd7fc02df6cbcf 100644 --- a/bin/scheduler.php +++ b/bin/scheduler.php @@ -177,7 +177,7 @@ function sendNotifications(array $args) $rows = ''; $recipientNotificationIds = []; foreach ($recipientNotifications as $recipientNotification) { - $url = $args['applicationUrl'] . 'dist/documents/' . $recipientNotification['main_document_id']; + $url = $args['applicationUrl'] . 'dist/#/documents/' . $recipientNotification['main_document_id']; $document = DocumentModel::getById(['id' => $recipientNotification['main_document_id'], 'select' => ['title']]); $row = str_replace('{{object}}', $document['title'], $lang['notificationTemplateRow']); diff --git a/index.php b/index.php index 0267c86efab0fb6fba259180788d0e502bec99cc..e7e1bf90926217aeb923c6cc0b8a33db62202367 100644 --- a/index.php +++ b/index.php @@ -1,3 +1,3 @@ <script> - window.location.href = 'dist/index.html'; + window.location.href = 'dist/'; </script> diff --git a/lang/en.json b/lang/en.json index 9524834565f9e35e98bbcef1c36d1c6cb056c9bb..af1fd867d6baa16390a84520a9337f48e0915319 100755 --- a/lang/en.json +++ b/lang/en.json @@ -668,6 +668,8 @@ "cannotAcces": "Unable to access document", "metaSignSignatureDisabled": "The metaSignSignature parameter is not activated", "eidas_metasign": "Metasign Stamp", - "eidas_metasignUser": "Metasign Stamp" + "eidas_metasignUser": "Metasign Stamp", + "inputCardReaderUpdateCerts": "Card entry detected, updating certificates...", + "outputCardReaderUpdateCerts": "Card output detected, updating certificates..." } } \ No newline at end of file diff --git a/lang/fr.json b/lang/fr.json index 9f0d16cf13a6378046abd0bdf06d724ba5a905b6..29b28724f647df17acdca9f487c0bb8a01c978d0 100755 --- a/lang/fr.json +++ b/lang/fr.json @@ -667,6 +667,8 @@ "hardDeleted":"Fichiers supprimés", "metaSignSignatureDisabled": "Le paramètre metaSignSignature n'est pas activé", "eidas_metasign": "Cachet Metasign", - "eidas_metasignUser": "Cachet Metasign" + "eidas_metasignUser": "Cachet Metasign", + "inputCardReaderUpdateCerts": "Une entrée de carte détectée, actualisation des certificats...", + "outputCardReaderUpdateCerts": "Une sortie de carte détectée, actualisation des certificats..." } } diff --git a/package.json b/package.json index b147c9d5b3dcbf48404b5ad1ebc2eb82bbf34a9c..506ff7112b6374f71309ff348e1568bfb186cdd5 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,9 @@ "version": "21.03.1", "description": "Annoter ou Signer vos documents sur votre tablette", "homepage": "https://labs.maarch.org/maarch/MaarchParapheur", - "engines" : { - "npm" : ">=6.14.5", - "node" : ">=14.4.0" + "engines": { + "npm": ">=6.14.5", + "node": ">=14.4.0" }, "scripts": { "dep-list": "npm list --depth=0", @@ -54,7 +54,7 @@ "@ngrx/store-devtools": "^9.2.1", "@ngx-translate/core": "^12.1.2", "@ngx-translate/http-loader": "^4.0.0", - "@peculiar/fortify-webcomponents": "^0.17.3", + "@peculiar/fortify-webcomponents": "^3.1.0", "@types/hammerjs": "^2.0.39", "@types/jasmine": "^3.7.7", "@types/jasminewd2": "^2.0.9", diff --git a/src/app/document/controllers/DocumentController.php b/src/app/document/controllers/DocumentController.php index 86a507f557e6b6b0ce19cc92fee6e965a00c1fef..ab29ce646ab3cd6e386d68e79f21722cb10ceb16 100755 --- a/src/app/document/controllers/DocumentController.php +++ b/src/app/document/controllers/DocumentController.php @@ -500,7 +500,8 @@ class DocumentController return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] signaturePositions is not an array"]); } foreach ($workflow['signaturePositions'] as $keySP => $signaturePosition) { - if (empty($signaturePosition['positionX']) || empty($signaturePosition['positionY']) || empty($signaturePosition['page'])) { + if (!Validator::each(Validator::between(0, 100, true))->validate([$signaturePosition['positionX'], $signaturePosition['positionY']]) + || !Validator::intVal()->positive()->validate($signaturePosition['page'])) { return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}][signaturePositions][{$keySP}] is wrong formatted"]); } } @@ -510,7 +511,8 @@ class DocumentController return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] datePositions is not an array"]); } foreach ($workflow['datePositions'] as $keyDP => $datePosition) { - if (empty($datePosition['positionX']) || empty($datePosition['positionY']) || empty($datePosition['page'])) { + if (!Validator::each(Validator::between(0, 100, true))->validate([$datePosition['positionX'], $datePosition['positionY']]) + || !Validator::intVal()->positive()->validate($datePosition['page'])) { return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}][datePositions][{$keyDP}] is wrong formatted"]); } elseif (empty($datePosition['color']) || empty($datePosition['font']) || empty($datePosition['format']) || empty($datePosition['width'])) { return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}][datePositions][{$keyDP}] is wrong formatted"]); diff --git a/src/app/email/controllers/EmailController.php b/src/app/email/controllers/EmailController.php index b4cc19f7e9b6cac8f25bc82c286efd26fa290a7d..ad73e50dde847563278aa23c51e261e508a4dc21 100644 --- a/src/app/email/controllers/EmailController.php +++ b/src/app/email/controllers/EmailController.php @@ -263,7 +263,7 @@ class EmailController public static function sendNotificationToTypist(array $args) { $lang = LanguageController::get(['lang' => $args['recipient']['lang']]); - $url = ConfigurationModel::getApplicationUrl() . 'dist/search?documentId=' . $args['documentId']; + $url = ConfigurationModel::getApplicationUrl() . 'dist/#/search?documentId=' . $args['documentId']; if ($args['mode'] == 'END') { $subject = $lang['notificationEndOfWorkflowSubject']; @@ -347,7 +347,7 @@ class EmailController public static function sendNotificationToUser(array $args) { $lang = LanguageController::get(['lang' => $args['preferences']['lang']]); - $url = ConfigurationModel::getApplicationUrl() . 'dist/documents/' . $args['documentId']; + $url = ConfigurationModel::getApplicationUrl() . 'dist/#/documents/' . $args['documentId']; EmailController::createEmail([ 'userId' => $args['senderId'], 'data' => [ diff --git a/src/app/user/controllers/UserController.php b/src/app/user/controllers/UserController.php index 468e4753442a5eab6f20086ce3e1c21fa2807d1e..368b5419e1d95a148c87ce99c076e8a1427061be 100755 --- a/src/app/user/controllers/UserController.php +++ b/src/app/user/controllers/UserController.php @@ -732,7 +732,7 @@ class UserController $user['preferences'] = json_decode($user['preferences'], true); $lang = LanguageController::get(['lang' => $user['preferences']['lang']]); - $url = ConfigurationModel::getApplicationUrl() . 'dist/update-password?token=' . $resetToken; + $url = ConfigurationModel::getApplicationUrl() . 'dist/#/update-password?token=' . $resetToken; EmailController::createEmail([ 'userId' => $user['id'], 'data' => [ diff --git a/src/core/controllers/AuthenticationController.php b/src/core/controllers/AuthenticationController.php index aa044897be97824643448c3a0d4f0e093f42a383..23e725183f9da7e698053cf65617b322e92d1852 100755 --- a/src/core/controllers/AuthenticationController.php +++ b/src/core/controllers/AuthenticationController.php @@ -377,7 +377,7 @@ class AuthenticationController $user = UserModel::getById(['select' => ['login'], 'id' => $args['userId']]); $lang = LanguageController::get(['lang' => 'fr']); - $url = ConfigurationModel::getApplicationUrl() . 'dist/update-password?token=' . $resetToken; + $url = ConfigurationModel::getApplicationUrl() . 'dist/#/update-password?token=' . $resetToken; EmailController::createEmail([ 'userId' => $args['userId'], 'data' => [ diff --git a/src/frontend/app/administration/customization/customization.component.ts b/src/frontend/app/administration/customization/customization.component.ts index defeeb982c7447832d26deb75e1f20420f111baa..84b8d8cf50054a89193a408f49271748ab15d2d3 100644 --- a/src/frontend/app/administration/customization/customization.component.ts +++ b/src/frontend/app/administration/customization/customization.component.ts @@ -109,11 +109,11 @@ export class CustomizationComponent implements OnInit, OnDestroy { initMce() { const param = { selector: '#login_message', - base_url: this.functions.getBaseUrl() + '/tinymce/', + base_url: `${this.functions.getBaseUrl().split('#')[0]}/tinymce/`, height: '200', suffix: '.min', extended_valid_elements : 'tag,class', - content_css: this.functions.getBaseUrl() + '/assets/custom_tinymce.css', + content_css: `${this.functions.getBaseUrl().split('#')[0]}/assets/custom_tinymce.css`, language: this.translate.instant('lang.langISO').replace('-', '_'), language_url: `../node_modules/tinymce-i18n/langs/${this.translate.instant('lang.langISO').replace('-', '_')}.js`, menubar: false, diff --git a/src/frontend/app/administration/otp/otp.component.ts b/src/frontend/app/administration/otp/otp.component.ts index ddb988e7557b174b93b48d30cd99f267c47cb1f8..7d85a9b32e04081383f5c398a5d9f2385498d217 100644 --- a/src/frontend/app/administration/otp/otp.component.ts +++ b/src/frontend/app/administration/otp/otp.component.ts @@ -110,11 +110,11 @@ export class OtpComponent implements OnInit, OnDestroy { initMce() { const param = { selector: '#email_message', - base_url: this.functions.getBaseUrl() + '/tinymce/', + base_url: `${this.functions.getBaseUrl().split('#')[0]}/tinymce/`, height: '200', suffix: '.min', extended_valid_elements : 'tag,class', - content_css: this.functions.getBaseUrl() + '/assets/custom_tinymce.css', + content_css: `${this.functions.getBaseUrl().split('#')[0]}/assets/custom_tinymce.css`, language: this.translate.instant('lang.langISO').replace('-', '_'), language_url: `../node_modules/tinymce-i18n/langs/${this.translate.instant('lang.langISO').replace('-', '_')}.js`, menubar: false, diff --git a/src/frontend/app/app-routing.module.ts b/src/frontend/app/app-routing.module.ts index b6f8bbc5cf0bfbf21e2a2fdacf6e499b0be6a232..57b02f95ea80012fd80acac5fbe7c80c97e5dfee 100755 --- a/src/frontend/app/app-routing.module.ts +++ b/src/frontend/app/app-routing.module.ts @@ -62,7 +62,7 @@ import { NotificationsListComponent } from './administration/notification/notifi { path: 'update-password', component: UpdatePasswordComponent }, { path: 'password-modification', component: PasswordModificationComponent }, { path: '**', redirectTo: 'login', pathMatch: 'full' }, - ], { relativeLinkResolution: 'legacy' }), + ], { relativeLinkResolution: 'legacy', useHash: true }), ], exports: [ RouterModule diff --git a/src/frontend/app/document/document.component.ts b/src/frontend/app/document/document.component.ts index 99d0490b9dc56d5ddebadf88ee7f3833a3cea0de..823c33e785a6e27f7bd7c9f839929925a94ef470 100755 --- a/src/frontend/app/document/document.component.ts +++ b/src/frontend/app/document/document.component.ts @@ -635,18 +635,7 @@ export class DocumentComponent implements OnInit, OnDestroy { { text: this.getLabel(event), handler: async (data: any) => { - const currentUserWorkflow = this.mainDocument.workflow.filter((line: { current: boolean }) => line.current === true)[0]; - const idsToProcess = await this.actionsService.checkGroupMail(this.mainDocument, 'validate'); - const res = await this.signatureMethodService.checkAuthenticationAndLaunchAction(currentUserWorkflow, data.paragraph, idsToProcess); - - if (!this.functionsService.empty(res)) { - const config: MatBottomSheetConfig = { - disableClose: true, - direction: 'ltr' - }; - this.bottomSheet.open(SuccessInfoValidBottomSheetComponent, config); - this.localStorage.remove(this.mainDocument.id.toString()); - } + await this.processToSign(data); } } ] @@ -655,6 +644,24 @@ export class DocumentComponent implements OnInit, OnDestroy { await alert.present(); } + async processToSign(data: any) { + const currentUserWorkflow = this.mainDocument.workflow.filter((line: { current: boolean }) => line.current === true)[0]; + const idsToProcess = await this.actionsService.checkGroupMail(this.mainDocument, 'validate'); + const res = await this.signatureMethodService.checkAuthenticationAndLaunchAction(currentUserWorkflow, data.paragraph, idsToProcess); + if (!this.functionsService.empty(res)) { + if (res === 'refresh_certs') { + this.processToSign(data); + } else { + const config: MatBottomSheetConfig = { + disableClose: true, + direction: 'ltr' + }; + this.bottomSheet.open(SuccessInfoValidBottomSheetComponent, config); + this.localStorage.remove(this.mainDocument.id.toString()); + } + } + } + async removeTags() { this.signaturesService.currentAction = 0; diff --git a/src/frontend/app/document/visa-workflow/visa-workflow.component.ts b/src/frontend/app/document/visa-workflow/visa-workflow.component.ts index 21ee72d5939cbed3a12394d0af743914766b4108..db4cd09b78b9a64c00c95575ef4e54f932d91c63 100644 --- a/src/frontend/app/document/visa-workflow/visa-workflow.component.ts +++ b/src/frontend/app/document/visa-workflow/visa-workflow.component.ts @@ -22,7 +22,7 @@ import { FunctionsService } from '../../service/functions.service'; export class VisaWorkflowComponent implements OnInit { @Input() editMode: boolean = false; - @Input() visaWorkflow: any = []; + @Input() visaWorkflow: any[] = []; @ViewChild(IonReorderGroup) reorderGroup: IonReorderGroup; loading: boolean = false; @@ -37,6 +37,8 @@ export class VisaWorkflowComponent implements OnInit { hasConnector: boolean = false; userDelegated: boolean = false; + tmpArray: any[] = []; + constructor( public http: HttpClient, private translate: TranslateService, @@ -406,20 +408,55 @@ export class VisaWorkflowComponent implements OnInit { }); } - setPositionsWorkfow(resource: any, positions: any[]) { - this.clearPositionsFromResource(resource); + setPositionsWorkfow(resources: any[], index: number, positions: any[]) { + this.clearPositionsFromResource(resources[index]); if (positions.length > 0) { Object.keys(positions).forEach(key => { const objPos = { ...positions[key], - mainDocument : resource.mainDocument, + mainDocument : resources[index].mainDocument, }; - this.visaWorkflow[positions[key].sequence].signaturePositions = this.visaWorkflow[positions[key].sequence].signaturePositions.filter((pos: any) => pos.mainDocument !== resource.mainDocument); this.visaWorkflow[positions[key].sequence].signaturePositions.push(objPos); + + const existingObject: any = this.tmpArray.find((item: any) => item.key === positions[key].sequence && item.page === positions[key].page && item.docIndex === resources.indexOf(resources[index])); + if (existingObject !== undefined) { + this.tmpArray[this.tmpArray.indexOf(existingObject)].signaturePositions = positions[key].position; + } else { + this.tmpArray.push({ + key: positions[key].sequence, + page: positions[key].page, + docIndex: resources.indexOf(resources[index]), + signaturePositions: positions[key].position + }); + } + }); + + this.tmpArray.forEach((element: any) => { + const existingObject: any = (this.visaWorkflow[element.key].signaturePositions as any[]).find((item: any) => item.page === element.page && item.sequence === element.key && item.docIndex === element.docIndex); + if (existingObject !== undefined) { + this.updatePositionsWorkfow(existingObject, element); + } else { + (this.visaWorkflow[element.key].signaturePositions as any[]).push( + { + docIndex: element.docIndex, + page: element.page, + mainDocument: true, + sequence: element.key, + position: element.signaturePositions + } + ); + } }); } } + updatePositionsWorkfow(object: any, data: any) { + this.visaWorkflow.forEach((element: any) => { + element.signaturePositions[(element.signaturePositions as any[]).indexOf(object)].position = data.signaturePositions; + }); + } + + clearPositionsFromResource(resource: any) { this.visaWorkflow.forEach((user: any) => { user.signaturePositions = user.signaturePositions.filter((pos: any) => pos.mainDocument !== resource.mainDocument); diff --git a/src/frontend/app/indexation/indexation.component.ts b/src/frontend/app/indexation/indexation.component.ts index 3273ee61fd0d942b82f4a7d2aa6d732b159e9001..83fb369cdbdb3b5102a2e5218c945a00f5902c52 100644 --- a/src/frontend/app/indexation/indexation.component.ts +++ b/src/frontend/app/indexation/indexation.component.ts @@ -188,7 +188,7 @@ export class IndexationComponent implements OnInit { load.present(); const objTosend = this.formatData(data.note); for (let index = 0; index < objTosend.length; index++) { - await this.saveDocument(objTosend[index], index); + await this.saveDocument(objTosend, index); if (objTosend[0].workflow[0].userId === this.authService.user.id) { this.actionsService.setEvent('refreshList'); } @@ -207,14 +207,19 @@ export class IndexationComponent implements OnInit { } saveDocument(data: any, index: number) { + data.forEach((element: any) => { + (element.workflow as any[]).forEach((item: any) => { + item.signaturePositions = (item.signaturePositions as any[]).filter((el: any) => el.docIndex === data.indexOf(element)); + }); + }); return new Promise((resolve) => { - this.http.post('../rest/documents', data).pipe( + this.http.post('../rest/documents', data[index]).pipe( tap(() => { this.errors = []; }), finalize(() => resolve(true)), catchError((err: any) => { - this.errors.push(data.title); + this.errors.push(data[index].title); this.notificationService.handleErrors(err); return of(false); }) @@ -273,6 +278,7 @@ export class IndexationComponent implements OnInit { signatureMode: this.authService.getSignatureMode(item.role), signaturePositions: item.signaturePositions !== undefined ? this.formatPositions(item.signaturePositions.filter((pos: any) => pos.mainDocument === file.mainDocument && file.signPos !== undefined)).map((itemFile: any) => ({ page: itemFile.page, + docIndex: itemFile.docIndex, positionX: itemFile.position.positionX, positionY: itemFile.position.positionY, })) : [] @@ -360,6 +366,11 @@ export class IndexationComponent implements OnInit { deleteFile(index: number) { this.filesToUpload.splice(index, 1); + if (this.filesToUpload.length === 0) { + this.appVisaWorkflow.getCurrentWorkflow().forEach((element: any) => { + element.signaturePositions = []; + }); + } } async signPos(index: number) { @@ -378,16 +389,17 @@ export class IndexationComponent implements OnInit { component: SignaturePositionComponent, cssClass: 'custom-alert-fullscreen', componentProps: { - 'workflow': this.appVisaWorkflow.getCurrentWorkflow(), - 'resource': this.filesToUpload[index], - 'pdfContent': 'data:application/pdf;base64,' + this.filesToUpload[index].content, + workflow: this.appVisaWorkflow.getCurrentWorkflow(), + resource: this.filesToUpload[index], + pdfContent: 'data:application/pdf;base64,' + this.filesToUpload[index].content, + docIndex: this.filesToUpload.indexOf(this.filesToUpload[index]) } }); await modal.present(); const { data } = await modal.onWillDismiss(); if (data !== undefined) { this.filesToUpload[index].signPos = data; - this.appVisaWorkflow.setPositionsWorkfow(this.filesToUpload[index], data); + this.appVisaWorkflow.setPositionsWorkfow(this.filesToUpload, index, data); } } else { this.notificationService.error('lang.mustSetWorkflowBeforeSignPositions'); diff --git a/src/frontend/app/indexation/signature-position/signature-position.component.ts b/src/frontend/app/indexation/signature-position/signature-position.component.ts index 8e3bf242a28cf19c48f8840c24764c0326fefbeb..904e94434351c2dcb9c08421f17d0e9f6fc53b7c 100644 --- a/src/frontend/app/indexation/signature-position/signature-position.component.ts +++ b/src/frontend/app/indexation/signature-position/signature-position.component.ts @@ -14,6 +14,7 @@ export class SignaturePositionComponent implements OnInit { @Input() workflow: any[] = []; @Input() resource: any = []; @Input() pdfContent: any = null; + @Input() docIndex: number = null; loading: boolean = false; dragging: boolean = false; @@ -61,9 +62,10 @@ export class SignaturePositionComponent implements OnInit { initSignPos() { this.workflow.forEach((user: any, index: number) => { if (user.signaturePositions?.length > 0) { - this.signList = this.signList.concat(user.signaturePositions.filter((pos: any) => pos.mainDocument === this.resource.mainDocument).map((pos: any) => ({ + this.signList = this.signList.concat(user.signaturePositions.filter((pos: any) => pos.mainDocument === this.resource.mainDocument && pos.docIndex === this.docIndex).map((pos: any) => ({ ...pos, - sequence : index + sequence : index, + docIndex: this.docIndex, }))); } }); @@ -103,8 +105,14 @@ export class SignaturePositionComponent implements OnInit { } moveSign(event: any, i: number) { - const percentx = (event.x * 100) / this.workingAreaWidth; - const percenty = (event.y * 100) / this.workingAreaHeight; + let percentx = (event.x * 100) / this.workingAreaWidth; + let percenty = (event.y * 100) / this.workingAreaHeight; + + percentx = percentx < 0 ? 0 : percentx; + percentx = percentx > 100 ? 100 : percentx; + percenty = percenty < 0 ? 0 : percenty; + percenty = percenty > 100 ? 100 : percenty; + this.signList.filter((item: any) => item.sequence === this.currentUser && item.page === this.currentPage)[0].position.positionX = percentx; this.signList.filter((item: any) => item.sequence === this.currentUser && item.page === this.currentPage)[0].position.positionY = percenty; this.dragging = false; @@ -119,6 +127,7 @@ export class SignaturePositionComponent implements OnInit { { sequence: this.currentUser, page: this.currentPage, + docIndex: this.docIndex, position: { positionX: 75, positionY: 90 diff --git a/src/frontend/app/service/notification.service.ts b/src/frontend/app/service/notification.service.ts index 54d66710bb057c2f5635204091916504d5ca81c2..a1b0fa175b12839a0d983d9ac111cb0dc5256148 100644 --- a/src/frontend/app/service/notification.service.ts +++ b/src/frontend/app/service/notification.service.ts @@ -23,8 +23,22 @@ export class NotificationService { toast.present(); } - async error(message: string) { + async message(message: string) { const msg = message.includes('lang.') ? this.translate.instant(message) : message; + const toast = await this.toastController.create({ + cssClass: 'notif-primary', + duration: 3000, + message: msg, + position: 'top' + }); + toast.present(); + } + + async error(message: any) { + if (typeof message === 'object') { + message = message.toString(); + } + const msg = message.indexOf('lang.') > -1 ? this.translate.instant(message) : message; const toast = await this.toastController.create({ cssClass: 'notif-error', duration: 3000, diff --git a/src/frontend/app/service/signature-method/signature-method-modal.component.html b/src/frontend/app/service/signature-method/signature-method-modal.component.html index 5aaa59004357c7a9f66020a379cb0e756ad666bf..52c27d6b6f9d8f80a79b164a9d4b3f6e17baefa7 100644 --- a/src/frontend/app/service/signature-method/signature-method-modal.component.html +++ b/src/frontend/app/service/signature-method/signature-method-modal.component.html @@ -1,4 +1,4 @@ <ion-content> - <peculiar-fortify-certificates style="height: 100%;width: 100%;" language="fr" [filters]="filters" - (continue)="certificateChosen($event)" (cancel)="cancelSign()" hide-footer></peculiar-fortify-certificates> + <peculiar-fortify-certificates *ngIf="!signaturesService.mustRefreshCerts" style="height: 100%;width: 100%;" language="fr" [filters]="filters" + (selectionSuccess)="certificateChosen($event)" (selectionCancel)="cancelSign()" hide-footer></peculiar-fortify-certificates> </ion-content> diff --git a/src/frontend/app/service/signature-method/signature-method-modal.component.ts b/src/frontend/app/service/signature-method/signature-method-modal.component.ts index 1f3f2a70679fdaed3e5fd2bc65d59e694c27c643..3b9c9fd57e82ea840c67b499132dbcf8d33c1f5a 100644 --- a/src/frontend/app/service/signature-method/signature-method-modal.component.ts +++ b/src/frontend/app/service/signature-method/signature-method-modal.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; import { LoadingController, ModalController } from '@ionic/angular'; import { catchError, tap } from 'rxjs/operators'; import { of } from 'rxjs'; @@ -9,13 +9,15 @@ import { ActionsService } from '../actions.service'; import { SignaturesContentService } from '../signatures.service'; import { AuthService } from '../auth.service'; import { FunctionsService } from '../functions.service'; +import { FortifyAPI } from '@peculiar/fortify-client-core'; + @Component({ selector: 'signature-method-modal', templateUrl: 'signature-method-modal.component.html', styleUrls: ['./signature-method-modal.component.scss'] }) -export class SignatureMethodModalComponent implements OnInit { +export class SignatureMethodModalComponent implements OnInit, OnDestroy { @Input() note: string; @Input() signatureMode: string; @@ -39,7 +41,7 @@ export class SignatureMethodModalComponent implements OnInit { certificate: any; signatureLength: any = null; - server: any = null; + api: FortifyAPI = null; constructor( public modalController: ModalController, @@ -64,6 +66,68 @@ export class SignatureMethodModalComponent implements OnInit { if (!this.functionsService.empty(signatureModeData.keyUsage)) { this.filters.keyUsage.push(signatureModeData.keyUsage); } + + await this.processInitFortify(); + } + + async ngOnDestroy(): Promise<void> { + if (!this.functionsService.empty(this.api)) { + this.api.finish(); + this.api = null; + } + } + + async initFortify() { + this.api = new FortifyAPI(null); + + await this.waitFortifyStart(); + + const challenge = await this.api.challenge(); + + if (challenge) { + await this.api.login(); + } + + if (this.signaturesService.mustRefreshCerts) { + await this.updateProvider(null); + } + this.api.server.cardReader + .on('insert', (event: any) => this.updateProvider(event)) + .on('remove', (event: any) => this.updateProvider(event)); + } + + async processInitFortify() { + if (this.signaturesService.mustRefreshCerts) { + this.loadingController.create({ + message: this.translate.instant('lang.waiting'), + spinner: 'dots' + }).then(async (load: HTMLIonLoadingElement) => { + load.present(); + await this.initFortify(); + load.dismiss(); + }); + } else { + await this.initFortify(); + } + } + + async waitFortifyStart() { + let fortifyState: boolean = false; + while (!fortifyState) { + fortifyState = await this.fortifyStart(); + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } + + fortifyStart(): Promise<boolean> { + return new Promise(async (resolve) => { + try { + await this.api.start(); + resolve(true); + } catch (error) { + resolve(false); + } + }); } async certificateChosen(certData: any) { @@ -74,9 +138,8 @@ export class SignatureMethodModalComponent implements OnInit { load.present(); try { - this.server = certData.detail.server; this.checkWebsocketSession(); - this.provider = await certData.detail.server.getCrypto(certData.detail.providerId); + this.provider = await this.api.server.getCrypto(certData.detail.providerId); this.checkWebsocketSession(); this.cert = await this.provider.certStorage.getItem(certData.detail.certificateId); this.checkWebsocketSession(); @@ -101,18 +164,40 @@ export class SignatureMethodModalComponent implements OnInit { result = await this.sendAndSign(this.idsToProcess[index]); } load.dismiss(); + setTimeout(() => { + this.signaturesService.mustRefreshCerts = true; + }, 500); this.modalController.dismiss(result); }); } async checkWebsocketSession() { // session closed?! - while (this.server.client.state !== WebSocket.OPEN) { - await this.server.connect(); + while (this.api.server.state !== WebSocket.OPEN) { + await this.api.server.connect(); await new Promise(resolve => setTimeout(resolve, 150)); } } + async updateProvider(event: any) { + const info = await this.api.server.info(); + for (const provider of info.providers) { + const crypto = await this.api.server.getCrypto(provider.id); + await crypto.reset(); + } + + if (!this.functionsService.empty(event?.action) && event?.action.includes('insert')) { + this.notificationService.message('lang.inputCardReaderUpdateCerts'); + this.modalController.dismiss('refresh_certs'); + } else if (!this.functionsService.empty(event?.action) && event?.action.includes('remove')) { + this.notificationService.message('lang.outputCardReaderUpdateCerts'); + this.modalController.dismiss('refresh_certs'); + } else if (this.signaturesService.mustRefreshCerts && event === null) { + this.modalController.dismiss('refresh_certs'); + this.signaturesService.mustRefreshCerts = false; + } + } + async sendAndSign(idToProcess: number) { let allSignaturesComplete: boolean = false; let res: any = {}; @@ -199,6 +284,7 @@ export class SignatureMethodModalComponent implements OnInit { } cancelSign() { + this.signaturesService.mustRefreshCerts = true; this.modalController.dismiss(false); } diff --git a/src/frontend/app/service/signatures.service.ts b/src/frontend/app/service/signatures.service.ts index 15568b71092953dfcbcb731bf92b944e7bc4d4ff..1272d4aac4a4e0e34d7dad3a855c7d933c96cb9b 100755 --- a/src/frontend/app/service/signatures.service.ts +++ b/src/frontend/app/service/signatures.service.ts @@ -36,6 +36,8 @@ export class SignaturesContentService { appSession: any; + mustRefreshCerts: boolean = true; + private portalHost: DomPortalOutlet; constructor( diff --git a/src/frontend/core/global.scss b/src/frontend/core/global.scss index 3ffcb298dc4e756be6f54e34ce70db2dd69ee0b1..cac6dfc4ce9183658407b2caf57772a1ca3886d4 100644 --- a/src/frontend/core/global.scss +++ b/src/frontend/core/global.scss @@ -75,6 +75,17 @@ input:read-only { font-weight: bold; } +.notif-primary { + z-index: 60003; + --background: white; + --color: var(--ion-color-primary); + --border-color: var(--ion-color-primary); + --border-width: 1px; + --border-style: solid; + text-align: center; + font-weight: bold; +} + .fullscreen { --width: 100%; --height: 100%; diff --git a/src/frontend/core/index.html b/src/frontend/core/index.html index d3456fed9f0f30cb429e55fe634d1446459ea5f1..bfbb5d58357c3aaeac8a8135321febe33b673466 100644 --- a/src/frontend/core/index.html +++ b/src/frontend/core/index.html @@ -1,21 +1,7 @@ <!doctype html> <html lang="fr"> <head> - <base id="baseHref"> - <script type="text/javascript"> - let fullPath = window.location.pathname; - let before = fullPath.split('/')[0]; - let splitedUrl = fullPath.split('/')[1]; - let after = fullPath.split('/')[2]; - - if (typeof splitedUrl === 'undefined' || splitedUrl === '' || splitedUrl === 'dist') { - document.getElementById('baseHref').href = '/dist/'; - } else if (before == '' && (after == 'dist' || after == '')) { - document.getElementById('baseHref').href = '/' + splitedUrl + '/dist/'; - } else { - document.getElementById('baseHref').href = '/' + splitedUrl; - } - </script> + <base href="."> <meta charset="utf-8"> <title>Maarch Parapheur</title> <meta name="viewport"