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",
|
"githost": "code.foss.global",
|
||||||
"gitscope": "push.rocks",
|
"gitscope": "push.rocks",
|
||||||
"gitrepo": "smarthash",
|
"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",
|
"npmPackagename": "@push.rocks/smarthash",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
"MD5",
|
"MD5",
|
||||||
"security",
|
"security",
|
||||||
"node.js",
|
"node.js",
|
||||||
|
"browser",
|
||||||
|
"cross-environment",
|
||||||
|
"web crypto",
|
||||||
"stream hashing",
|
"stream hashing",
|
||||||
"file hashing",
|
"file hashing",
|
||||||
"synchronous hashing",
|
"synchronous hashing",
|
||||||
|
12
package.json
12
package.json
@@ -2,12 +2,12 @@
|
|||||||
"name": "@push.rocks/smarthash",
|
"name": "@push.rocks/smarthash",
|
||||||
"version": "3.0.4",
|
"version": "3.0.4",
|
||||||
"private": false,
|
"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",
|
"main": "dist_ts/index.js",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/ --web)",
|
"test": "(tstest test/ --web)",
|
||||||
"build": "(tsbuild --web --allowimplicitany)",
|
"build": "(tsbuild tsfolders --allowimplicitany)",
|
||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -17,6 +17,9 @@
|
|||||||
"MD5",
|
"MD5",
|
||||||
"security",
|
"security",
|
||||||
"node.js",
|
"node.js",
|
||||||
|
"browser",
|
||||||
|
"cross-environment",
|
||||||
|
"web crypto",
|
||||||
"stream hashing",
|
"stream hashing",
|
||||||
"file hashing",
|
"file hashing",
|
||||||
"synchronous hashing",
|
"synchronous hashing",
|
||||||
@@ -30,10 +33,10 @@
|
|||||||
"@git.zone/tsbuild": "^2.1.70",
|
"@git.zone/tsbuild": "^2.1.70",
|
||||||
"@git.zone/tsrun": "^1.2.46",
|
"@git.zone/tsrun": "^1.2.46",
|
||||||
"@git.zone/tstest": "^1.0.81",
|
"@git.zone/tstest": "^1.0.81",
|
||||||
"@push.rocks/tapbundle": "^5.0.15",
|
|
||||||
"@types/node": "^20.6.3"
|
"@types/node": "^20.6.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@push.rocks/smartenv": "^5.0.5",
|
||||||
"@push.rocks/smartjson": "^5.0.10",
|
"@push.rocks/smartjson": "^5.0.10",
|
||||||
"@push.rocks/smartpromise": "^4.0.3",
|
"@push.rocks/smartpromise": "^4.0.3",
|
||||||
"@types/through2": "^2.0.39",
|
"@types/through2": "^2.0.39",
|
||||||
@@ -59,5 +62,6 @@
|
|||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://code.foss.global/push.rocks/smarthash.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 fs from 'fs';
|
||||||
|
|
||||||
import * as smarthash from '../ts/index.js';
|
import * as smarthash from '../ts/index.js';
|
||||||
@@ -52,4 +52,4 @@ tap.test('should create md5hash from string', async () => {
|
|||||||
expect(md5Hash).toEqual('c6f7c372641dd25e0fddf0215375561f');
|
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