Skip to content

Commit 9f1b2d7

Browse files
authored
added powers (#149)
* added powers * update * logs * map fix * new tests * fixes * updates * new implem * logs * deleted logs * fix tests
1 parent 52e7600 commit 9f1b2d7

File tree

8 files changed

+170
-20
lines changed

8 files changed

+170
-20
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eudistack-wallet-ui",
3-
"version": "2.0.13",
3+
"version": "2.0.14",
44
"author": "Ionic Framework",
55
"homepage": "https://ionicframework.com/",
66
"scripts": {

src/app/interfaces/websocket-data.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@ interface PinRequestData {
33
timeout?: number;
44
}
55

6-
interface NotificationData {
6+
export interface Power {
7+
function: string;
8+
action: string[];
9+
}
10+
11+
export interface CredentialPreview {
12+
power: Power[];
13+
subjectName: string;
14+
organization: string;
15+
expirationDate: string;
16+
}
17+
18+
19+
export interface NotificationData {
720
decision: boolean;
8-
credentialPreview?: {
9-
subjectName?: string;
10-
organization?: string;
11-
issuer?: string;
12-
expirationDate?: string;
13-
};
21+
credentialPreview?: CredentialPreview;
1422
timeout?: number;
1523
expiresAt?: number;
1624
}

src/app/services/websocket.service.spec.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { environment } from 'src/environments/environment';
77
import { TranslateModule, TranslateService } from '@ngx-translate/core';
88
import { WEBSOCKET_PIN_PATH } from '../constants/api.constants';
99
import { LoaderService } from './loader.service';
10+
import { Power } from '../interfaces/verifiable-credential';
1011

1112

1213
//todo mock broadcast channel
@@ -415,8 +416,8 @@ describe('WebsocketService', () => {
415416
timeout: 60,
416417
credentialPreview: {
417418
subjectName: 'John Doe',
419+
power: [{ function: 'Administrar', action: ['create', 'update'] }],
418420
organization: 'Test Org',
419-
issuer: 'Test Issuer',
420421
expirationDate: '2025-12-31',
421422
},
422423
}),
@@ -670,4 +671,78 @@ describe('WebsocketService', () => {
670671
clearIntervalSpy.mockRestore();
671672
}));
672673

674+
it('should map powers to human readable format with single power', () => {
675+
const powers = [
676+
{ function: 'Administrar', action: ['create', 'update'] }
677+
];
678+
679+
const result = service['mapPowersToHumanReadable'](powers);
680+
681+
expect(translateMock.instant).toHaveBeenCalledWith('vc-fields.power.administrar');
682+
expect(translateMock.instant).toHaveBeenCalledWith('vc-fields.power.create');
683+
expect(translateMock.instant).toHaveBeenCalledWith('vc-fields.power.update');
684+
expect(result).toContain(':');
685+
});
686+
687+
it('should map powers to human readable format with multiple powers', () => {
688+
const powers = [
689+
{ function: 'Administrar', action: ['crear'] },
690+
{ function: 'Gestionar', action: ['borrar', 'modificar'] }
691+
];
692+
693+
const result = service['mapPowersToHumanReadable'](powers);
694+
695+
expect(result).toContain('<br/>');
696+
expect(translateMock.instant).toHaveBeenCalledWith('vc-fields.power.administrar');
697+
expect(translateMock.instant).toHaveBeenCalledWith('vc-fields.power.gestionar');
698+
});
699+
700+
it('should return empty string for empty powers array', () => {
701+
expect(service['mapPowersToHumanReadable']([])).toBe('');
702+
});
703+
704+
it('should normalize key by removing special characters and converting to lowercase', () => {
705+
expect(service['normalizeKey']('Administrar')).toBe('administrar');
706+
expect(service['normalizeKey']('Test Key-123')).toBe('testkey123');
707+
expect(service['normalizeKey']('Special@#$Chars')).toBe('specialchars');
708+
expect(service['normalizeKey'](' spaces ')).toBe('spaces');
709+
});
710+
711+
it('should normalize key with null or undefined', () => {
712+
expect(service['normalizeKey'](null)).toBe('');
713+
expect(service['normalizeKey'](undefined)).toBe('');
714+
});
715+
716+
it('should normalize key with numbers', () => {
717+
expect(service['normalizeKey'](123)).toBe('123');
718+
expect(service['normalizeKey'](0)).toBe('0');
719+
});
720+
721+
it('should normalize action keys from array', () => {
722+
const actions = ['Crear', 'Editar', 'Borrar'];
723+
const result = service['normalizeActionKeys'](actions);
724+
725+
expect(result).toEqual(['crear', 'editar', 'borrar']);
726+
});
727+
728+
it('should normalize action keys with special characters', () => {
729+
const actions = ['Test-Action', 'Special@Char', '123Number'];
730+
const result = service['normalizeActionKeys'](actions);
731+
732+
expect(result).toEqual(['testaction', 'specialchar', '123number']);
733+
});
734+
735+
it('should return empty array for non-array action keys', () => {
736+
expect(service['normalizeActionKeys'](null)).toEqual([]);
737+
expect(service['normalizeActionKeys'](undefined)).toEqual([]);
738+
expect(service['normalizeActionKeys']('not-array')).toEqual([]);
739+
});
740+
741+
it('should filter out empty strings from normalized action keys', () => {
742+
const actions = ['valid', '', ' ', 'another'];
743+
const result = service['normalizeActionKeys'](actions);
744+
745+
expect(result).toEqual(['valid', 'another']);
746+
});
747+
673748
});

