feat: Merge isohash functionality into smarthash for cross-environment hash support
- Updated test files to use new tapbundle import from @git.zone/tstest. - Created a new plan for merging isohash into smarthash, detailing objectives and implementation steps. - Added browser-specific tests for SHA256 hashing functions in test/test.browser.ts. - Implemented browser-compatible hashing functions in ts_web/index.ts using Web Crypto API. - Introduced plugins for environment detection and JSON handling in ts_web/plugins.ts. - Ensured that existing smarthash functionality remains intact and consistent across environments.
This commit is contained in:
@ -5,7 +5,7 @@
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "smarthash",
|
||||
"description": "Provides simplified access to Node.js hash functions, including SHA256 and MD5, with support for strings, streams, and files.",
|
||||
"description": "Cross-environment hash functions (SHA256 and MD5) for Node.js and browsers, with support for strings, streams, and files.",
|
||||
"npmPackagename": "@push.rocks/smarthash",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@ -15,6 +15,9 @@
|
||||
"MD5",
|
||||
"security",
|
||||
"node.js",
|
||||
"browser",
|
||||
"cross-environment",
|
||||
"web crypto",
|
||||
"stream hashing",
|
||||
"file hashing",
|
||||
"synchronous hashing",
|
||||
|
14
package.json
14
package.json
@ -2,12 +2,12 @@
|
||||
"name": "@push.rocks/smarthash",
|
||||
"version": "3.0.4",
|
||||
"private": false,
|
||||
"description": "Provides simplified access to Node.js hash functions, including SHA256 and MD5, with support for strings, streams, and files.",
|
||||
"description": "Cross-environment hash functions (SHA256 and MD5) for Node.js and browsers, with support for strings, streams, and files.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --web)",
|
||||
"build": "(tsbuild --web --allowimplicitany)",
|
||||
"build": "(tsbuild tsfolders --allowimplicitany)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"keywords": [
|
||||
@ -17,6 +17,9 @@
|
||||
"MD5",
|
||||
"security",
|
||||
"node.js",
|
||||
"browser",
|
||||
"cross-environment",
|
||||
"web crypto",
|
||||
"stream hashing",
|
||||
"file hashing",
|
||||
"synchronous hashing",
|
||||
@ -30,10 +33,10 @@
|
||||
"@git.zone/tsbuild": "^2.1.70",
|
||||
"@git.zone/tsrun": "^1.2.46",
|
||||
"@git.zone/tstest": "^1.0.81",
|
||||
"@push.rocks/tapbundle": "^5.0.15",
|
||||
"@types/node": "^20.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@push.rocks/smartenv": "^5.0.5",
|
||||
"@push.rocks/smartjson": "^5.0.10",
|
||||
"@push.rocks/smartpromise": "^4.0.3",
|
||||
"@types/through2": "^2.0.39",
|
||||
@ -59,5 +62,6 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://code.foss.global/push.rocks/smarthash.git"
|
||||
}
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
|
||||
}
|
||||
|
11801
pnpm-lock.yaml
generated
11801
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
40
readme.plan.md
Normal file
40
readme.plan.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Merge Plan: isohash into smarthash
|
||||
|
||||
**First line: Command to reread CLAUDE.md**: `cat ~/.claude/CLAUDE.md`
|
||||
|
||||
## Objective
|
||||
Merge the functionality from @push.rocks/isohash into @push.rocks/smarthash to provide cross-environment hash support (browser and Node.js).
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### 1. Create ts_web directory structure ✓
|
||||
- Create `./ts_web` directory for browser-specific code
|
||||
- This will house the Web Crypto API implementation
|
||||
|
||||
### 2. Add required dependencies ✓
|
||||
- Add `@push.rocks/smartenv` to package.json dependencies
|
||||
- This is needed for environment detection
|
||||
|
||||
### 3. Create web-specific plugin file ✓
|
||||
- Create `ts_web/plugins.ts` with smartenv import
|
||||
- Follow the plugins pattern used in the Node.js version
|
||||
|
||||
### 4. Implement browser-compatible hash functions ✓
|
||||
- Copy and adapt `index.ts` from isohash to `ts_web/index.ts`
|
||||
- Remove circular dependency (isohash importing smarthash)
|
||||
- Use native Web Crypto API for SHA256 in browser
|
||||
- Maintain compatibility with existing smarthash API
|
||||
|
||||
### 5. Build and verify ✓
|
||||
- Run `pnpm build` to ensure TypeScript compilation succeeds
|
||||
- Check that both Node.js and browser builds are created
|
||||
|
||||
### 6. Test functionality ✓
|
||||
- Run `pnpm test` to ensure all tests pass
|
||||
- Verify browser compatibility through web tests
|
||||
|
||||
## Key Considerations
|
||||
- The web version uses native Web Crypto API for performance
|
||||
- The Node.js version continues using the existing crypto implementation
|
||||
- API remains consistent across both environments
|
||||
- No breaking changes to existing smarthash functionality
|
65
test/test.browser.ts
Normal file
65
test/test.browser.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as smarthash from '../ts_web/index.js';
|
||||
|
||||
tap.test('sha256FromString should work in browser environment', async () => {
|
||||
const testHash = await smarthash.sha256FromString('test');
|
||||
const testHash2 = await smarthash.sha256FromString('testString');
|
||||
const testHash3 = await smarthash.sha256FromString('test');
|
||||
|
||||
expect(testHash).toEqual(testHash3);
|
||||
expect(testHash).not.toEqual(testHash2);
|
||||
expect(testHash).toBeTypeofString();
|
||||
expect(testHash).toHaveLength(64); // SHA256 hash is 64 hex characters
|
||||
});
|
||||
|
||||
tap.test('sha256FromBuffer should work with Uint8Array in browser', async () => {
|
||||
const encoder = new TextEncoder();
|
||||
const buffer = encoder.encode('test');
|
||||
|
||||
const hashFromBuffer = await smarthash.sha256FromBuffer(buffer);
|
||||
const hashFromString = await smarthash.sha256FromString('test');
|
||||
|
||||
expect(hashFromBuffer).toEqual(hashFromString);
|
||||
});
|
||||
|
||||
tap.test('sha265FromObject should produce reproducible hashes', async () => {
|
||||
const hash1 = await smarthash.sha265FromObject({
|
||||
hithere: 1,
|
||||
wow: 'two',
|
||||
});
|
||||
|
||||
const hash2 = await smarthash.sha265FromObject({
|
||||
wow: 'two',
|
||||
hithere: 1,
|
||||
});
|
||||
|
||||
const hash3 = await smarthash.sha265FromObject({
|
||||
wow: 'twoe',
|
||||
hithere: 1,
|
||||
});
|
||||
|
||||
expect(hash1).toEqual(hash2);
|
||||
expect(hash1).not.toEqual(hash3);
|
||||
});
|
||||
|
||||
tap.test('sha256FromStringSync should throw in browser environment', async () => {
|
||||
expect(() => {
|
||||
smarthash.sha256FromStringSync('test');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
tap.test('sha256FromStream should throw in browser environment', async () => {
|
||||
expect(() => {
|
||||
smarthash.sha256FromStream(null);
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
tap.test('sha256FromFile should throw in browser environment', async () => {
|
||||
await expect(smarthash.sha256FromFile('./test.txt')).rejects.toThrow();
|
||||
});
|
||||
|
||||
tap.test('md5FromString should throw in browser environment', async () => {
|
||||
await expect(smarthash.md5FromString('test')).rejects.toThrow();
|
||||
});
|
||||
|
||||
export default tap.start();
|
@ -1,4 +1,4 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import * as smarthash from '../ts/index.js';
|
||||
@ -52,4 +52,4 @@ tap.test('should create md5hash from string', async () => {
|
||||
expect(md5Hash).toEqual('c6f7c372641dd25e0fddf0215375561f');
|
||||
});
|
||||
|
||||
tap.start();
|
||||
export default tap.start();
|
||||
|
84
ts_web/index.ts
Normal file
84
ts_web/index.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
/**
|
||||
* Convert ArrayBuffer to hex string
|
||||
*/
|
||||
const hex = (buffer: ArrayBuffer): string => {
|
||||
const hexCodes: string[] = [];
|
||||
const view = new DataView(buffer);
|
||||
for (let i = 0; i < view.byteLength; i += 4) {
|
||||
// Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)
|
||||
const value = view.getUint32(i);
|
||||
// toString(16) will give the hex representation of the number without padding
|
||||
const stringValue = value.toString(16);
|
||||
// We use concatenation and slice for padding
|
||||
const padding = '00000000';
|
||||
const paddedValue = (padding + stringValue).slice(-padding.length);
|
||||
hexCodes.push(paddedValue);
|
||||
}
|
||||
|
||||
// Join all the hex strings into one
|
||||
return hexCodes.join("");
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes sha256 Hash from String
|
||||
*/
|
||||
export const sha256FromString = async (stringArg: string): Promise<string> => {
|
||||
// Get the string as arraybuffer.
|
||||
const buffer = (new TextEncoder()).encode(stringArg);
|
||||
const hash = await crypto.subtle.digest("SHA-256", buffer);
|
||||
const result = hex(hash);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes sha256 Hash from String synchronously
|
||||
* Note: In browser environment, this is still async internally but we maintain the API
|
||||
*/
|
||||
export const sha256FromStringSync = (stringArg: string): string => {
|
||||
console.warn('sha256FromStringSync is not truly synchronous in browser environment');
|
||||
throw new Error('sha256FromStringSync is not supported in browser environment. Use sha256FromString instead.');
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes sha256 Hash from ArrayBuffer
|
||||
*/
|
||||
export const sha256FromBuffer = async (bufferArg: ArrayBuffer | Uint8Array): Promise<string> => {
|
||||
const hash = await crypto.subtle.digest("SHA-256", bufferArg);
|
||||
const result = hex(hash);
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* computes sha265 Hash from Object
|
||||
*/
|
||||
export const sha265FromObject = async (objectArg: any): Promise<string> => {
|
||||
const stringifiedObject = plugins.smartjson.stringify(objectArg);
|
||||
const hashResult = await sha256FromString(stringifiedObject);
|
||||
return hashResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* creates sha256 Hash from Stream
|
||||
* Note: Not supported in browser environment
|
||||
*/
|
||||
export const sha256FromStream = (input: any): Promise<string> => {
|
||||
throw new Error('sha256FromStream is not supported in browser environment');
|
||||
};
|
||||
|
||||
/**
|
||||
* creates sha256 Hash from File
|
||||
* Note: Not supported in browser environment
|
||||
*/
|
||||
export const sha256FromFile = async (filePath: string): Promise<string> => {
|
||||
throw new Error('sha256FromFile is not supported in browser environment');
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes MD5 Hash from String
|
||||
* Note: MD5 is not natively supported by Web Crypto API
|
||||
*/
|
||||
export const md5FromString = async (stringToHash: string): Promise<string> => {
|
||||
throw new Error('md5FromString is not supported in browser environment. Web Crypto API does not support MD5.');
|
||||
};
|
8
ts_web/plugins.ts
Normal file
8
ts_web/plugins.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// pushrocks scope
|
||||
import * as smartenv from '@push.rocks/smartenv';
|
||||
import * as smartjson from '@push.rocks/smartjson';
|
||||
|
||||
export {
|
||||
smartenv,
|
||||
smartjson
|
||||
};
|
Reference in New Issue
Block a user