Skip to content
26 changes: 26 additions & 0 deletions projects/igniteui-angular/src/lib/grids/common/random.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { getUUID } from './random';

describe('Random (crypto.randomUuid()) fallback unit tests', () => {
let originalRandomUuid = crypto.randomUUID;

beforeAll(() => {
crypto.randomUUID = null; // Mock crypto.randomUUID to simulate a non-secure context
});

it('should generate a valid UUID', () => {
const uuid = getUUID();
expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
});

it('should generate unique UUIDs', () => {
const uuids = new Set();
for (let i = 0; i < 100; i++) {
uuids.add(getUUID());
}
expect(uuids.size).toBe(100); // All UUIDs should be unique
});

afterAll(() => {
crypto.randomUUID = originalRandomUuid; // Restore the original function
});
});
19 changes: 19 additions & 0 deletions projects/igniteui-angular/src/lib/grids/common/random.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Use the function to get a random UUID string when secure context is not guaranteed making crypto.randomUUID unavailable.
* @returns A random UUID string.
*/
export function getUUID(): `${string}-${string}-${string}-${string}-${string}` {
if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
// Secure fallback using crypto.getRandomValues()
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);

// Set version (4) and variant (RFC 4122)
bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 1

const a = [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
return `${a.slice(0, 8)}-${a.slice(8, 12)}-${a.slice(12, 16)}-${a.slice(16, 20)}-${a.slice(20)}`;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isTree } from '../../../data-operations/expressions-tree-util';
import { FilteringLogic, IFilteringExpression } from '../../../data-operations/filtering-expression.interface';
import { IFilteringExpressionsTree } from '../../../data-operations/filtering-expressions-tree';
import { getUUID } from '../../common/random';

/**
* @hidden @internal
Expand Down Expand Up @@ -30,7 +31,7 @@ export class ExpressionUI {

constructor() {
// Use IDs to identify expressions clearly and use to track them in template @for cycles.
this.expressionId = crypto.randomUUID();
this.expressionId = getUUID();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ import { IgxGridCellComponent } from './cell.component';
import { IgxGridValidationService } from './grid/grid-validation.service';
import { getCurrentResourceStrings } from '../core/i18n/resources';
import { isTree, recreateTreeFromFields } from '../data-operations/expressions-tree-util';
import { getUUID } from './common/random';

interface IMatchInfoCache {
row: any;
Expand Down Expand Up @@ -3785,7 +3786,7 @@ export abstract class IgxGridBaseDirective implements GridType,
const primaryColumn = this._columns.find(col => col.field === this.primaryKey);
const idType = this.data.length ?
this.resolveDataTypes(this.data[0][this.primaryKey]) : primaryColumn ? primaryColumn.dataType : 'string';
return idType === 'string' ? crypto.randomUUID() : FAKE_ROW_ID--;
return idType === 'string' ? getUUID() : FAKE_ROW_ID--;
}

/**
Expand Down
Loading