src/app/services/websocket.service.ts

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { TranslateService } from '@ngx-translate/core';
66
import { WEBSOCKET_NOTIFICATION_PATH, WEBSOCKET_PIN_PATH } from '../constants/api.constants';
77
import { LoaderService } from './loader.service';
88
import { ToastServiceHandler } from './toast.service';
9-
import { isPinRequest, isNotificationRequest } from '../interfaces/websocket-data';
9+
import { isPinRequest, isNotificationRequest, Power, CredentialPreview } from '../interfaces/websocket-data';
1010

1111
@Injectable({
1212
providedIn: 'root',
@@ -200,9 +200,10 @@ export class WebsocketService {
200200

201201
const counter = data.timeout || 80;
202202

203-
const preview = data.credentialPreview;
203+
const preview = data.credentialPreview as CredentialPreview;
204204
const subjectLabel = this.translate.instant('confirmation.holder');
205205
const organizationLabel = this.translate.instant('confirmation.organization');
206+
const powersLabel = this.translate.instant('confirmation.powers');
206207
const expirationLabel = this.translate.instant('confirmation.expiration');
207208

208209

@@ -219,9 +220,13 @@ export class WebsocketService {
219220
<span class="cred-label"><strong>${organizationLabel}</strong>${this.escapeHtml(preview.organization)}</span>
220221
</div>
221222
222-
<div class="cred-row">
223-
<span class="cred-label"><strong>${expirationLabel}</strong>${this.formatDateHuman(preview.expirationDate)}</span>
224-
</div>
223+
<div class="cred-row">
224+
<span class="cred-label"><strong>${powersLabel}</strong>${this.mapPowersToHumanReadable(preview.power)}</span>
225+
</div>
226+
227+
<div class="cred-row">
228+
<span class="cred-label"><strong>${expirationLabel}</strong>${this.formatDateHuman(preview.expirationDate)}</span>
229+
</div>
225230
</div>
226231
`;
227232
}
@@ -278,13 +283,69 @@ export class WebsocketService {
278283
this.toastServiceHandler
279284
.showErrorAlert("The QR session expired")
280285
.subscribe();
281-
window.location.reload();
282286
}
283287

284288
});
285289
interval = this.startCountdown(alert, descriptionWithPreview, counter);
286290
}
287291

292+
private mapPowersToHumanReadable(powers: Power[]): string {
293+
if (powers.length === 0) return '';
294+
295+
const unknown = this.translate.instant('confirmation.unknown');
296+
297+
const lines = powers
298+
.map((p) => {
299+
const fnKey = this.normalizeKey(p?.function);
300+
const actionKeys = this.normalizeActionKeys(p?.action);
301+
302+
const functionLabelRaw =
303+
this.getSafeTranslation(`vc-fields.power.${fnKey}`, p?.function, unknown);
304+
305+
const actionLabelsRaw = actionKeys
306+
.map((a) => this.getSafeTranslation(`vc-fields.power.${a}`, a, unknown))
307+
.filter((x) => x && x !== unknown);
308+
309+
const functionLabel = this.escapeHtml(functionLabelRaw);
310+
const actionLabels = this.escapeHtml(actionLabelsRaw.join(', '));
311+
312+
if (!functionLabel || !actionLabels) return '';
313+
314+
return `${functionLabel}: ${actionLabels}`;
315+
})
316+
.filter(Boolean);
317+
318+
return lines.join('<br/>');
319+
}
320+
321+
private getSafeTranslation(key: string, fallbackText: unknown, unknown: string): string {
322+
const translated = this.translate.instant(key);
323+
324+
const hasRealTranslation = translated && translated !== key;
325+
326+
if (hasRealTranslation) return String(translated);
327+
328+
const fb = String(fallbackText ?? '').trim();
329+
const looksLikeKey = fb.includes('.') || fb.includes('_') || fb.includes('-');
330+
if (!fb || looksLikeKey) return unknown;
331+
332+
return fb;
333+
}
334+
335+
private normalizeKey(value: unknown): string {
336+
return String(value ?? '')
337+
.trim()
338+
.toLowerCase()
339+
.replace(/[^a-z0-9]/g, '');
340+
}
341+
342+
private normalizeActionKeys(actions: unknown): string[] {
343+
if (!Array.isArray(actions)) return [];
344+
return actions
345+
.map((a) => this.normalizeKey(a))
346+
.filter(Boolean);
347+
}
348+
288349
private async showTempOkMessage(message: string): Promise<void> {
289350
const alert = await this.alertController.create({
290351
message: `

src/assets/i18n/ca.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@
233233
"messageHtml": "{{description}}<br><small class=\"counter\">Temps restant: {{counter}} segundos</small>",
234234
"organization":"Organització: ",
235235
"holder":"Titular: ",
236-
"expiration":"Data de caducitat: "
236+
"powers":"Poders: ",
237+
"expiration":"Data de caducitat: ",
238+
"unknown": "Desconegut"
237239
}
238240
}

src/assets/i18n/en.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,9 @@
231231
"messageHtml": "{{description}}<br><small class=\"counter\">Time remaining: {{counter}} seconds</small>",
232232
"organization":"Organization: ",
233233
"holder":"Holder: ",
234-
"expiration":"Expiration date: "
234+
"powers":"Powers: ",
235+
"expiration":"Expiration date: ",
236+
"unknown": "Unknown"
235237
}
236238

237239
}

src/assets/i18n/es.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@
232232
"messageHtml": "{{description}}<br><small class=\"counter\">Tiempo restante: {{counter}} segundos</small>",
233233
"organization":"Organización: ",
234234
"holder":"Titular: ",
235-
"expiration":"Fecha de caducidad: "
235+
"powers":"Poderes: ",
236+
"expiration":"Fecha de caducidad: ",
237+
"unknown": "Desconocido"
236238
}
237239
}

0 commit comments

Comments
 (0)