fortify2-js
Version:
MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.
1,506 lines (1,501 loc) • 53.3 kB
JavaScript
'use strict';
var index = require('../types/index.js');
var metadataManager = require('../metadata/metadata-manager.js');
var validation = require('../utils/validation.js');
var idGenerator = require('../utils/id-generator.js');
var secureMemory = require('../../secure-memory.js');
var index$1 = require('../../../utils/memory/index.js');
var eventManager = require('../events/event-manager.js');
var ArrayCryptoHandler = require('../crypto/ArrayCryptoHandler.js');
var ArraySerializationHandler = require('../serialization/ArraySerializationHandler.js');
require('../../../index.js');
var crypto = require('../../../core/crypto.js');
var types = require('../../../utils/memory/types.js');
/***************************************************************************
* FortifyJS - Enhanced Secure Array Core Implementation
*
* security features and array methods
*
* @author Nehonix & Community
*
* @license MIT
***************************************************************************** */
/**
* A secure array that can store sensitive data with enhanced security features
* T represents the type of elements stored in the array
*/
class SecureArray {
/**
* Creates a new secure array
*/
constructor(initialData, options) {
// Core data storage
this.elements = [];
this.secureBuffers = new Map();
// State management
this._isDestroyed = false;
this._isReadOnly = false;
this._isFrozen = false;
this._version = 1;
this._lastModified = Date.now();
this._createdAt = Date.now();
// Memory management
this._memoryTracking = false;
// Snapshot functionality
this.snapshots = new Map();
this._id = idGenerator.ArrayIdGenerator.generate();
this.options = { ...index.DEFAULT_SECURE_ARRAY_OPTIONS, ...options };
this._isReadOnly = this.options.readOnly;
this._maxSize = this.options.maxSize;
// Initialize modular components
this.metadataManager = new metadataManager.ArrayMetadataManager();
this.eventManager = new eventManager.ArrayEventManager();
this.cryptoHandler = new ArrayCryptoHandler.ArrayCryptoHandler(this._id);
this.serializationHandler = new ArraySerializationHandler.ArraySerializationHandler(this.cryptoHandler, this.metadataManager);
// Set encryption key if provided
if (this.options.encryptionKey) {
this.cryptoHandler.setEncryptionKey(this.options.encryptionKey);
}
// Enable memory tracking
this._memoryTracking = this.options.enableMemoryTracking;
// Register with advanced memory manager if tracking is enabled
if (this._memoryTracking) {
index$1.memoryManager.registerObject(this, this._id);
// Listen to memory events for proactive management
index$1.memoryManager.on(types.MemoryEventType.MEMORY_PRESSURE, (event) => {
if (event.data?.pressure > 0.8) {
this.handleMemoryPressure();
}
});
index$1.memoryManager.on(types.MemoryEventType.LEAK_DETECTED, (event) => {
if (event.data?.leaks?.includes(this._id)) {
console.warn(`Potential memory leak detected in SecureArray ${this._id}`);
}
});
}
// Initialize secure buffer pool
this.initializeSecureBufferPool();
// Set initial data
if (initialData) {
this.pushAll(initialData);
}
this.eventManager.emit("created", -1, undefined, { id: this._id });
}
// ===== MEMORY MANAGEMENT =====
/**
* Initialize secure buffer pool for efficient memory reuse
*/
initializeSecureBufferPool() {
if (!this.secureBufferPool) {
try {
this.secureBufferPool =
index$1.memoryManager.getPool("secure-array-buffer-pool") ||
index$1.memoryManager.createPool({
name: "secure-array-buffer-pool",
factory: () => new Uint8Array(1024), // 1KB buffers
reset: (buffer) => {
// Secure wipe before reuse
this.secureWipe(buffer);
},
capacity: 50,
strategy: "LRU",
validator: (buffer) => buffer instanceof Uint8Array,
});
}
catch (error) {
// Pool might already exist, try to get it
this.secureBufferPool = index$1.memoryManager.getPool("secure-array-buffer-pool");
}
}
}
/**
* Handle memory pressure by cleaning up unused resources
*/
handleMemoryPressure() {
// Clean up unused secure buffers
for (const [index, buffer] of this.secureBuffers.entries()) {
if (index >= this.elements.length) {
buffer.destroy();
this.secureBuffers.delete(index);
}
}
// Clear old snapshots (keep only the latest 3)
const sortedSnapshots = Array.from(this.snapshots.entries()).sort(([, a], [, b]) => b.timestamp - a.timestamp);
if (sortedSnapshots.length > 3) {
for (let i = 3; i < sortedSnapshots.length; i++) {
this.snapshots.delete(sortedSnapshots[i][0]);
}
}
// Trigger garbage collection if available
if (global.gc) {
global.gc();
}
this.eventManager.emit("gc", -1, undefined, {
operation: "memory_pressure_cleanup",
buffersCleared: this.secureBuffers.size,
});
}
/**
* Secure wipe of buffer contents
*/
secureWipe(buffer) {
return crypto.FortifyJS.secureWipe(buffer);
}
// ===== UTILITY METHODS =====
/**
* Ensures the array is not destroyed
*/
ensureNotDestroyed() {
if (this._isDestroyed) {
console.error("Array has been destroyed and cannot be used.");
throw new Error("Cannot use destroyed fortified Array. ");
}
}
/**
* Ensures the array is not read-only
*/
ensureNotReadOnly() {
if (this._isReadOnly) {
throw new Error("SecureArray is read-only");
}
}
/**
* Ensures the array is not frozen
*/
ensureNotFrozen() {
if (this._isFrozen) {
throw new Error("SecureArray is frozen");
}
}
/**
* Check if adding elements would exceed max size
*/
checkSizeLimit(additionalElements = 1) {
if (this._maxSize &&
this.elements.length + additionalElements > this._maxSize) {
throw new Error(`Operation would exceed maximum size limit of ${this._maxSize}`);
}
}
/**
* Updates the last modified timestamp and version
*/
updateLastModified() {
this._lastModified = Date.now();
this._version++;
}
/**
* Validates an index
*/
validateIndex(index) {
if (this.options.enableIndexValidation) {
if (!Number.isInteger(index) || index < 0) {
throw new Error(`Invalid index: ${index}`);
}
}
}
/**
* Validates a value
*/
validateValue(value) {
if (this.options.enableTypeValidation) {
validation.ArrayValidationUtils.validateSecureArrayValue(value);
}
}
// ===== CORE ARRAY OPERATIONS =====
/**
* Gets the length of the array
*/
get length() {
this.ensureNotDestroyed();
return this.elements.length;
}
/**
* Gets the unique identifier of this array
*/
get id() {
return this._id;
}
/**
* Gets the version number (increments on each mutation)
*/
get version() {
return this._version;
}
/**
* Gets when the array was last modified
*/
get lastModified() {
return this._lastModified;
}
/**
* Gets when the array was created
*/
get createdAt() {
return this._createdAt;
}
/**
* Check if the array is empty
*/
get isEmpty() {
return this.elements.length === 0;
}
/**
* Check if the array is read-only
*/
get isReadOnly() {
return this._isReadOnly;
}
/**
* Check if the array is frozen
*/
get isFrozen() {
return this._isFrozen;
}
/**
* Check if the array is destroyed
*/
get isDestroyed() {
return this._isDestroyed;
}
/**
* Gets an element at the specified index with automatic decryption
*/
get(index) {
this.ensureNotDestroyed();
this.validateIndex(index);
if (index >= this.elements.length) {
return undefined;
}
const value = this.elements[index];
// Update access metadata
if (this.metadataManager.has(index)) {
const metadata = this.metadataManager.get(index);
this.metadataManager.update(index, metadata.type, metadata.isSecure, true);
}
// Check if value is encrypted (starts with [ENCRYPTED:)
if (typeof value === "string" &&
this.cryptoHandler.isEncrypted(value)) {
try {
// Decrypt the value automatically
const decryptedValue = this.cryptoHandler.decryptValue(value);
this.eventManager.emit("get", index, decryptedValue);
return decryptedValue;
}
catch (error) {
console.error(`Failed to decrypt value at index ${index}:`, error);
// Return the encrypted value if decryption fails
this.eventManager.emit("get", index, value);
return value;
}
}
// Convert SecureBuffer back to original type based on metadata
if (value instanceof secureMemory.SecureBuffer) {
const metadata = this.metadataManager.get(index);
if (metadata?.type === "string") {
// Convert SecureBuffer back to string
const buffer = value.getBuffer();
const result = new TextDecoder().decode(buffer);
this.eventManager.emit("get", index, result);
return result;
}
}
this.eventManager.emit("get", index, value);
return value;
}
/**
* Gets element at index with bounds checking
*/
at(index) {
this.ensureNotDestroyed();
// Handle negative indices
if (index < 0) {
index = this.elements.length + index;
}
return this.get(index);
}
/**
* Sets an element at the specified index
*/
set(index, value) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
this.validateIndex(index);
this.validateValue(value);
// Check size limit for new indices
if (index >= this.elements.length) {
this.checkSizeLimit(index - this.elements.length + 1);
}
// Extend array if necessary
while (index >= this.elements.length) {
this.elements.push(undefined);
}
// Clean up any existing secure buffer for this index
this.cleanupIndex(index);
// Handle secure storage for strings
if (typeof value === "string") {
const secureBuffer = secureMemory.SecureBuffer.from(value);
this.secureBuffers.set(index, secureBuffer);
this.elements[index] = secureBuffer;
this.metadataManager.update(index, "string", true);
}
else {
this.elements[index] = value;
this.metadataManager.update(index, typeof value, false);
}
this.updateLastModified();
this.eventManager.emit("set", index, value);
return this;
}
/**
* Cleans up resources associated with an index
*/
cleanupIndex(index) {
if (this.secureBuffers.has(index)) {
this.secureBuffers.get(index)?.destroy();
this.secureBuffers.delete(index);
}
}
/**
* Adds an element to the end of the array
*/
push(value) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
this.validateValue(value);
this.checkSizeLimit(1);
const index = this.elements.length;
// Handle secure storage for strings
if (typeof value === "string") {
const secureBuffer = secureMemory.SecureBuffer.from(value);
this.secureBuffers.set(index, secureBuffer);
this.elements.push(secureBuffer);
this.metadataManager.update(index, "string", true);
}
else {
this.elements.push(value);
this.metadataManager.update(index, typeof value, false);
}
this.updateLastModified();
this.eventManager.emit("push", index, value);
return this.elements.length;
}
/**
* Removes and returns the last element
*/
pop() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
if (this.elements.length === 0) {
return undefined;
}
const index = this.elements.length - 1;
const value = this.get(index);
// Clean up resources
this.cleanupIndex(index);
this.elements.pop();
this.metadataManager.delete(index);
this.updateLastModified();
this.eventManager.emit("pop", index, value);
return value;
}
/**
* Removes and returns the first element
*/
shift() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
if (this.elements.length === 0) {
return undefined;
}
const value = this.get(0);
// Clean up resources for index 0
this.cleanupIndex(0);
this.elements.shift();
// Shift all metadata indices down by 1
this.metadataManager.shiftIndices(0, -1);
// Shift all secure buffer indices down by 1
const newSecureBuffers = new Map();
for (const [index, buffer] of this.secureBuffers.entries()) {
if (index > 0) {
newSecureBuffers.set(index - 1, buffer);
}
}
this.secureBuffers = newSecureBuffers;
this.updateLastModified();
this.eventManager.emit("shift", 0, value);
return value;
}
/**
* Adds elements to the beginning of the array
*/
unshift(...values) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
this.checkSizeLimit(values.length);
for (const value of values) {
this.validateValue(value);
}
// Shift existing secure buffers and metadata
const shiftAmount = values.length;
this.metadataManager.shiftIndices(0, shiftAmount);
const newSecureBuffers = new Map();
for (const [index, buffer] of this.secureBuffers.entries()) {
newSecureBuffers.set(index + shiftAmount, buffer);
}
this.secureBuffers = newSecureBuffers;
// Add new values at the beginning
for (let i = 0; i < values.length; i++) {
const value = values[i];
if (typeof value === "string") {
const secureBuffer = secureMemory.SecureBuffer.from(value);
this.secureBuffers.set(i, secureBuffer);
this.metadataManager.update(i, "string", true);
}
else {
this.metadataManager.update(i, typeof value, false);
}
}
this.elements.unshift(...values);
this.updateLastModified();
this.eventManager.emit("unshift", 0, values);
return this.elements.length;
}
/**
* Adds multiple elements to the array
*/
pushAll(values) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
this.checkSizeLimit(values.length);
for (const value of values) {
this.push(value);
}
return this.elements.length;
}
// ===== ENHANCED ARRAY METHODS =====
/**
* Removes elements from array and optionally inserts new elements
*/
splice(start, deleteCount, ...items) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
if (start < 0) {
start = Math.max(0, this.elements.length + start);
}
deleteCount =
deleteCount === undefined
? this.elements.length - start
: Math.max(0, deleteCount);
if (items.length > 0) {
this.checkSizeLimit(items.length - deleteCount);
for (const item of items) {
this.validateValue(item);
}
}
// Create a new SecureArray for removed elements
const removedElements = new SecureArray();
// Get elements to be removed
for (let i = start; i < start + deleteCount && i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
removedElements.push(value);
}
}
// Clean up secure buffers for removed elements
for (let i = start; i < start + deleteCount; i++) {
this.cleanupIndex(i);
}
// Perform the splice operation
const removed = this.elements.splice(start, deleteCount, ...items);
// Update metadata and secure buffers
this.metadataManager.splice(start, deleteCount, items.length);
// Handle secure storage for new items
for (let i = 0; i < items.length; i++) {
const value = items[i];
const index = start + i;
if (typeof value === "string") {
const secureBuffer = secureMemory.SecureBuffer.from(value);
this.secureBuffers.set(index, secureBuffer);
this.metadataManager.update(index, "string", true);
}
else {
this.metadataManager.update(index, typeof value, false);
}
}
this.updateLastModified();
this.eventManager.emit("splice", start, { removed, added: items });
return removedElements;
}
/**
* Returns a shallow copy of a portion of the array
*/
slice(start, end) {
this.ensureNotDestroyed();
const slicedElements = [];
const actualStart = start === undefined
? 0
: start < 0
? Math.max(0, this.elements.length + start)
: start;
const actualEnd = end === undefined
? this.elements.length
: end < 0
? this.elements.length + end
: end;
for (let i = actualStart; i < actualEnd && i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
slicedElements.push(value);
}
}
const newArray = new SecureArray(slicedElements, {
...this.options,
});
this.eventManager.emit("slice", actualStart, {
start: actualStart,
end: actualEnd,
result: newArray,
});
return newArray;
}
/**
* Concatenates arrays and returns a new SecureArray
*/
concat(...arrays) {
this.ensureNotDestroyed();
const newElements = [];
// Add current array elements
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
newElements.push(value);
}
}
// Add elements from other arrays
for (const arr of arrays) {
if (arr instanceof SecureArray) {
for (let i = 0; i < arr.length; i++) {
const value = arr.get(i);
if (value !== undefined) {
newElements.push(value);
}
}
}
else {
newElements.push(...arr);
}
}
return new SecureArray(newElements, { ...this.options });
}
/**
* Joins all elements into a string
*/
join(separator = ",") {
this.ensureNotDestroyed();
const stringElements = [];
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
stringElements.push(value?.toString() ?? "");
}
return stringElements.join(separator);
}
/**
* Reverses the array in place
*/
reverse() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
// Create new mappings for secure buffers
const newSecureBuffers = new Map();
const length = this.elements.length;
for (const [index, buffer] of this.secureBuffers.entries()) {
newSecureBuffers.set(length - 1 - index, buffer);
}
this.secureBuffers = newSecureBuffers;
this.elements.reverse();
this.metadataManager.reverse(length);
this.updateLastModified();
this.eventManager.emit("reverse", -1, undefined);
return this;
}
/**
* Sorts the array in place
*/
sort(compareFn) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
// Get actual values for sorting
const indexValuePairs = [];
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
indexValuePairs.push({ index: i, value });
}
}
// Sort the pairs
indexValuePairs.sort((a, b) => {
if (compareFn) {
return compareFn(a.value, b.value);
}
const aStr = String(a.value);
const bStr = String(b.value);
return aStr < bStr ? -1 : aStr > bStr ? 1 : 0;
});
// Rebuild the array in sorted order
const newElements = [];
const newSecureBuffers = new Map();
for (let i = 0; i < indexValuePairs.length; i++) {
const { index: oldIndex, value } = indexValuePairs[i];
newElements[i] = this.elements[oldIndex];
if (this.secureBuffers.has(oldIndex)) {
newSecureBuffers.set(i, this.secureBuffers.get(oldIndex));
}
}
this.elements = newElements;
this.secureBuffers = newSecureBuffers;
this.metadataManager.reorder(indexValuePairs.map((p) => p.index));
this.updateLastModified();
this.eventManager.emit("sort", -1, undefined);
return this;
}
// ===== FUNCTIONAL PROGRAMMING METHODS =====
/**
* Calls a function for each element
*/
forEach(callback, thisArg) {
this.ensureNotDestroyed();
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
callback.call(thisArg, value, i, this);
}
}
}
/**
* Creates a new array with results of calling a function for every element
*/
map(callback, thisArg) {
this.ensureNotDestroyed();
const mappedElements = [];
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
mappedElements.push(callback.call(thisArg, value, i, this));
}
}
return new SecureArray(mappedElements, { ...this.options });
}
/**
* Creates a new array with elements that pass a test
*/
filter(predicate, thisArg) {
this.ensureNotDestroyed();
const filteredElements = [];
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined &&
predicate.call(thisArg, value, i, this)) {
filteredElements.push(value);
}
}
return new SecureArray(filteredElements, { ...this.options });
}
/**
* Reduces the array to a single value
*/
reduce(callback, initialValue) {
this.ensureNotDestroyed();
let accumulator = initialValue;
let startIndex = 0;
if (accumulator === undefined) {
if (this.elements.length === 0) {
throw new TypeError("Reduce of empty array with no initial value");
}
accumulator = this.get(0);
startIndex = 1;
}
for (let i = startIndex; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
accumulator = callback(accumulator, value, i, this);
}
}
return accumulator;
}
/**
* Tests whether at least one element passes the test
*/
some(predicate, thisArg) {
this.ensureNotDestroyed();
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined &&
predicate.call(thisArg, value, i, this)) {
return true;
}
}
return false;
}
/**
* Tests whether all elements pass the test
*/
every(predicate, thisArg) {
this.ensureNotDestroyed();
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined &&
!predicate.call(thisArg, value, i, this)) {
return false;
}
}
return true;
}
/**
* Finds the first element that satisfies the predicate
*/
find(predicate, thisArg) {
this.ensureNotDestroyed();
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined &&
predicate.call(thisArg, value, i, this)) {
return value;
}
}
return undefined;
}
/**
* Finds the index of the first element that satisfies the predicate
*/
findIndex(predicate, thisArg) {
this.ensureNotDestroyed();
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined &&
predicate.call(thisArg, value, i, this)) {
return i;
}
}
return -1;
}
/**
* Finds the last element that satisfies the predicate
*/
findLast(predicate, thisArg) {
this.ensureNotDestroyed();
for (let i = this.elements.length - 1; i >= 0; i--) {
const value = this.get(i);
if (value !== undefined &&
predicate.call(thisArg, value, i, this)) {
return value;
}
}
return undefined;
}
/**
* Finds the index of the last element that satisfies the predicate
*/
findLastIndex(predicate, thisArg) {
this.ensureNotDestroyed();
for (let i = this.elements.length - 1; i >= 0; i--) {
const value = this.get(i);
if (value !== undefined &&
predicate.call(thisArg, value, i, this)) {
return i;
}
}
return -1;
}
/**
* Returns the first index of an element
*/
indexOf(searchElement, fromIndex = 0) {
this.ensureNotDestroyed();
const startIndex = fromIndex < 0
? Math.max(0, this.elements.length + fromIndex)
: fromIndex;
for (let i = startIndex; i < this.elements.length; i++) {
const value = this.get(i);
if (value === searchElement) {
return i;
}
}
return -1;
}
/**
* Returns the last index of an element
*/
lastIndexOf(searchElement, fromIndex) {
this.ensureNotDestroyed();
const startIndex = fromIndex === undefined
? this.elements.length - 1
: fromIndex < 0
? this.elements.length + fromIndex
: fromIndex;
for (let i = Math.min(startIndex, this.elements.length - 1); i >= 0; i--) {
const value = this.get(i);
if (value === searchElement) {
return i;
}
}
return -1;
}
/**
* Checks if an element exists in the array
*/
includes(searchElement, fromIndex = 0) {
return this.indexOf(searchElement, fromIndex) !== -1;
}
// ===== ENHANCED SECURITY METHODS =====
/**
* Creates a snapshot of the current array state
*/
createSnapshot(name) {
this.ensureNotDestroyed();
const snapshotId = name ||
`snapshot_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Deep copy elements
const elementsCopy = [];
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
elementsCopy.push(value);
}
}
// Copy metadata
const metadataCopy = new Map(this.metadataManager.getAllMetadata());
this.snapshots.set(snapshotId, {
elements: elementsCopy,
metadata: metadataCopy,
timestamp: Date.now(),
});
this.eventManager.emit("snapshot_created", -1, undefined, {
snapshotId,
});
return snapshotId;
}
/**
* Restores the array from a snapshot
*/
restoreFromSnapshot(snapshotId) {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
const snapshot = this.snapshots.get(snapshotId);
if (!snapshot) {
return false;
}
// Clear current state
this.clear();
// Restore from snapshot
for (const element of snapshot.elements) {
this.push(element);
}
this.eventManager.emit("snapshot_restored", -1, undefined, {
snapshotId,
});
return true;
}
/**
* Lists available snapshots
*/
listSnapshots() {
return Array.from(this.snapshots.entries()).map(([id, snapshot]) => ({
id,
timestamp: snapshot.timestamp,
}));
}
/**
* Deletes a snapshot
*/
deleteSnapshot(snapshotId) {
return this.snapshots.delete(snapshotId);
}
/**
* Clears all elements from the array
*/
clear() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
// Clean up all secure buffers
for (const buffer of this.secureBuffers.values()) {
buffer.destroy();
}
this.secureBuffers.clear();
// Clear elements and metadata
this.elements.length = 0;
this.metadataManager.clear();
this.updateLastModified();
this.eventManager.emit("clear", -1, undefined);
return this;
}
/**
* Freezes the array to prevent modifications
*/
freeze() {
this.ensureNotDestroyed();
this._isFrozen = true;
this.eventManager.emit("freeze", -1, undefined);
return this;
}
/**
* Unfreezes the array to allow modifications
*/
unfreeze() {
this.ensureNotDestroyed();
this._isFrozen = false;
this.eventManager.emit("unfreeze", -1, undefined);
return this;
}
/**
* Makes the array read-only
*/
makeReadOnly() {
this.ensureNotDestroyed();
this._isReadOnly = true;
this.eventManager.emit("readonly", -1, undefined);
return this;
}
/**
* Removes read-only restriction
*/
makeWritable() {
this.ensureNotDestroyed();
this._isReadOnly = false;
this.eventManager.emit("writable", -1, undefined);
return this;
}
/**
* Securely wipes all data and destroys the array
* @example
* //===================== correct ===========
const x = fArray([] as number[]);
x.push(12);
console.log(x._array);
x.destroy();
//================ incorrect ===============
const x = fArray([] as number[]);
x.destroy(); // will destroy the array
x.push(12); // x.push will throw an error
console.log(x._array); // will throw an error
*/
destroy() {
if (this._isDestroyed) {
return;
}
// Clean up all secure buffers
for (const buffer of this.secureBuffers.values()) {
buffer.destroy();
}
this.secureBuffers.clear();
// Clear snapshots
this.snapshots.clear();
// Secure wipe of elements array
if (this.elements.length > 0) {
this.elements.fill(null);
this.elements.length = 0;
}
// Clear metadata
this.metadataManager.clear();
// Unregister from memory manager
if (this._memoryTracking) {
index$1.memoryManager.unregisterObject(this._id);
}
this._isDestroyed = true;
this.eventManager.emit("destroyed", -1, undefined);
// Clear event listeners
this.eventManager.removeAllListeners();
}
// ===== UTILITY AND STATISTICS METHODS =====
/**
* Gets comprehensive statistics about the array
*/
getStats() {
this.ensureNotDestroyed();
const typeCount = new Map();
let secureElementCount = 0;
let totalMemoryUsage = 0;
let totalAccesses = 0;
for (let i = 0; i < this.elements.length; i++) {
const metadata = this.metadataManager.get(i);
if (metadata) {
const type = metadata.type;
typeCount.set(type, (typeCount.get(type) || 0) + 1);
if (metadata.isSecure) {
secureElementCount++;
}
totalAccesses += metadata.accessCount;
}
// Estimate memory usage
const value = this.get(i);
if (value !== undefined) {
if (typeof value === "string") {
totalMemoryUsage += value.length * 2; // UTF-16
}
else if (typeof value === "number") {
totalMemoryUsage += 8; // 64-bit number
}
else if (typeof value === "boolean") {
totalMemoryUsage += 1;
}
else {
totalMemoryUsage += 64; // Estimate for objects
}
}
}
return {
length: this.elements.length,
secureElements: secureElementCount,
totalAccesses,
memoryUsage: totalMemoryUsage,
lastModified: this._lastModified,
version: this._version,
createdAt: this._createdAt,
isReadOnly: this._isReadOnly,
isFrozen: this._isFrozen,
typeDistribution: Object.fromEntries(typeCount),
secureElementCount,
estimatedMemoryUsage: totalMemoryUsage,
snapshotCount: this.snapshots.size,
encryptionEnabled: this.cryptoHandler.getEncryptionStatus().hasEncryptionKey,
};
}
/**
* Validates the integrity of the array
*/
validateIntegrity() {
this.ensureNotDestroyed();
const errors = [];
// Check if elements length matches metadata count
const metadataCount = this.metadataManager.size();
if (this.elements.length !== metadataCount) {
errors.push(`Element count (${this.elements.length}) doesn't match metadata count (${metadataCount})`);
}
// Check secure buffer consistency
for (const [index, buffer] of this.secureBuffers.entries()) {
if (index >= this.elements.length) {
errors.push(`Secure buffer exists for invalid index: ${index}`);
}
const metadata = this.metadataManager.get(index);
if (!metadata || !metadata.isSecure) {
errors.push(`Secure buffer exists but metadata indicates non-secure element at index: ${index}`);
}
try {
buffer.getBuffer(); // Test if buffer is still valid
}
catch (error) {
errors.push(`Invalid secure buffer at index ${index}: ${error}`);
}
}
// Check for memory leaks in metadata
for (let i = 0; i < this.metadataManager.size(); i++) {
if (!this.metadataManager.has(i) && i < this.elements.length) {
errors.push(`Missing metadata for element at index: ${i}`);
}
}
return {
isValid: errors.length === 0,
errors,
};
}
/**
* Compacts the array by removing undefined/null elements
*/
compact() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
const compactedElements = [];
const newSecureBuffers = new Map();
let newIndex = 0;
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== null && value !== undefined) {
compactedElements.push(this.elements[i]);
if (this.secureBuffers.has(i)) {
newSecureBuffers.set(newIndex, this.secureBuffers.get(i));
}
const metadata = this.metadataManager.get(i);
if (metadata) {
this.metadataManager.update(newIndex, metadata.type, metadata.isSecure);
}
newIndex++;
}
else {
// Clean up secure buffer for removed element
this.cleanupIndex(i);
}
}
// Clean up old metadata
this.metadataManager.clear();
this.elements = compactedElements;
this.secureBuffers = newSecureBuffers;
this.updateLastModified();
this.eventManager.emit("compact", -1, undefined, {
originalLength: this.elements.length +
(this.elements.length - compactedElements.length),
newLength: compactedElements.length,
});
return this;
}
/**
* Removes duplicate elements
*/
unique() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
const seen = new Set();
const uniqueElements = [];
const newSecureBuffers = new Map();
let newIndex = 0;
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined && !seen.has(value)) {
seen.add(value);
uniqueElements.push(this.elements[i]);
if (this.secureBuffers.has(i)) {
newSecureBuffers.set(newIndex, this.secureBuffers.get(i));
}
const metadata = this.metadataManager.get(i);
if (metadata) {
this.metadataManager.update(newIndex, metadata.type, metadata.isSecure);
}
newIndex++;
}
else if (value !== undefined) {
// Clean up duplicate's secure buffer
this.cleanupIndex(i);
}
}
// Clean up old metadata
this.metadataManager.clear();
this.elements = uniqueElements;
this.secureBuffers = newSecureBuffers;
this.updateLastModified();
this.eventManager.emit("unique", -1, undefined, {
originalLength: this.elements.length +
(this.elements.length - uniqueElements.length),
newLength: uniqueElements.length,
});
return this;
}
/**
* Groups elements by a key function
*/
groupBy(keyFn) {
this.ensureNotDestroyed();
const groups = new Map();
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
const key = keyFn(value, i);
if (!groups.has(key)) {
groups.set(key, new SecureArray([], { ...this.options }));
}
groups.get(key).push(value);
}
}
return groups;
}
/**
* Returns the minimum value
*/
min(compareFn) {
this.ensureNotDestroyed();
if (this.elements.length === 0) {
return undefined;
}
let min = this.get(0);
if (min === undefined) {
return undefined;
}
for (let i = 1; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
if (compareFn) {
if (compareFn(value, min) < 0) {
min = value;
}
}
else {
if (value < min) {
min = value;
}
}
}
}
return min;
}
/**
* Returns the maximum value
*/
max(compareFn) {
this.ensureNotDestroyed();
if (this.elements.length === 0) {
return undefined;
}
let max = this.get(0);
if (max === undefined) {
return undefined;
}
for (let i = 1; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
if (compareFn) {
if (compareFn(value, max) > 0) {
max = value;
}
}
else {
if (value > max) {
max = value;
}
}
}
}
return max;
}
/**
* Shuffles the array in place using Fisher-Yates algorithm
*/
shuffle() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
for (let i = this.elements.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
// Swap elements
[this.elements[i], this.elements[j]] = [
this.elements[j],
this.elements[i],
];
// Swap secure buffers if they exist
const bufferI = this.secureBuffers.get(i);
const bufferJ = this.secureBuffers.get(j);
if (bufferI) {
this.secureBuffers.set(j, bufferI);
}
else {
this.secureBuffers.delete(j);
}
if (bufferJ) {
this.secureBuffers.set(i, bufferJ);
}
else {
this.secureBuffers.delete(i);
}
// Swap metadata
const metaI = this.metadataManager.get(i);
const metaJ = this.metadataManager.get(j);
if (metaI && metaJ) {
this.metadataManager.update(i, metaJ.type, metaJ.isSecure);
this.metadataManager.update(j, metaI.type, metaI.isSecure);
}
}
this.updateLastModified();
this.eventManager.emit("shuffle", -1, undefined);
return this;
}
/**
* Returns a random sample of elements
*/
sample(count = 1) {
this.ensureNotDestroyed();
if (count <= 0 || this.elements.length === 0) {
return new SecureArray([], { ...this.options });
}
const sampleCount = Math.min(count, this.elements.length);
const indices = new Set();
const sampledElements = [];
while (indices.size < sampleCount) {
const randomIndex = Math.floor(Math.random() * this.elements.length);
if (!indices.has(randomIndex)) {
indices.add(randomIndex);
const value = this.get(randomIndex);
if (value !== undefined) {
sampledElements.push(value);
}
}
}
return new SecureArray(sampledElements, { ...this.options });
}
// ===== ITERATOR IMPLEMENTATION =====
/**
* Returns an iterator for the array
*/
*[Symbol.iterator]() {
this.ensureNotDestroyed();
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
yield value;
}
}
}
/**
* Returns an iterator for array entries [index, value]
*/
*entries() {
this.ensureNotDestroyed();
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
yield [i, value];
}
}
}
/**
* Returns an iterator for array indices
*/
*keys() {
this.ensureNotDestroyed();
for (let i = 0; i < this.elements.length; i++) {
yield i;
}
}
/**
* Returns an iterator for array values
*/
*values() {
return this[Symbol.iterator]();
}
// ===== EVENT MANAGEMENT =====
/**
* Adds an event listener
*/
on(event, listener) {
this.eventManager.on(event, listener);
return this;
}
/**
* Removes an event listener
*/
off(event, listener) {
this.eventManager.off(event, listener);
return this;
}
/**
* Adds a one-time event listener
*/
once(event, listener) {
this.eventManager.once(event, listener);
return this;
}
// ===== SERIALIZATION AND EXPORT =====
/**
* Serializes the SecureArray to a secure format
*/
serialize(options) {
this.ensureNotDestroyed();
return this.serializationHandler.serialize(this.elements, options);
}
/**
* Exports the SecureArray data in various formats
*/
exportData(format = "json") {
this.ensureNotDestroyed();
// Get the actual values (not SecureBuffers)
const actualElements = [];
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
actualElements.push(value);
}
}
return this.serializationHandler.exportData(actualElements, format);
}
/**
* Converts to a regular JavaScript array (loses security features)
*/
toArray() {
this.ensureNotDestroyed();
const regularArray = [];
for (let i = 0; i < this.elements.length; i++) {
const value = this.get(i);
if (value !== undefined) {
regularArray.push(value);
}
}
return regularArray;
}
/**
* Converts to a regular JavaScript array (same as toArray method)
*/
get _array() {
return this.toArray();
}
/**
* Creates a SecureArray from a regular array
*/
static from(arrayLike, options) {
const elements = Array.from(arrayLike);
return new SecureArray(elements, options);
}
/**
* Creates a SecureArray with specified length and fill value
*/
static of(...elements) {
return new SecureArray(elements);
}
// ===== ENCRYPTION METHODS =====
/**
* Gets encryption status from the crypto handler
*/
getEncryptionStatus() {
return this.cryptoHandler.getEncryptionStatus();
}
/**
* Sets an encryption key for the array
*/
setEncryptionKey(key) {
this.ensureNotDestroyed();
this.cryptoHandler.setEncryptionKey(key);
}
/**
* Gets the raw encrypted data without decryption (for verification)
*/
getRawEncryptedData() {
this.ensureNotDestroyed();
return [...this.elements];
}
/**
* Gets a specific element's raw encrypted form (for verification)
*/
getRawEncryptedElement(index) {
this.ensureNotDestroyed();
this.validateIndex(index);
if (index >= this.elements.length) {
return undefined;
}
return this.elements[index];
}
/**
* Encrypts all elements in the array using AES-256-CTR-HMAC encryption
* with proper memory management and atomic operations
*/
encryptAll() {
this.ensureNotDestroyed();
this.ensureNotReadOnly();
this.ensureNotFrozen();
// Check if encryption key is set
const encryptionStatus = this.cryptoHandler.getEncryptionStatus();
if (!encryptionStatus.hasEncryptionKey) {
throw new Error("Encryption key must be set before calling encryptAll()");
}
// Prepare temporary storage for atomic operation
const encryptedValues = [];
const originalMetadata = new Map();
const indicesToProcess = [];
try {
// First pass: encrypt all values i