Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7078919
Draft: validation in worker thread
juslesan Dec 22, 2025
9bf8431
package-lock
juslesan Dec 22, 2025
19ce5d7
DestroySignal to tests
juslesan Dec 22, 2025
c4586b8
eslint
juslesan Dec 22, 2025
c2a74ed
npm run version
juslesan Dec 22, 2025
2dd5dd4
refactors
juslesan Dec 22, 2025
d6481e2
fix issue with StreamMessage passing
juslesan Dec 22, 2025
46a9c6f
revert
juslesan Dec 22, 2025
d31d1c1
nodejs workers for validation
juslesan Dec 22, 2025
360dfdc
most unit tests work now
juslesan Dec 22, 2025
494cc21
SignatureValidationData
juslesan Dec 22, 2025
4741a17
esöint
juslesan Dec 22, 2025
49ba698
increase tiemout
juslesan Jan 12, 2026
cf1fc06
Merge branch 'main' into NET-1663-validation
juslesan Jan 15, 2026
8eef444
process.exit(0) after a comand is completed to avoid hanging because …
juslesan Jan 20, 2026
642d7a1
Merge branch 'main' into NET-1663-validation
juslesan Jan 20, 2026
bde83db
destroy ServerSignatureValidation worker
juslesan Jan 20, 2026
558ee87
fix resend.ts
juslesan Jan 20, 2026
72632f4
Merge remote-tracking branch 'origin/main' into NET-1663-validation
mondoreale Jan 27, 2026
31dbc7d
Install `web-worker`
mondoreale Jan 27, 2026
cce4c35
Refactor signature validation – use `web-worker` to unify worker code…
mondoreale Jan 27, 2026
364baf1
Rename `signatureValidation` to `signatureValidationUtils`
mondoreale Jan 27, 2026
5fb810b
Fix filenames
mondoreale Jan 27, 2026
9f2ab70
Use named exports from `comlink`
mondoreale Jan 27, 2026
a7c4f0f
Custom expose for nodejs (using Comlink's `nodeAdapter`)
mondoreale Jan 27, 2026
21b3c6e
Release proxy
mondoreale Jan 27, 2026
53a1173
Merge remote-tracking branch 'origin/main' into NET-1663-validation-2
mondoreale Jan 28, 2026
c45177f
Fix signature validation tests
mondoreale Jan 28, 2026
856a012
Fix SDK's browser tests
mondoreale Jan 28, 2026
6853ccd
remove process.exit
juslesan Jan 29, 2026
ae45762
keep the process.exit
juslesan Jan 29, 2026
7e03be6
Merge remote-tracking branch 'origin/main' into NET-1663-validation
mondoreale Jan 29, 2026
738a126
Add comment explaining `servedFiles` config in SDK's Karma
mondoreale Jan 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 16 additions & 2 deletions packages/browser-test-runner/src/createKarmaConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@ import type { Configuration, ExternalItem } from 'webpack'

const DEBUG_MODE = process.env.BROWSER_TEST_DEBUG_MODE ?? false

export interface KarmaConfigOptions {
// File patterns to serve but not include in the test bundle (e.g. worker files)
servedFiles?: string[]
}

export const createKarmaConfig = (
testPaths: string[], webpackConfig: () => Configuration, localDirectory?: string
testPaths: string[],
webpackConfig: () => Configuration,
localDirectory?: string,
options: KarmaConfigOptions = {}
): (config: any) => any => {
const setupFiles = [fileURLToPath(new URL('./karma-setup.js', import.meta.url))]

Expand Down Expand Up @@ -57,7 +65,13 @@ export const createKarmaConfig = (
reporters: ['spec'],
files: [
...setupFiles,
...testPaths
...testPaths,
...(options.servedFiles ?? []).map((pattern) => ({
pattern,
included: false,
served: true,
watched: false
}))
],
preprocessors,
customLaunchers: {
Expand Down
1 change: 1 addition & 0 deletions packages/browser-test-runner/src/exports.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { createKarmaConfig } from './createKarmaConfig'
export type { KarmaConfigOptions } from './createKarmaConfig'
export { createWebpackConfig } from './createWebpackConfig'
2 changes: 2 additions & 0 deletions packages/cli-tools/src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export const createClientCommand = (
await client.destroy()
}
}
// Exit cleanly after command completes - worker threads may keep event loop alive
process.exit(0)
} catch (e: any) {
console.error(e)
process.exit(1)
Expand Down
11 changes: 7 additions & 4 deletions packages/cli-tools/src/resend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@ export const resend = async (
subscribe: boolean
): Promise<void> => {
try {
const handler = (message: any) => {
console.info(JSON.stringify(message))
}
if (subscribe) {
const handler = (message: any) => {
console.info(JSON.stringify(message))
}
await client.subscribe({
stream: streamId,
resend: resendOpts
}, handler)
} else {
await client.resend(streamId, resendOpts, handler)
const messageStream = await client.resend(streamId, resendOpts)
for await (const message of messageStream) {
console.info(JSON.stringify(message.content))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent CLI output between subscribe and resend modes

Medium Severity

Subscribe mode logs the full Message object via JSON.stringify(message) (line 24), but resend mode now logs only message.content via JSON.stringify(message.content). This creates inconsistent output where subscribe mode shows all message metadata (streamId, timestamp, publisherId, etc.) while resend mode shows only the payload content.

Fix in Cursor Fix in Web

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juslesan, thoughts on this?

}
}
} catch (err) {
console.error(err.message ?? err)
Expand Down
11 changes: 10 additions & 1 deletion packages/sdk/createKarmaConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function createKarmaConfig(testPaths: string[]): ReturnType<typeof create
'test/test-utils/jestGlobalsMock.ts'
),
'@streamr/dht': resolve(__dirname, '../dht/dist/exports-browser.cjs'),
"@/createSignatureValidationWorker": resolve(__dirname, 'src/_karma/createSignatureValidationWorker.ts'),
'@': resolve(__dirname, 'src/_browser'),
},
fallback: {
Expand All @@ -30,6 +31,14 @@ export function createKarmaConfig(testPaths: string[]): ReturnType<typeof create
'node:timers/promises': 'timers/promises',
},
}),
__dirname
__dirname,
{
/**
* Karma's Webpack copies workers from dist/workers to dist and hashes their names.
* We need to serve these files so they're accessible during tests. Normally Karma
* only serves test and setup files.
*/
servedFiles: ['dist/*.mjs']
}
)
}
1 change: 1 addition & 0 deletions packages/sdk/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const config: Config.InitialOptions = {
'@streamr/test-utils/setupCustomMatchers',
],
moduleNameMapper: {
"^@/createSignatureValidationWorker$": "<rootDir>/src/_jest/createSignatureValidationWorker.ts",
"^@/(.*)$": "<rootDir>/src/_nodejs/$1",
},
transform: {
Expand Down
3 changes: 3 additions & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"dist/exports-browser.*",
"dist/exports-umd.*",
"dist/encryption/migrations",
"dist/workers",
"!*.tsbuildinfo",
"LICENSE",
"README.md",
Expand Down Expand Up @@ -94,6 +95,7 @@
"@streamr/proto-rpc": "103.3.0",
"@streamr/trackerless-network": "103.3.0",
"@streamr/utils": "103.3.0",
"comlink": "^4.4.2",
"env-paths": "^2.2.1",
"ethers": "^6.13.0",
"eventemitter3": "^5.0.0",
Expand All @@ -112,6 +114,7 @@
"ts-toolbelt": "^9.6.0",
"tsyringe": "^4.10.0",
"uuid": "^11.1.0",
"web-worker": "^1.5.0",
"zod": "^4.1.13"
},
"optionalDependencies": {
Expand Down
57 changes: 57 additions & 0 deletions packages/sdk/rollup.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const browserAliases: Alias[] = [
]

export default defineConfig([
workerNodejs(),
workerBrowser(),
nodejs(),
nodejsTypes(),
browser(),
Expand Down Expand Up @@ -204,3 +206,58 @@ function umdMinified(): RollupOptions {
onwarn,
}
}

/**
* Worker bundle for Node.js - ESM format for use with web-worker {type: 'module'}
*/
function workerNodejs(): RollupOptions {
return {
input: './dist/nodejs/src/signature/SignatureValidationWorker.js',
context: 'globalThis',
output: {
format: 'es',
file: './dist/workers/SignatureValidationWorker.node.mjs',
sourcemap: true,
},
plugins: [
json(),
alias({
entries: nodejsAliases,
}),
nodeResolve({
preferBuiltins: true,
}),
cjs(),
],
external: [/node_modules/, /@streamr\//],
onwarn,
}
}

/**
* Worker bundle for browser - ESM format for use with web-worker {type: 'module'}
*/
function workerBrowser(): RollupOptions {
return {
input: './dist/browser/src/signature/SignatureValidationWorker.js',
context: 'self',
output: {
format: 'es',
file: './dist/workers/SignatureValidationWorker.browser.mjs',
sourcemap: true,
},
plugins: [
json(),
alias({
entries: browserAliases,
}),
nodeResolve({
browser: true,
preferBuiltins: false,
}),
cjs(),
],
external: [],
onwarn,
}
}
11 changes: 11 additions & 0 deletions packages/sdk/src/_browser/createSignatureValidationWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Browser-specific signature validation worker factory.
*/
import Worker from 'web-worker'

export function createSignatureValidationWorker(): InstanceType<typeof Worker> {
return new Worker(
new URL('./workers/SignatureValidationWorker.browser.mjs', import.meta.url),
{ type: 'module' }
)
}
11 changes: 11 additions & 0 deletions packages/sdk/src/_jest/createSignatureValidationWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Jest-specific signature validation worker factory.
*/
import Worker from 'web-worker'

export function createSignatureValidationWorker(): InstanceType<typeof Worker> {
return new Worker(
new URL('../../dist/workers/SignatureValidationWorker.node.mjs', import.meta.url),
{ type: 'module' }
)
}
11 changes: 11 additions & 0 deletions packages/sdk/src/_karma/createSignatureValidationWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Browser-specific signature validation worker factory.
*/
import Worker from 'web-worker'

export function createSignatureValidationWorker(): InstanceType<typeof Worker> {
return new Worker(
new URL('../../dist/workers/SignatureValidationWorker.browser.mjs', import.meta.url),
{ type: 'module' }
)
}
11 changes: 11 additions & 0 deletions packages/sdk/src/_nodejs/createSignatureValidationWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Node.js-specific signature validation worker factory.
*/
import Worker from 'web-worker'

export function createSignatureValidationWorker(): InstanceType<typeof Worker> {
return new Worker(
new URL('./workers/SignatureValidationWorker.node.mjs', import.meta.url),
{ type: 'module' }
)
}
31 changes: 31 additions & 0 deletions packages/sdk/src/signature/SignatureValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Unified signature validation using Web Worker.
* This offloads CPU-intensive cryptographic operations to a separate thread.
* Works in both browser and Node.js environments via platform-specific config.
*/
import { wrap, releaseProxy, type Remote } from 'comlink'
import { createSignatureValidationWorker } from '@/createSignatureValidationWorker'
import { SignatureValidationResult, toSignatureValidationData } from './signatureValidationUtils'
import type { SignatureValidationWorkerApi } from './SignatureValidationWorker'
import { StreamMessage } from '../protocol/StreamMessage'

export class SignatureValidation {
private worker: ReturnType<typeof createSignatureValidationWorker>
private workerApi: Remote<SignatureValidationWorkerApi>

constructor() {
this.worker = createSignatureValidationWorker()
this.workerApi = wrap<SignatureValidationWorkerApi>(this.worker)
}

async validateSignature(message: StreamMessage): Promise<SignatureValidationResult> {
// Convert class instance to plain serializable data before sending to worker
const data = toSignatureValidationData(message)
return this.workerApi.validateSignature(data)
}

destroy(): void {
this.workerApi[releaseProxy]()
this.worker.terminate()
}
}
18 changes: 18 additions & 0 deletions packages/sdk/src/signature/SignatureValidationWorker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expose } from 'comlink'
import {
validateSignatureData,
SignatureValidationResult,
SignatureValidationData,
} from './signatureValidationUtils'

const workerApi = {
validateSignature: async (
data: SignatureValidationData
): Promise<SignatureValidationResult> => {
return validateSignatureData(data)
},
}

export type SignatureValidationWorkerApi = typeof workerApi

expose(workerApi)
Loading